diff --git a/.github/ISSUE_TEMPLATE/oss-gg-hack-submission.yml b/.github/ISSUE_TEMPLATE/oss-gg-hack-submission.yml new file mode 100644 index 000000000000..d68d45cffb14 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/oss-gg-hack-submission.yml @@ -0,0 +1,33 @@ +name: oss.gg hack submission ๐Ÿ•น๏ธ +description: "Submit your contribution for the for the oss.gg hackathon" +title: "[๐Ÿ•น๏ธ]" +labels: ๐Ÿ•น๏ธ oss.gg, player submission, hacktoberfest +assignees: [] +body: + - type: textarea + id: contribution-name + attributes: + label: What side quest or challenge are you solving? + description: Add the name of the side quest or challenge. + validations: + required: true + - type: textarea + id: points + attributes: + label: Points + description: How many points are assigned to this contribution? + validations: + required: true + - type: textarea + id: description + attributes: + label: Description + description: What's the task your performed? + validations: + - type: textarea + id: proof + attributes: + label: Provide proof that you've completed the task + description: Screenshots, loom recordings, links to the content you shared or interacted with. + validations: + required: true 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/README.md b/README.md index f0c96b0a32ac..86ed0fe51a6f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +

+ + + + + Hacktoberfest + + +


@@ -34,7 +43,7 @@ We felt the need for a CRM platform that empowers rather than constrains. We bel Go to demo.twenty.com and login with the following credentials: ``` -email: noah@demo.dev +email: tim@apple.dev password: Applecar2025 ``` 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..b61f187117b5 --- /dev/null +++ b/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md @@ -0,0 +1,25 @@ +**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/) + +ยป 11-October-2024 by [thefool76](https://oss.gg/thefool76) poster Link: [poster](https://drive.google.com/file/d/1cIC1eitvY6zKVTXKq2LnVrS_2Ho9H8-P/view?usp=sharing) + +ยป 12-October-2024 by [Ionfinisher](https://oss.gg/Ionfinisher) poster Link: [poster](https://x.com/ion_finisher/status/1845168965963628802) + +--- 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..ceee0fa8e4da --- /dev/null +++ b/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md @@ -0,0 +1,28 @@ +**Side Quest**: Design/Create new Twenty logo, tweet your design, and mention @twentycrm. +**Points**: 300 Points +**Proof**: Create a logo upload 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 ๐Ÿ‘‡ + +//////////////////////////// + +ยป 08-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) Logo Link: [logo](https://drive.google.com/drive/folders/13k22xMnX2fhnWK94vas_hO1t-ImqXcHZ?usp=drive_link) ยป tweet Link: [tweet](https://x.com/adityadeshlahre/status/1843354963176718374) + +ยป 11-October-2024 by [thefool76](https://oss.gg/thefool76) Logo Link: [logo](https://drive.google.com/file/d/1DxSwNY_i90kGgWzPQj5SxScBz_6r02l4/view?usp=sharing) ยป tweet Link: [tweet](https://x.com/thefool1135/status/1844693487067034008) + +ยป 13-October-2024 by [Atharva_404](https://oss.gg/Atharva-3000) Logo Link: [logo](https://drive.google.com/drive/folders/1XB7ELR7kPA4x7Fx5RQr8wo5etdZAZgcs?usp=drive_link) ยป tweet Link: [tweet](https://x.com/0x_atharva/status/1845421218914095453) + +ยป 13-October-2024 by [Ionfinisher](https://oss.gg/Ionfinisher) Logo Link: [logo](https://drive.google.com/file/d/1l9vE8CIjW9KfdioI5WKzxrdmvO8LR4j7/view?usp=drive_link) ยป tweet Link: [tweet](https://x.com/ion_finisher/status/1845466470429442163) + + +--- 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..168e311b9a32 --- /dev/null +++ b/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md @@ -0,0 +1,45 @@ +**Side Quest**: Like & Re-Tweet oss.gg Launch Tweet. Quote-tweet it tagging @twentycrm to say youโ€™ll be contributing. +**Points**: 50 Points +**Proof**: Add a screenshot of the retweet to the PR description. Add a link to your retweet in the list below. + +Please follow the following schema: + +--- + +ยป 05-April-2024 by YOUR NAME +ยป Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn ๐Ÿ‘‡ + +//////////////////////////// + +ยป 13-October-2024 by Vanshika Dargan +ยป Link to Tweet: https://x.com/VanshikaDargan/status/1845467453108949123 + +ยป 13-October-2024 by Utsav Bhattarai +ยป Link to Tweet: https://x.com/utsavdotdev/status/1845417863462649900 + +ยป 10-October-2024 by Devansh Baghel +ยป Link to Tweet: https://x.com/DevanshBaghel5/status/1844359648037748954 + +ยป 11-October-2024 by Bhavesh Mishra +ยป Link to Tweet: https://x.com/thefool1135/status/1844453425188405326 + +ยป 11-October-2024 by Chirag Arora +ยป Link to Tweet: https://x.com/Chirag8023/status/1844689900668682699 + +ยป 11-October-2024 by Aritra Sadhukhan +ยป Link to Tweet: https://x.com/AritraDevelops/status/1844670236512878646 + +ยป 13-October-2024 by Nabhag Motivaras +ยป Link to Tweet: https://x.com/NabhagMotivaras/status/1845449144695218357 + +ยป 13-October-2024 by Ali Yar Khan +ยป Link to Tweet: https://x.com/Mr_Programmer14/status/1845527862549577860 + +ยป 13-October-2024 by Yash Parmar +ยป Link to Tweet: https://x.com/yashp3020/status/1845720834716959009 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..508210ae5a6e --- /dev/null +++ b/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md @@ -0,0 +1,28 @@ +**Side Quest**: Share a tweet about your favorite feature in Twenty. Tweet about your favorite feature in Twenty and mention @twentycrm. +**Points**: 50 Points +**Proof**: Add a screenshot of the tweet 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 ๐Ÿ‘‡ + +//////////////////////////// + +ยป 10-October-2024 by Devansh Baghel +ยป Link to Tweet: https://x.com/DevanshBaghel5/status/1844384722119704972 + +ยป 11-October-2024 by Bhavesh Mishra +ยป Link to Tweet: https://x.com/thefool1135/status/1844456500380696969 + +ยป 13-October-2024 by Ali Yar Khan +ยป Link to Tweet: https://x.com/Mr_Programmer14/status/1845530448245711197 +--- diff --git a/oss-gg/twenty-side-quest/3-bug-report.md b/oss-gg/twenty-side-quest/3-bug-report.md new file mode 100644 index 000000000000..d393a2cbeac8 --- /dev/null +++ b/oss-gg/twenty-side-quest/3-bug-report.md @@ -0,0 +1,23 @@ +**Side Quest**: Create a bug report. Use the Twenty bug issue template to report a bug in detail, including steps to reproduce it. +**Points**: 50-150 Points +**Proof**: Add a link to your bug report in the list below. + +Please follow the following schema: + +--- + +ยป 05-April-2024 by YOUR NAME +ยป Link to bug report: https://github.com/twentyhq/twenty/issues/... + +--- + +//////////////////////////// + +Your turn ๐Ÿ‘‡ + +//////////////////////////// + +ยป 10-October-2024 by Devansh Baghel +ยป Link to bug report: https://github.com/twentyhq/twenty/issues/7560 + +--- diff --git a/oss-gg/twenty-side-quest/4-meme-magic.md b/oss-gg/twenty-side-quest/4-meme-magic.md new file mode 100644 index 000000000000..b5cb3263d218 --- /dev/null +++ b/oss-gg/twenty-side-quest/4-meme-magic.md @@ -0,0 +1,34 @@ +**Side Quest**: Meme Magic: Craft a meme where the number twenty plays a role. Tweet it, and tag @twentycrm. +**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 ๐Ÿ‘‡ + +//////////////////////////// + +ยป 10-October-2024 by Teddy ASSIH +ยป Link to Tweet: https://x.com/ion_finisher/status/1844389252253299173 + +ยป 11-October-2024 by Bhavesh Mishra +ยป Link to Tweet: https://x.com/thefool1135/status/1844458836402503931 + +ยป 12-October-2024 by Chirag Arora +ยป Link to Tweet: https://x.com/Chirag8023/status/1845108226527994222 + +ยป 13-October-2024 by Ali Yar Khan +ยป Link to Tweet: https://x.com/Mr_Programmer14/status/1845537662587072697 + +ยป 14-October-2024 by Yash Parmar +ยป Link to Tweet: [https://x.com/yashp3020/status/1845108226527994222](https://x.com/yashp3020/status/1845720142702842093) +--- diff --git a/oss-gg/twenty-side-quest/5-gif-magic.md b/oss-gg/twenty-side-quest/5-gif-magic.md new file mode 100644 index 000000000000..20467fef4784 --- /dev/null +++ b/oss-gg/twenty-side-quest/5-gif-magic.md @@ -0,0 +1,33 @@ +**Side Quest**: Gif Magic: Create a gif related to Twenty. Tweet it, and tag @twentycrm. +**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 gif: https://giphy.com/... + +--- + +//////////////////////////// + +Your turn ๐Ÿ‘‡ + +//////////////////////////// + +ยป 10-October-2024 by Teddy ASSIH +ยป Link to gif: https://giphy.com/gifs/oss-crm-twenty-VWDHAIlGTbc6Nqdza9 + +ยป 11-October-2024 by Bhavesh Mishra +ยป Link to gif: https://shorturl.at/yln9H + +ยป 12-October-2024 by Chirag Arora +ยป Link to gif: https://giphy.com/gifs/yCJIS2MGbBdifbnuj0 + +ยป 13-October-2024 by Nabhag Motivaras +ยป Link to gif: https://giphy.com/gifs/twenty-twentycrm-opensourcecrm-wCcsmnJuzzzGrfuf9B + + +--- diff --git a/oss-gg/twenty-side-quest/6-quest-wizard.md b/oss-gg/twenty-side-quest/6-quest-wizard.md new file mode 100644 index 000000000000..9543e3767d6f --- /dev/null +++ b/oss-gg/twenty-side-quest/6-quest-wizard.md @@ -0,0 +1,19 @@ +**Side Quest**: Complete all Twenty 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/package.json b/package.json index 640ba3bb5c96..a4dc90df92ac 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,6 @@ "@blocknote/react": "^0.15.3", "@codesandbox/sandpack-react": "^2.13.5", "@dagrejs/dagre": "^1.1.2", - "@docusaurus/core": "^3.1.0", - "@docusaurus/preset-classic": "^3.1.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@envelop/on-resolve": "^4.1.0", @@ -38,8 +36,6 @@ "@nestjs/serve-static": "^4.0.1", "@nestjs/terminus": "^9.2.2", "@nestjs/typeorm": "^10.0.0", - "@nivo/calendar": "^0.84.0", - "@nivo/core": "^0.84.0", "@nx/eslint-plugin": "^17.2.8", "@octokit/graphql": "^7.0.2", "@ptc-org/nestjs-query-core": "^4.2.0", @@ -77,13 +73,13 @@ "class-transformer": "^0.5.1", "clsx": "^2.1.1", "cross-env": "^7.0.3", + "css-loader": "^7.1.2", "danger-plugin-todos": "^1.3.1", "dataloader": "^2.2.2", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", "debounce": "^2.0.0", "deep-equal": "^2.2.2", - "docusaurus-node-polyfills": "^1.0.0", "dompurify": "^3.0.11", "dotenv-cli": "^7.2.1", "drizzle-orm": "^0.29.3", @@ -198,8 +194,6 @@ "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.24.6", "@crxjs/vite-plugin": "^1.0.14", - "@docusaurus/module-type-aliases": "^3.1.0", - "@docusaurus/tsconfig": "3.1.0", "@graphql-codegen/cli": "^3.3.1", "@graphql-codegen/client-preset": "^4.1.0", "@graphql-codegen/typescript": "^3.0.4", @@ -273,6 +267,7 @@ "@types/node": "18.19.26", "@types/passport-google-oauth20": "^2.0.11", "@types/passport-jwt": "^3.0.8", + "@types/pluralize": "^0.0.33", "@types/react": "^18.2.39", "@types/react-datepicker": "^6.2.0", "@types/react-dom": "^18.2.15", @@ -343,7 +338,7 @@ }, "license": "AGPL-3.0", "name": "twenty", - "packageManager": "yarn@4.3.1", + "packageManager": "yarn@4.4.0", "resolutions": { "graphql": "16.8.0", "type-fest": "4.10.1", diff --git a/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx b/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx index 1b1af1e59ac0..760b7625ee2a 100644 --- a/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx +++ b/packages/twenty-chrome-extension/src/options/modules/ui/input/components/TextInput.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import styled from '@emotion/styled'; +import React, { useId } from 'react'; interface TextInputProps { label?: string; @@ -18,7 +18,7 @@ const StyledContainer = styled.div<{ fullWidth?: boolean }>` margin-bottom: ${({ theme }) => theme.spacing(4)}; `; -const StyledLabel = styled.span` +const StyledLabel = styled.label` color: ${({ theme }) => theme.font.color.light}; font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; @@ -65,12 +65,15 @@ const TextInput: React.FC = ({ placeholder, icon, }) => { + const inputId = useId(); + return ( - {label && {label}} + {label && {label}} {icon && {icon}} onChange(e.target.value)} diff --git a/packages/twenty-docker/.env.example b/packages/twenty-docker/.env.example index c25482220fce..59d8d03f93a7 100644 --- a/packages/twenty-docker/.env.example +++ b/packages/twenty-docker/.env.example @@ -5,6 +5,8 @@ TAG=latest PG_DATABASE_HOST=db:5432 SERVER_URL=http://localhost:3000 +# REDIS_HOST=redis +# REDIS_PORT=6379 # Use openssl rand -base64 32 for each secret # ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access @@ -19,5 +21,3 @@ STORAGE_TYPE=local # STORAGE_S3_REGION=eu-west3 # STORAGE_S3_NAME=my-bucket # STORAGE_S3_ENDPOINT= - -MESSAGE_QUEUE_TYPE=pg-boss diff --git a/packages/twenty-docker/Makefile b/packages/twenty-docker/Makefile index deee953f19cb..e8d77b60bf53 100644 --- a/packages/twenty-docker/Makefile +++ b/packages/twenty-docker/Makefile @@ -1,28 +1,3 @@ -dev-build: - @docker compose -f dev/docker-compose.yml down -v - @docker compose -f dev/docker-compose.yml build - -dev-up: - @docker compose -f dev/docker-compose.yml up -d - -dev-start: - @docker compose -f dev/docker-compose.yml start - -dev-stop: - @docker compose -f dev/docker-compose.yml stop - -dev-down: - @docker compose -f dev/docker-compose.yml down -v - -dev-sh: - @docker compose -f dev/docker-compose.yml exec twenty-dev sh - -dev-postgres-build: - @docker stop twenty_postgres || true - @docker rm twenty_postgres || true - @docker volume rm twenty_db_data || true - @docker compose -f dev/docker-compose.yml up --build postgres -d - prod-build: @cd ../.. && docker build -f ./packages/twenty-docker/twenty/Dockerfile --tag twenty . && cd - diff --git a/packages/twenty-docker/docker-compose.yml b/packages/twenty-docker/docker-compose.yml index 553d8ca6c9fa..b2efc1a168e4 100644 --- a/packages/twenty-docker/docker-compose.yml +++ b/packages/twenty-docker/docker-compose.yml @@ -25,7 +25,8 @@ services: PG_DATABASE_URL: postgres://twenty:twenty@${PG_DATABASE_HOST}/default SERVER_URL: ${SERVER_URL} FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL} - MESSAGE_QUEUE_TYPE: ${MESSAGE_QUEUE_TYPE} + REDIS_PORT: ${REDIS_PORT:-6379} + REDIS_HOST: ${REDIS_HOST:-redis} ENABLE_DB_MIGRATIONS: "true" @@ -34,6 +35,7 @@ services: STORAGE_S3_REGION: ${STORAGE_S3_REGION} STORAGE_S3_NAME: ${STORAGE_S3_NAME} STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} + ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET} LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET} REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET} @@ -57,7 +59,8 @@ services: PG_DATABASE_URL: postgres://twenty:twenty@${PG_DATABASE_HOST}/default SERVER_URL: ${SERVER_URL} FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL} - MESSAGE_QUEUE_TYPE: ${MESSAGE_QUEUE_TYPE} + REDIS_PORT: ${REDIS_PORT:-6379} + REDIS_HOST: ${REDIS_HOST:-redis} ENABLE_DB_MIGRATIONS: "false" # it already runs on the server @@ -65,6 +68,7 @@ services: STORAGE_S3_REGION: ${STORAGE_S3_REGION} STORAGE_S3_NAME: ${STORAGE_S3_NAME} STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} + ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET} LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET} REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET} @@ -89,6 +93,12 @@ services: retries: 10 restart: always + redis: + image: redis + ports: + - "6379:6379" + restart: always + volumes: docker-data: db-data: diff --git a/packages/twenty-docker/k8s/manifests/deployment-db.yaml b/packages/twenty-docker/k8s/manifests/deployment-db.yaml index 2e317376d53b..31a3361774e4 100644 --- a/packages/twenty-docker/k8s/manifests/deployment-db.yaml +++ b/packages/twenty-docker/k8s/manifests/deployment-db.yaml @@ -22,33 +22,33 @@ spec: app: twentycrm-db spec: volumes: - - name: twentycrm-db-data - persistentVolumeClaim: - claimName: twentycrm-db-pvc + - name: twentycrm-db-data + persistentVolumeClaim: + claimName: twentycrm-db-pvc containers: - - env: - - name: POSTGRES_PASSWORD - value: "twenty" - - name: BITNAMI_DEBUG - value: "true" - - image: twentycrm/twenty-postgres:latest - imagePullPolicy: Always - name: twentycrm - ports: - - containerPort: 5432 - name: tcp - protocol: TCP - resources: - requests: - memory: "256Mi" - cpu: "250m" - limits: - memory: "1024Mi" - cpu: "1000m" - stdin: true - tty: true - volumeMounts: - - mountPath: /bitnami/postgresql - name: twentycrm-db-data + - name: twentycrm + image: twentycrm/twenty-postgres:latest + imagePullPolicy: Always + env: + - name: POSTGRES_PASSWORD + value: "twenty" + - name: BITNAMI_DEBUG + value: "true" + ports: + - containerPort: 5432 + name: tcp + protocol: TCP + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1024Mi" + cpu: "1000m" + stdin: true + tty: true + volumeMounts: + - mountPath: /bitnami/postgresql + name: twentycrm-db-data dnsPolicy: ClusterFirst restartPolicy: Always diff --git a/packages/twenty-docker/k8s/manifests/deployment-redis.yaml b/packages/twenty-docker/k8s/manifests/deployment-redis.yaml new file mode 100644 index 000000000000..e09874aac262 --- /dev/null +++ b/packages/twenty-docker/k8s/manifests/deployment-redis.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: twentycrm-redis + name: twentycrm-redis + namespace: twentycrm +spec: + progressDeadlineSeconds: 600 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + selector: + matchLabels: + app: twentycrm-redis + template: + metadata: + labels: + app: twentycrm-redis + spec: + containers: + - name: redis + image: redis/redis-stack-server:latest + imagePullPolicy: Always + env: + - name: PORT + value: 6379 + ports: + - containerPort: 6379 + name: redis + protocol: TCP + resources: + requests: + memory: "1024Mi" + cpu: "250m" + limits: + memory: "2048Mi" + cpu: "500m" + + dnsPolicy: ClusterFirst + restartPolicy: Always diff --git a/packages/twenty-docker/k8s/manifests/deployment-server.yaml b/packages/twenty-docker/k8s/manifests/deployment-server.yaml index b4596e9fc87b..b1229d649bbb 100644 --- a/packages/twenty-docker/k8s/manifests/deployment-server.yaml +++ b/packages/twenty-docker/k8s/manifests/deployment-server.yaml @@ -22,67 +22,78 @@ spec: app: twentycrm-server spec: volumes: - - name: twentycrm-server-data - persistentVolumeClaim: - claimName: twentycrm-server-pvc + - name: twentycrm-server-data + persistentVolumeClaim: + claimName: twentycrm-server-pvc + - name: twentycrm-docker-data + persistentVolumeClaim: + claimName: twentycrm-docker-data-pvc containers: - - env: - - name: PORT - value: 3000 - - name: SERVER_URL - value: "https://crm.example.com:443" - - name: FRONT_BASE_URL - value: "https://crm.example.com:443" - - name: PG_DATABASE_URL - value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default" - - name: ENABLE_DB_MIGRATIONS - value: "true" - - name: SIGN_IN_PREFILLED - value: "true" - - name: STORAGE_TYPE - value: "local" - - name: "MESSAGE_QUEUE_TYPE" - value: "pg-boss" - - name: ACCESS_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: accessToken - - name: LOGIN_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: loginToken - - name: REFRESH_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: refreshToken - - name: FILE_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: fileToken - - image: twentycrm/twenty:latest - imagePullPolicy: Always - name: twentycrm - ports: - - containerPort: 3000 - name: http-tcp - protocol: TCP - resources: - requests: - memory: "256Mi" - cpu: "250m" - limits: - memory: "1024Mi" - cpu: "1000m" - stdin: true - tty: true - volumeMounts: - - mountPath: /app/docker-data - name: twentycrm-server-data - - mountPath: /app/.local-storage - name: twentycrm-server-data + - name: twentycrm + image: twentycrm/twenty:latest + imagePullPolicy: Always + env: + - name: PORT + value: 3000 + - name: SERVER_URL + value: "https://crm.example.com:443" + - name: FRONT_BASE_URL + value: "https://crm.example.com:443" + - name: "PG_DATABASE_URL" + value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default" + - name: "REDIS_HOST" + value: "twentycrm-redis.twentycrm.svc.cluster.local" + - name: "REDIS_PORT" + value: 6379 + - name: ENABLE_DB_MIGRATIONS + value: "true" + - name: SIGN_IN_PREFILLED + value: "true" + - name: STORAGE_TYPE + value: "local" + - name: "MESSAGE_QUEUE_TYPE" + value: "bull-mq" + - name: "ACCESS_TOKEN_EXPIRES_IN" + value: "7d" + - name: "LOGIN_TOKEN_EXPIRES_IN" + value: "1h" + - name: ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: accessToken + - name: LOGIN_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: loginToken + - name: REFRESH_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: refreshToken + - name: FILE_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: fileToken + ports: + - containerPort: 3000 + name: http-tcp + protocol: TCP + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1024Mi" + cpu: "1000m" + stdin: true + tty: true + volumeMounts: + - mountPath: /app/docker-data + name: twentycrm-docker-data + - mountPath: /app/packages/twenty-server/.local-storage + name: twentycrm-server-data dnsPolicy: ClusterFirst restartPolicy: Always diff --git a/packages/twenty-docker/k8s/manifests/deployment-worker.yaml b/packages/twenty-docker/k8s/manifests/deployment-worker.yaml index b3834c46e515..b3a7e07a19aa 100644 --- a/packages/twenty-docker/k8s/manifests/deployment-worker.yaml +++ b/packages/twenty-docker/k8s/manifests/deployment-worker.yaml @@ -21,58 +21,60 @@ spec: labels: app: twentycrm-worker spec: - volumes: - - name: twentycrm-worker-data - persistentVolumeClaim: - claimName: twentycrm-worker-pvc containers: - - env: - - name: SERVER_URL - value: "https://crm.example.com:443" - - name: FRONT_BASE_URL - value: "https://crm.example.com:443" - - name: PG_DATABASE_URL - value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default" - - name: ENABLE_DB_MIGRATIONS - value: "false" # it already runs on the server - - name: STORAGE_TYPE - value: "local" - - name: "MESSAGE_QUEUE_TYPE" - value: "pg-boss" - - name: ACCESS_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: accessToken - - name: LOGIN_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: loginToken - - name: REFRESH_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: refreshToken - - name: FILE_TOKEN_SECRET - valueFrom: - secretKeyRef: - name: tokens - key: fileToken - - image: twentycrm/twenty:latest - imagePullPolicy: Always - name: twentycrm - command: - - yarn - - worker:prod - resources: - requests: - memory: "256Mi" - cpu: "250m" - limits: - memory: "1024Mi" - cpu: "1000m" - stdin: true - tty: true + - name: twentycrm + image: twentycrm/twenty:latest + imagePullPolicy: Always + env: + - name: SERVER_URL + value: "https://crm.example.com:443" + - name: FRONT_BASE_URL + value: "https://crm.example.com:443" + - name: PG_DATABASE_URL + value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default" + - name: ENABLE_DB_MIGRATIONS + value: "false" # it already runs on the server + - name: STORAGE_TYPE + value: "local" + - name: "MESSAGE_QUEUE_TYPE" + value: "bull-mq" + - name: "CACHE_STORAGE_TYPE" + value: "redis" + - name: "REDIS_HOST" + value: "twentycrm-redis.twentycrm.svc.cluster.local" + - name: "REDIS_PORT" + value: 6379 + - name: ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: accessToken + - name: LOGIN_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: loginToken + - name: REFRESH_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: refreshToken + - name: FILE_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: tokens + key: fileToken + command: + - yarn + - worker:prod + resources: + requests: + memory: "1024Mi" + cpu: "250m" + limits: + memory: "2048Mi" + cpu: "1000m" + stdin: true + tty: true dnsPolicy: ClusterFirst restartPolicy: Always diff --git a/packages/twenty-docker/k8s/manifests/ingress.yaml b/packages/twenty-docker/k8s/manifests/ingress.yaml index b334aac21916..0bbae11dd72b 100644 --- a/packages/twenty-docker/k8s/manifests/ingress.yaml +++ b/packages/twenty-docker/k8s/manifests/ingress.yaml @@ -4,21 +4,21 @@ metadata: name: twentycrm namespace: twentycrm annotations: - nginx.ingress.kubernetes.io/configuration-snippet: | + nginx.ingress.kubernetes.io/configuration-snippet: | more_set_headers "X-Forwarded-For $http_x_forwarded_for"; - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - kubernetes.io/ingress.class: "nginx" - nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + nginx.ingress.kubernetes.io/force-ssl-redirect: "false" + kubernetes.io/ingress.class: "nginx" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" spec: ingressClassName: nginx rules: - - host: crm.example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: twentycrm-server - port: - name: http-tcp + - host: crm.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: twentycrm-server + port: + name: http-tcp diff --git a/packages/twenty-docker/k8s/manifests/pv-docker-data.yaml b/packages/twenty-docker/k8s/manifests/pv-docker-data.yaml new file mode 100644 index 000000000000..95fc52a26251 --- /dev/null +++ b/packages/twenty-docker/k8s/manifests/pv-docker-data.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: twentycrm-docker-data-pv +spec: + storageClassName: default + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain diff --git a/packages/twenty-docker/k8s/manifests/pvc-docker-data.yaml b/packages/twenty-docker/k8s/manifests/pvc-docker-data.yaml new file mode 100644 index 000000000000..12dd071a7f21 --- /dev/null +++ b/packages/twenty-docker/k8s/manifests/pvc-docker-data.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: twentycrm-docker-data-pvc + namespace: twentycrm +spec: + storageClassName: default + volumeName: twentycrm-docker-data-pv + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi diff --git a/packages/twenty-docker/k8s/manifests/service-db.yaml b/packages/twenty-docker/k8s/manifests/service-db.yaml index bb0e38df6d6d..89dbd1464bed 100644 --- a/packages/twenty-docker/k8s/manifests/service-db.yaml +++ b/packages/twenty-docker/k8s/manifests/service-db.yaml @@ -6,9 +6,9 @@ metadata: spec: internalTrafficPolicy: Cluster ports: - - port: 5432 - protocol: TCP - targetPort: 5432 + - port: 5432 + protocol: TCP + targetPort: 5432 selector: app: twentycrm-db sessionAffinity: ClientIP diff --git a/packages/twenty-docker/k8s/manifests/service-redis.yaml b/packages/twenty-docker/k8s/manifests/service-redis.yaml new file mode 100644 index 000000000000..49f508897dfa --- /dev/null +++ b/packages/twenty-docker/k8s/manifests/service-redis.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: twentycrm-redis + namespace: twentycrm +spec: + internalTrafficPolicy: Cluster + ports: + - port: 6379 + protocol: TCP + targetPort: 6379 + selector: + app: twentycrm-redis + sessionAffinity: ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 + type: ClusterIP diff --git a/packages/twenty-docker/k8s/manifests/service-server.yaml b/packages/twenty-docker/k8s/manifests/service-server.yaml index 7fcc869a6edc..b45b28f312ff 100644 --- a/packages/twenty-docker/k8s/manifests/service-server.yaml +++ b/packages/twenty-docker/k8s/manifests/service-server.yaml @@ -6,10 +6,10 @@ metadata: spec: internalTrafficPolicy: Cluster ports: - - name: http-tcp - port: 3000 - protocol: TCP - targetPort: 3000 + - name: http-tcp + port: 3000 + protocol: TCP + targetPort: 3000 selector: app: twentycrm-server sessionAffinity: ClientIP diff --git a/packages/twenty-docker/k8s/terraform/.terraform-docs.yml b/packages/twenty-docker/k8s/terraform/.terraform-docs.yml index 00778168f3ee..792c543f4d30 100644 --- a/packages/twenty-docker/k8s/terraform/.terraform-docs.yml +++ b/packages/twenty-docker/k8s/terraform/.terraform-docs.yml @@ -15,12 +15,12 @@ output: # TwentyCRM Terraform Docs - This file was generated by [terraform-docs](https://terraform-docs.io/), for more information on how to install, configure and use visit their website. + This file was generated by [terraform-docs](https://terraform-docs.io/), for more information on how to install, configure, and use visit their website. - To update this `README.md` after changes to the Terraform code in this folder, run: `terraform-docs .` + To update this `README.md` after changes to the Terraform code in this folder, run: `terraform-docs -c `./.terraform-docs.yml .` To make configuration changes to how this doc is generated, see `./.terraform-docs.yml` - + {{ .Content }} @@ -45,4 +45,4 @@ settings: read-comments: true required: true sensitive: true - type: true \ No newline at end of file + type: true diff --git a/packages/twenty-docker/k8s/terraform/README.md b/packages/twenty-docker/k8s/terraform/README.md index 10a7ab557cb7..f6955300a63f 100644 --- a/packages/twenty-docker/k8s/terraform/README.md +++ b/packages/twenty-docker/k8s/terraform/README.md @@ -1,9 +1,9 @@ # TwentyCRM Terraform Docs -This file was generated by [terraform-docs](https://terraform-docs.io/), for more information on how to install, configure and use visit their website. +This file was generated by [terraform-docs](https://terraform-docs.io/), for more information on how to install, configure, and use visit their website. -To update this `README.md` after changes to the Terraform code in this folder, run: `terraform-docs .` +To update this `README.md` after changes to the Terraform code in this folder, run: `terraform-docs -c `./.terraform-docs.yml .` To make configuration changes to how this doc is generated, see `./.terraform-docs.yml` @@ -12,30 +12,37 @@ To make configuration changes to how this doc is generated, see `./.terraform-do | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.9.2 | -| [kubernetes](#requirement\_kubernetes) | >= 2.31.0 | +| [kubernetes](#requirement\_kubernetes) | >= 2.32.0 | +| [random](#requirement\_random) | >= 3.6.3 | ## Providers | Name | Version | |------|---------| -| [kubernetes](#provider\_kubernetes) | >= 2.31.0 | +| [kubernetes](#provider\_kubernetes) | >= 2.32.0 | +| [random](#provider\_random) | >= 3.6.3 | ## Resources | Name | Type | |------|------| | [kubernetes_deployment.twentycrm_db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource | +| [kubernetes_deployment.twentycrm_redis](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource | | [kubernetes_deployment.twentycrm_server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource | | [kubernetes_deployment.twentycrm_worker](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource | | [kubernetes_ingress.twentycrm](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/ingress) | resource | | [kubernetes_namespace.twentycrm](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | | [kubernetes_persistent_volume.db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume) | resource | +| [kubernetes_persistent_volume.docker_data](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume) | resource | | [kubernetes_persistent_volume.server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume) | resource | | [kubernetes_persistent_volume_claim.db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume_claim) | resource | +| [kubernetes_persistent_volume_claim.docker_data](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume_claim) | resource | | [kubernetes_persistent_volume_claim.server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume_claim) | resource | | [kubernetes_secret.twentycrm_tokens](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource | | [kubernetes_service.twentycrm_db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource | +| [kubernetes_service.twentycrm_redis](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource | | [kubernetes_service.twentycrm_server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource | +| [random_bytes.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/bytes) | resource | ## Inputs @@ -43,22 +50,24 @@ To make configuration changes to how this doc is generated, see `./.terraform-do |------|-------------|------|---------|:--------:| | [twentycrm\_app\_hostname](#input\_twentycrm\_app\_hostname) | The protocol, DNS fully qualified hostname, and port used to access TwentyCRM in your environment. Ex: https://crm.example.com:443 | `string` | n/a | yes | | [twentycrm\_pgdb\_admin\_password](#input\_twentycrm\_pgdb\_admin\_password) | TwentyCRM password for postgres database. | `string` | n/a | yes | -| [twentycrm\_token\_accessToken](#input\_twentycrm\_token\_accessToken) | TwentyCRM access Token | `string` | n/a | yes | -| [twentycrm\_token\_fileToken](#input\_twentycrm\_token\_fileToken) | TwentyCRM file Token | `string` | n/a | yes | -| [twentycrm\_token\_loginToken](#input\_twentycrm\_token\_loginToken) | TwentyCRM login Token | `string` | n/a | yes | -| [twentycrm\_token\_refreshToken](#input\_twentycrm\_token\_refreshToken) | TwentyCRM refresh Token | `string` | n/a | yes | | [twentycrm\_app\_name](#input\_twentycrm\_app\_name) | A friendly name prefix to use for every component deployed. | `string` | `"twentycrm"` | no | | [twentycrm\_db\_image](#input\_twentycrm\_db\_image) | TwentyCRM image for database deployment. This defaults to latest. | `string` | `"twentycrm/twenty-postgres:latest"` | no | | [twentycrm\_db\_pv\_capacity](#input\_twentycrm\_db\_pv\_capacity) | Storage capacity provisioned for database persistent volume. | `string` | `"10Gi"` | no | | [twentycrm\_db\_pv\_path](#input\_twentycrm\_db\_pv\_path) | Local path to use to store the physical volume if using local storage on nodes. | `string` | `""` | no | | [twentycrm\_db\_pvc\_requests](#input\_twentycrm\_db\_pvc\_requests) | Storage capacity reservation for database persistent volume claim. | `string` | `"10Gi"` | no | | [twentycrm\_db\_replicas](#input\_twentycrm\_db\_replicas) | Number of replicas for the TwentyCRM database deployment. This defaults to 1. | `number` | `1` | no | +| [twentycrm\_docker\_data\_mount\_path](#input\_twentycrm\_docker\_data\_mount\_path) | TwentyCRM mount path for servers application data. Defaults to '/app/docker-data'. | `string` | `"/app/docker-data"` | no | +| [twentycrm\_docker\_data\_pv\_capacity](#input\_twentycrm\_docker\_data\_pv\_capacity) | Storage capacity provisioned for server persistent volume. | `string` | `"10Gi"` | no | +| [twentycrm\_docker\_data\_pv\_path](#input\_twentycrm\_docker\_data\_pv\_path) | Local path to use to store the physical volume if using local storage on nodes. | `string` | `""` | no | +| [twentycrm\_docker\_data\_pvc\_requests](#input\_twentycrm\_docker\_data\_pvc\_requests) | Storage capacity reservation for server persistent volume claim. | `string` | `"10Gi"` | no | | [twentycrm\_namespace](#input\_twentycrm\_namespace) | Namespace for all TwentyCRM resources | `string` | `"twentycrm"` | no | -| [twentycrm\_server\_data\_mount\_path](#input\_twentycrm\_server\_data\_mount\_path) | TwentyCRM mount path for servers application data. Defaults to '/app/docker-data'. | `string` | `"/app/docker-data"` | no | +| [twentycrm\_redis\_image](#input\_twentycrm\_redis\_image) | TwentyCRM image for Redis deployment. This defaults to latest. | `string` | `"redis/redis-stack-server:latest"` | no | +| [twentycrm\_redis\_replicas](#input\_twentycrm\_redis\_replicas) | Number of replicas for the TwentyCRM Redis deployment. This defaults to 1. | `number` | `1` | no | +| [twentycrm\_server\_data\_mount\_path](#input\_twentycrm\_server\_data\_mount\_path) | TwentyCRM mount path for servers application data. Defaults to '/app/packages/twenty-server/.local-storage'. | `string` | `"/app/packages/twenty-server/.local-storage"` | no | | [twentycrm\_server\_image](#input\_twentycrm\_server\_image) | TwentyCRM server image for the server deployment. This defaults to latest. This value is also used for the workers image. | `string` | `"twentycrm/twenty:latest"` | no | | [twentycrm\_server\_pv\_capacity](#input\_twentycrm\_server\_pv\_capacity) | Storage capacity provisioned for server persistent volume. | `string` | `"10Gi"` | no | | [twentycrm\_server\_pv\_path](#input\_twentycrm\_server\_pv\_path) | Local path to use to store the physical volume if using local storage on nodes. | `string` | `""` | no | | [twentycrm\_server\_pvc\_requests](#input\_twentycrm\_server\_pvc\_requests) | Storage capacity reservation for server persistent volume claim. | `string` | `"10Gi"` | no | | [twentycrm\_server\_replicas](#input\_twentycrm\_server\_replicas) | Number of replicas for the TwentyCRM server deployment. This defaults to 1. | `number` | `1` | no | | [twentycrm\_worker\_replicas](#input\_twentycrm\_worker\_replicas) | Number of replicas for the TwentyCRM worker deployment. This defaults to 1. | `number` | `1` | no | - \ No newline at end of file + diff --git a/packages/twenty-docker/k8s/terraform/deployment-redis.tf b/packages/twenty-docker/k8s/terraform/deployment-redis.tf new file mode 100644 index 000000000000..d867dac76ee0 --- /dev/null +++ b/packages/twenty-docker/k8s/terraform/deployment-redis.tf @@ -0,0 +1,60 @@ +resource "kubernetes_deployment" "twentycrm_redis" { + metadata { + name = "${var.twentycrm_app_name}-redis" + namespace = kubernetes_namespace.twentycrm.metadata.0.name + + labels = { + app = "${var.twentycrm_app_name}-redis" + } + } + + spec { + replicas = var.twentycrm_redis_replicas + selector { + match_labels = { + app = "${var.twentycrm_app_name}-redis" + } + } + + strategy { + type = "RollingUpdate" + rolling_update { + max_surge = "1" + max_unavailable = "1" + } + } + + template { + metadata { + labels = { + app = "${var.twentycrm_app_name}-redis" + } + } + + spec { + container { + image = var.twentycrm_redis_image + name = "redis" + + port { + container_port = 6379 + protocol = "TCP" + } + + resources { + requests = { + cpu = "250m" + memory = "1024Mi" + } + limits = { + cpu = "500m" + memory = "2048Mi" + } + } + } + dns_policy = "ClusterFirst" + restart_policy = "Always" + } + } + } +} diff --git a/packages/twenty-docker/k8s/terraform/deployment-server.tf b/packages/twenty-docker/k8s/terraform/deployment-server.tf index a3c1f9ac1d11..1868b17624da 100644 --- a/packages/twenty-docker/k8s/terraform/deployment-server.tf +++ b/packages/twenty-docker/k8s/terraform/deployment-server.tf @@ -37,20 +37,14 @@ resource "kubernetes_deployment" "twentycrm_server" { stdin = true tty = true - security_context { - allow_privilege_escalation = true - privileged = true - run_as_user = 1000 - } - env { name = "PORT" value = "3000" } - env { - name = "DEBUG_MODE" - value = false - } + # env { + # name = "DEBUG_MODE" + # value = false + # } env { name = "SERVER_URL" @@ -64,9 +58,16 @@ resource "kubernetes_deployment" "twentycrm_server" { env { name = "PG_DATABASE_URL" - value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${var.twentycrm_app_name}-db.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default" + value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${kubernetes_service.twentycrm_db.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default" + } + env { + name = "REDIS_HOST" + value = "${kubernetes_service.twentycrm_redis.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local" + } + env { + name = "REDIS_PORT" + value = 6379 } - env { name = "ENABLE_DB_MIGRATIONS" value = "true" @@ -83,7 +84,15 @@ resource "kubernetes_deployment" "twentycrm_server" { } env { name = "MESSAGE_QUEUE_TYPE" - value = "pg-boss" + value = "bull-mq" + } + env { + name = "ACCESS_TOKEN_EXPIRES_IN" + value = "7d" + } + env { + name = "LOGIN_TOKEN_EXPIRES_IN" + value = "1h" } env { name = "ACCESS_TOKEN_SECRET" @@ -145,6 +154,11 @@ resource "kubernetes_deployment" "twentycrm_server" { name = "server-data" mount_path = var.twentycrm_server_data_mount_path } + + volume_mount { + name = "docker-data" + mount_path = var.twentycrm_docker_data_mount_path + } } volume { @@ -155,6 +169,14 @@ resource "kubernetes_deployment" "twentycrm_server" { } } + volume { + name = "docker-data" + + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim.docker_data.metadata.0.name + } + } + dns_policy = "ClusterFirst" restart_policy = "Always" } @@ -162,6 +184,7 @@ resource "kubernetes_deployment" "twentycrm_server" { } depends_on = [ kubernetes_deployment.twentycrm_db, + kubernetes_deployment.twentycrm_redis, kubernetes_secret.twentycrm_tokens ] } diff --git a/packages/twenty-docker/k8s/terraform/deployment-worker.tf b/packages/twenty-docker/k8s/terraform/deployment-worker.tf index 9a005839ddda..78e5ea6dcc1d 100644 --- a/packages/twenty-docker/k8s/terraform/deployment-worker.tf +++ b/packages/twenty-docker/k8s/terraform/deployment-worker.tf @@ -50,7 +50,22 @@ resource "kubernetes_deployment" "twentycrm_worker" { env { name = "PG_DATABASE_URL" - value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${var.twentycrm_app_name}-db.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default" + value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${kubernetes_service.twentycrm_db.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default" + } + + env { + name = "CACHE_STORAGE_TYPE" + value = "redis" + } + + env { + name = "REDIS_HOST" + value = "${kubernetes_service.twentycrm_redis.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local" + } + + env { + name = "REDIS_PORT" + value = 6379 } env { @@ -64,7 +79,7 @@ resource "kubernetes_deployment" "twentycrm_worker" { } env { name = "MESSAGE_QUEUE_TYPE" - value = "pg-boss" + value = "bull-mq" } env { @@ -110,11 +125,11 @@ resource "kubernetes_deployment" "twentycrm_worker" { resources { requests = { cpu = "250m" - memory = "256Mi" + memory = "1024Mi" } limits = { cpu = "1000m" - memory = "1024Mi" + memory = "2048Mi" } } } @@ -126,6 +141,8 @@ resource "kubernetes_deployment" "twentycrm_worker" { } depends_on = [ kubernetes_deployment.twentycrm_db, - kubernetes_secret.twentycrm_tokens + kubernetes_deployment.twentycrm_redis, + kubernetes_deployment.twentycrm_server, + kubernetes_secret.twentycrm_tokens, ] } diff --git a/packages/twenty-docker/k8s/terraform/main.tf b/packages/twenty-docker/k8s/terraform/main.tf index 66ae6e18e061..a0e208d15f5d 100644 --- a/packages/twenty-docker/k8s/terraform/main.tf +++ b/packages/twenty-docker/k8s/terraform/main.tf @@ -13,7 +13,11 @@ terraform { required_providers { kubernetes = { source = "hashicorp/kubernetes" - version = ">= 2.31.0" + version = ">= 2.32.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.6.3" } } } diff --git a/packages/twenty-docker/k8s/terraform/pv-docker-data.tf b/packages/twenty-docker/k8s/terraform/pv-docker-data.tf new file mode 100644 index 000000000000..9195fff61c8a --- /dev/null +++ b/packages/twenty-docker/k8s/terraform/pv-docker-data.tf @@ -0,0 +1,19 @@ +resource "kubernetes_persistent_volume" "docker_data" { + metadata { + name = "${var.twentycrm_app_name}-docker-data-pv" + } + spec { + storage_class_name = "default" + capacity = { + storage = var.twentycrm_docker_data_pv_capacity + } + access_modes = ["ReadWriteOnce"] + # refer to Terraform Docs for your specific implementation requirements + # https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume + persistent_volume_source { + local { + path = var.twentycrm_docker_data_pv_path + } + } + } +} diff --git a/packages/twenty-docker/k8s/terraform/pvc-docker-data.tf b/packages/twenty-docker/k8s/terraform/pvc-docker-data.tf new file mode 100644 index 000000000000..daac13dcc3a3 --- /dev/null +++ b/packages/twenty-docker/k8s/terraform/pvc-docker-data.tf @@ -0,0 +1,15 @@ +resource "kubernetes_persistent_volume_claim" "docker_data" { + metadata { + name = "${var.twentycrm_app_name}-docker-data-pvc" + namespace = kubernetes_namespace.twentycrm.metadata.0.name + } + spec { + access_modes = ["ReadWriteOnce"] + resources { + requests = { + storage = var.twentycrm_docker_data_pvc_requests + } + } + volume_name = kubernetes_persistent_volume.docker_data.metadata.0.name + } +} diff --git a/packages/twenty-docker/k8s/terraform/secret.tf b/packages/twenty-docker/k8s/terraform/secret.tf index 664d07803ccc..2aa7ccf4765a 100644 --- a/packages/twenty-docker/k8s/terraform/secret.tf +++ b/packages/twenty-docker/k8s/terraform/secret.tf @@ -1,3 +1,18 @@ +locals { + tokens = [ + "accessToken", + "loginToken", + "refreshToken", + "fileToken" + ] +} + +resource "random_bytes" "this" { + for_each = toset(local.tokens) + + length = 32 +} + resource "kubernetes_secret" "twentycrm_tokens" { metadata { name = "tokens" @@ -5,11 +20,9 @@ resource "kubernetes_secret" "twentycrm_tokens" { } data = { - accessToken = var.twentycrm_token_accessToken - loginToken = var.twentycrm_token_loginToken - refreshToken = var.twentycrm_token_refreshToken - fileToken = var.twentycrm_token_fileToken + accessToken = random_bytes.this["accessToken"].base64 + loginToken = random_bytes.this["loginToken"].base64 + refreshToken = random_bytes.this["refreshToken"].base64 + fileToken = random_bytes.this["fileToken"].base64 } - - # type = "kubernetes.io/basic-auth" } diff --git a/packages/twenty-docker/k8s/terraform/service-redis.tf b/packages/twenty-docker/k8s/terraform/service-redis.tf new file mode 100644 index 000000000000..fab1c0051ccf --- /dev/null +++ b/packages/twenty-docker/k8s/terraform/service-redis.tf @@ -0,0 +1,18 @@ +resource "kubernetes_service" "twentycrm_redis" { + metadata { + name = "${var.twentycrm_app_name}-redis" + namespace = kubernetes_namespace.twentycrm.metadata.0.name + } + spec { + selector = { + app = "${var.twentycrm_app_name}-redis" + } + session_affinity = "ClientIP" + port { + port = 6379 + target_port = 6379 + } + + type = "ClusterIP" + } +} diff --git a/packages/twenty-docker/k8s/terraform/variables.tf b/packages/twenty-docker/k8s/terraform/variables.tf index 53255aaf1489..7b682db79a35 100644 --- a/packages/twenty-docker/k8s/terraform/variables.tf +++ b/packages/twenty-docker/k8s/terraform/variables.tf @@ -1,30 +1,6 @@ ###################### # Required Variables # ###################### -variable "twentycrm_token_accessToken" { - type = string - description = "TwentyCRM access Token" - sensitive = true -} - -variable "twentycrm_token_loginToken" { - type = string - description = "TwentyCRM login Token" - sensitive = true -} - -variable "twentycrm_token_refreshToken" { - type = string - description = "TwentyCRM refresh Token" - sensitive = true -} - -variable "twentycrm_token_fileToken" { - type = string - description = "TwentyCRM file Token" - sensitive = true -} - variable "twentycrm_pgdb_admin_password" { type = string description = "TwentyCRM password for postgres database." @@ -77,8 +53,8 @@ variable "twentycrm_db_replicas" { variable "twentycrm_server_data_mount_path" { type = string - default = "/app/docker-data" - description = "TwentyCRM mount path for servers application data. Defaults to '/app/docker-data'." + default = "/app/packages/twenty-server/.local-storage" + description = "TwentyCRM mount path for servers application data. Defaults to '/app/packages/twenty-server/.local-storage'." } variable "twentycrm_db_pv_path" { @@ -122,3 +98,39 @@ variable "twentycrm_namespace" { default = "twentycrm" description = "Namespace for all TwentyCRM resources" } + +variable "twentycrm_redis_replicas" { + type = number + default = 1 + description = "Number of replicas for the TwentyCRM Redis deployment. This defaults to 1." +} + +variable "twentycrm_redis_image" { + type = string + default = "redis/redis-stack-server:latest" + description = "TwentyCRM image for Redis deployment. This defaults to latest." +} + +variable "twentycrm_docker_data_mount_path" { + type = string + default = "/app/docker-data" + description = "TwentyCRM mount path for servers application data. Defaults to '/app/docker-data'." +} + +variable "twentycrm_docker_data_pv_path" { + type = string + default = "" + description = "Local path to use to store the physical volume if using local storage on nodes." +} + +variable "twentycrm_docker_data_pv_capacity" { + type = string + default = "100Mi" + description = "Storage capacity provisioned for server persistent volume." +} + +variable "twentycrm_docker_data_pvc_requests" { + type = string + default = "100Mi" + description = "Storage capacity reservation for server persistent volume claim." +} diff --git a/packages/twenty-docker/twenty-website/Dockerfile b/packages/twenty-docker/twenty-website/Dockerfile deleted file mode 100644 index e3b7420ff76a..000000000000 --- a/packages/twenty-docker/twenty-website/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM node:18.17.1-alpine as twenty-website-build - - -WORKDIR /app - -COPY ./package.json . -COPY ./yarn.lock . -COPY ./.yarnrc.yml . -COPY ./.yarn/releases /app/.yarn/releases -COPY ./tools/eslint-rules /app/tools/eslint-rules -COPY ./packages/twenty-website/package.json /app/packages/twenty-website/package.json - -RUN yarn - -COPY ./packages/twenty-website /app/packages/twenty-website -RUN npx nx build twenty-website - -FROM node:18.17.1-alpine as twenty-website - -WORKDIR /app/packages/twenty-website - -COPY --from=twenty-website-build /app /app - -WORKDIR /app/packages/twenty-website - -LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty -LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the website." - -CMD ["/bin/sh", "-c", "npx nx start"] \ No newline at end of file diff --git a/packages/twenty-emails/package.json b/packages/twenty-emails/package.json index 36af44f3da6e..408930ac02ec 100644 --- a/packages/twenty-emails/package.json +++ b/packages/twenty-emails/package.json @@ -1,6 +1,6 @@ { "name": "twenty-emails", - "version": "0.24.2", + "version": "0.32.0-canary", "description": "", "author": "", "private": true, diff --git a/packages/twenty-emails/src/emails/workflow-action.email.tsx b/packages/twenty-emails/src/emails/workflow-action.email.tsx deleted file mode 100644 index 2eaa3a451ebb..000000000000 --- a/packages/twenty-emails/src/emails/workflow-action.email.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { BaseEmail } from 'src/components/BaseEmail'; -import { Title } from 'src/components/Title'; -import { CallToAction } from 'src/components/CallToAction'; - -type WorkflowActionEmailProps = { - dangerousHTML?: string; - title?: string; - callToAction?: { - value: string; - href: string; - }; -}; -export const WorkflowActionEmail = ({ - dangerousHTML, - title, - callToAction, -}: WorkflowActionEmailProps) => { - return ( - - {title && } - {dangerousHTML && ( - <div dangerouslySetInnerHTML={{ __html: dangerousHTML }} /> - )} - {callToAction && ( - <CallToAction value={callToAction.value} href={callToAction.href} /> - )} - </BaseEmail> - ); -}; diff --git a/packages/twenty-emails/src/index.ts b/packages/twenty-emails/src/index.ts index 9fca13d73b55..ddecb05c8655 100644 --- a/packages/twenty-emails/src/index.ts +++ b/packages/twenty-emails/src/index.ts @@ -3,4 +3,3 @@ export * from './emails/delete-inactive-workspaces.email'; export * from './emails/password-reset-link.email'; export * from './emails/password-update-notify.email'; export * from './emails/send-invite-link.email'; -export * from './emails/workflow-action.email'; diff --git a/packages/twenty-front/.eslintrc.cjs b/packages/twenty-front/.eslintrc.cjs index bb57531265be..df4daf7633a4 100644 --- a/packages/twenty-front/.eslintrc.cjs +++ b/packages/twenty-front/.eslintrc.cjs @@ -6,7 +6,6 @@ module.exports = { 'mockServiceWorker.js', '**/generated*/*', '**/generated/standard-metadata-query-result.ts', - '**/getObjectMetadataItemsMock.ts', 'tsup.config.ts', 'build', 'coverage', 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 434934ed719d..80eb86a7cf2a 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -1,6 +1,6 @@ { "name": "twenty-front", - "version": "0.24.2", + "version": "0.32.0-canary", "private": true, "type": "module", "scripts": { @@ -30,6 +30,9 @@ "workerDirectory": "public" }, "dependencies": { + "@nivo/calendar": "^0.87.0", + "@nivo/core": "^0.87.0", + "@nivo/line": "^0.87.0", "@xyflow/react": "^12.0.4", "transliteration": "^2.3.5" } diff --git a/packages/twenty-front/project.json b/packages/twenty-front/project.json index 4dcb7f444a00..3ed94b22f256 100644 --- a/packages/twenty-front/project.json +++ b/packages/twenty-front/project.json @@ -67,7 +67,7 @@ "test": {}, "storybook:build": { "options": { - "env": { "NODE_OPTIONS": "--max_old_space_size=6000" } + "env": { "NODE_OPTIONS": "--max_old_space_size=6500" } } }, "storybook:serve:dev": { diff --git a/packages/twenty-front/public/mockServiceWorker.js b/packages/twenty-front/public/mockServiceWorker.js index e369128ec00c..15751fa1994f 100644 --- a/packages/twenty-front/public/mockServiceWorker.js +++ b/packages/twenty-front/public/mockServiceWorker.js @@ -2,13 +2,14 @@ /* tslint:disable */ /** - * Mock Service Worker (2.0.11). + * Mock Service Worker. * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = 'c5f7f8e188b673ea4e677df7ea3c5a39' +const PACKAGE_VERSION = '2.3.5' +const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() @@ -48,7 +49,10 @@ self.addEventListener('message', async function (event) { case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { type: 'INTEGRITY_CHECK_RESPONSE', - payload: INTEGRITY_CHECKSUM, + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, }) break } @@ -202,13 +206,6 @@ async function getResponse(event, client, requestId) { return passthrough() } - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - const mswIntention = request.headers.get('x-msw-intention') - if (['bypass', 'passthrough'].includes(mswIntention)) { - return passthrough() - } - // Notify the client that a request has been intercepted. const requestBuffer = await request.arrayBuffer() const clientMessage = await sendToClient( @@ -240,7 +237,7 @@ async function getResponse(event, client, requestId) { return respondWithMock(clientMessage.data) } - case 'MOCK_NOT_FOUND': { + case 'PASSTHROUGH': { return passthrough() } } diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx deleted file mode 100644 index 1c6adcfdf60e..000000000000 --- a/packages/twenty-front/src/App.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { StrictMode } from 'react'; -import { - createBrowserRouter, - createRoutesFromElements, - Outlet, - Route, - RouterProvider, - useLocation, -} from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; - -import { ApolloProvider } from '@/apollo/components/ApolloProvider'; -import { AuthProvider } from '@/auth/components/AuthProvider'; -import { VerifyEffect } from '@/auth/components/VerifyEffect'; -import { ChromeExtensionSidecarEffect } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect'; -import { ChromeExtensionSidecarProvider } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider'; -import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; -import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; -import { billingState } from '@/client-config/states/billingState'; -import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect'; -import indexAppPath from '@/navigation/utils/indexAppPath'; -import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider'; -import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; -import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; -import { AppPath } from '@/types/AppPath'; -import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager'; -import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope'; -import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider'; -import { BlankLayout } from '@/ui/layout/page/BlankLayout'; -import { DefaultLayout } from '@/ui/layout/page/DefaultLayout'; -import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider'; -import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; -import { UserProvider } from '@/users/components/UserProvider'; -import { UserProviderEffect } from '@/users/components/UserProviderEffect'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect'; -import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect'; -import { PageChangeEffect } from '~/effect-components/PageChangeEffect'; -import { Authorize } from '~/pages/auth/Authorize'; -import { Invite } from '~/pages/auth/Invite'; -import { PasswordReset } from '~/pages/auth/PasswordReset'; -import { SignInUp } from '~/pages/auth/SignInUp'; -import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect'; -import { NotFound } from '~/pages/not-found/NotFound'; -import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage'; -import { RecordShowPage } from '~/pages/object-record/RecordShowPage'; -import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan'; -import { CreateProfile } from '~/pages/onboarding/CreateProfile'; -import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace'; -import { InviteTeam } from '~/pages/onboarding/InviteTeam'; -import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess'; -import { SyncEmails } from '~/pages/onboarding/SyncEmails'; -import { SettingsRoutes } from '~/SettingsRoutes'; -import { getPageTitleFromPath } from '~/utils/title-utils'; - -const ProvidersThatNeedRouterContext = () => { - const { pathname } = useLocation(); - const pageTitle = getPageTitleFromPath(pathname); - - return ( - <> - <ApolloProvider> - <ClientConfigProviderEffect /> - <ClientConfigProvider> - <ChromeExtensionSidecarEffect /> - <ChromeExtensionSidecarProvider> - <UserProviderEffect /> - <UserProvider> - <AuthProvider> - <ApolloMetadataClientProvider> - <ObjectMetadataItemsProvider> - <PrefetchDataProvider> - <AppThemeProvider> - <SnackBarProvider> - <DialogManagerScope dialogManagerScopeId="dialog-manager"> - <DialogManager> - <StrictMode> - <PromiseRejectionEffect /> - <CommandMenuEffect /> - <GotoHotkeysEffect /> - <PageTitle title={pageTitle} /> - <Outlet /> - </StrictMode> - </DialogManager> - </DialogManagerScope> - </SnackBarProvider> - </AppThemeProvider> - </PrefetchDataProvider> - <PageChangeEffect /> - </ObjectMetadataItemsProvider> - </ApolloMetadataClientProvider> - </AuthProvider> - </UserProvider> - </ChromeExtensionSidecarProvider> - </ClientConfigProvider> - </ApolloProvider> - </> - ); -}; - -const createRouter = ( - isBillingEnabled?: boolean, - isCRMMigrationEnabled?: boolean, - isServerlessFunctionSettingsEnabled?: boolean, -) => - createBrowserRouter( - createRoutesFromElements( - <Route - element={<ProvidersThatNeedRouterContext />} - // To switch state to `loading` temporarily to enable us - // to set scroll position before the page is rendered - loader={async () => Promise.resolve(null)} - > - <Route element={<DefaultLayout />}> - <Route path={AppPath.Verify} element={<VerifyEffect />} /> - <Route path={AppPath.SignInUp} element={<SignInUp />} /> - <Route path={AppPath.Invite} element={<Invite />} /> - <Route path={AppPath.ResetPassword} element={<PasswordReset />} /> - <Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} /> - <Route path={AppPath.CreateProfile} element={<CreateProfile />} /> - <Route path={AppPath.SyncEmails} element={<SyncEmails />} /> - <Route path={AppPath.InviteTeam} element={<InviteTeam />} /> - <Route path={AppPath.PlanRequired} element={<ChooseYourPlan />} /> - <Route - path={AppPath.PlanRequiredSuccess} - element={<PaymentSuccess />} - /> - <Route path={indexAppPath.getIndexAppPath()} element={<></>} /> - <Route path={AppPath.Impersonate} element={<ImpersonateEffect />} /> - <Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} /> - <Route path={AppPath.RecordShowPage} element={<RecordShowPage />} /> - <Route - path={AppPath.SettingsCatchAll} - element={ - <SettingsRoutes - isBillingEnabled={isBillingEnabled} - isCRMMigrationEnabled={isCRMMigrationEnabled} - isServerlessFunctionSettingsEnabled={ - isServerlessFunctionSettingsEnabled - } - /> - } - /> - <Route path={AppPath.NotFoundWildcard} element={<NotFound />} /> - </Route> - <Route element={<BlankLayout />}> - <Route path={AppPath.Authorize} element={<Authorize />} /> - </Route> - </Route>, - ), - ); - -export const App = () => { - const billing = useRecoilValue(billingState); - const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED'); - const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED'); - const isServerlessFunctionSettingsEnabled = useIsFeatureEnabled( - 'IS_FUNCTION_SETTINGS_ENABLED', - ); - - const isBillingPageEnabled = - billing?.isBillingEnabled && !isFreeAccessEnabled; - - return ( - <RouterProvider - router={createRouter( - isBillingPageEnabled, - isCRMMigrationEnabled, - isServerlessFunctionSettingsEnabled, - )} - /> - ); -}; diff --git a/packages/twenty-front/src/__stories__/App.stories.tsx b/packages/twenty-front/src/__stories__/AppRouter.stories.tsx similarity index 92% rename from packages/twenty-front/src/__stories__/App.stories.tsx rename to packages/twenty-front/src/__stories__/AppRouter.stories.tsx index c5314b5652a1..9d2fe91a6523 100644 --- a/packages/twenty-front/src/__stories__/App.stories.tsx +++ b/packages/twenty-front/src/__stories__/AppRouter.stories.tsx @@ -1,8 +1,8 @@ -import { HelmetProvider } from 'react-helmet-async'; import { getOperationName } from '@apollo/client/utilities'; import { jest } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; import { graphql, HttpResponse } from 'msw'; +import { HelmetProvider } from 'react-helmet-async'; import { RecoilRoot } from 'recoil'; import { IconsProvider } from 'twenty-ui'; @@ -11,13 +11,14 @@ import indexAppPath from '@/navigation/utils/indexAppPath'; import { AppPath } from '@/types/AppPath'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { App } from '~/App'; + +import { AppRouter } from '@/app/components/AppRouter'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { mockedUserData } from '~/testing/mock-data/users'; -const meta: Meta<typeof App> = { - title: 'App/App', - component: App, +const meta: Meta<typeof AppRouter> = { + title: 'App/AppRouter', + component: AppRouter, decorators: [ (Story) => { return ( @@ -41,7 +42,7 @@ const meta: Meta<typeof App> = { }; export default meta; -export type Story = StoryObj<typeof App>; +export type Story = StoryObj<typeof AppRouter>; export const Default: Story = { play: async () => { diff --git a/packages/twenty-front/src/effect-components/GotoHotkeysEffect.tsx b/packages/twenty-front/src/effect-components/GotoHotkeysEffect.tsx deleted file mode 100644 index 1109066af34a..000000000000 --- a/packages/twenty-front/src/effect-components/GotoHotkeysEffect.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useGoToHotkeys } from '@/ui/utilities/hotkey/hooks/useGoToHotkeys'; - -export const GotoHotkeysEffect = () => { - useGoToHotkeys('p', '/objects/people'); - useGoToHotkeys('c', '/objects/companies'); - useGoToHotkeys('o', '/objects/opportunities'); - useGoToHotkeys('s', '/settings/profile'); - useGoToHotkeys('t', '/objects/tasks'); - - return <></>; -}; diff --git a/packages/twenty-front/src/generated-metadata/gql.ts b/packages/twenty-front/src/generated-metadata/gql.ts index a01821654262..415482505650 100644 --- a/packages/twenty-front/src/generated-metadata/gql.ts +++ b/packages/twenty-front/src/generated-metadata/gql.ts @@ -25,15 +25,15 @@ const documents = { "\n \n query GetManyRemoteTables($input: FindManyRemoteTablesInput!) {\n findDistantTablesWithStatus(input: $input) {\n ...RemoteTableFields\n }\n }\n": types.GetManyRemoteTablesDocument, "\n \n query GetOneDatabaseConnection($input: RemoteServerIdInput!) {\n findOneRemoteServerById(input: $input) {\n ...RemoteServerFields\n }\n }\n": types.GetOneDatabaseConnectionDocument, "\n mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) {\n createOneObject(input: $input) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.CreateOneObjectMetadataItemDocument, - "\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument, + "\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument, "\n mutation CreateOneRelationMetadata($input: CreateOneRelationInput!) {\n createOneRelation(input: $input) {\n id\n relationType\n fromObjectMetadataId\n toObjectMetadataId\n fromFieldMetadataId\n toFieldMetadataId\n createdAt\n updatedAt\n }\n }\n": types.CreateOneRelationMetadataDocument, - "\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.UpdateOneFieldMetadataItemDocument, + "\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.UpdateOneFieldMetadataItemDocument, "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument, "\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument, - "\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument, + "\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.DeleteOneFieldMetadataItemDocument, "\n mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {\n deleteOneRelation(input: { id: $idToDelete }) {\n id\n }\n }\n": types.DeleteOneRelationMetadataItemDocument, - "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument, - "\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n sourceCodeHash\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n": types.ServerlessFunctionFieldsFragmentDoc, + "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument, + "\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n": types.ServerlessFunctionFieldsFragmentDoc, "\n \n mutation CreateOneServerlessFunctionItem(\n $input: CreateServerlessFunctionInput!\n ) {\n createOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.CreateOneServerlessFunctionItemDocument, "\n \n mutation DeleteOneServerlessFunction($input: DeleteServerlessFunctionInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.DeleteOneServerlessFunctionDocument, "\n mutation ExecuteOneServerlessFunction(\n $input: ExecuteServerlessFunctionInput!\n ) {\n executeOneServerlessFunction(input: $input) {\n data\n duration\n status\n error\n }\n }\n": types.ExecuteOneServerlessFunctionDocument, @@ -110,7 +110,7 @@ export function graphql(source: "\n mutation CreateOneObjectMetadataItem($input /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n }\n }\n"): (typeof documents)["\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n }\n }\n"]; +export function graphql(source: "\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n defaultValue\n options\n }\n }\n"): (typeof documents)["\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n defaultValue\n options\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -118,7 +118,7 @@ export function graphql(source: "\n mutation CreateOneRelationMetadata($input: /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n"): (typeof documents)["\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n"]; +export function graphql(source: "\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n"): (typeof documents)["\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -130,7 +130,7 @@ export function graphql(source: "\n mutation DeleteOneObjectMetadataItem($idToD /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n"): (typeof documents)["\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n"]; +export function graphql(source: "\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n"): (typeof documents)["\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -138,11 +138,11 @@ export function graphql(source: "\n mutation DeleteOneRelationMetadataItem($idT /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"]; +export function graphql(source: "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n sourceCodeHash\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n"): (typeof documents)["\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n sourceCodeHash\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n"]; +export function graphql(source: "\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n"): (typeof documents)["\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 1a20ddc54315..97705b937cba 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -165,7 +165,6 @@ export type ClientConfig = { signInPrefilled: Scalars['Boolean']['output']; signUpDisabled: Scalars['Boolean']['output']; support: Support; - telemetry: Telemetry; }; export type CreateAppTokenInput = { @@ -181,6 +180,7 @@ export type CreateFieldInput = { isNullable?: InputMaybe<Scalars['Boolean']['input']>; isRemoteCreation?: InputMaybe<Scalars['Boolean']['input']>; isSystem?: InputMaybe<Scalars['Boolean']['input']>; + isUnique?: InputMaybe<Scalars['Boolean']['input']>; label: Scalars['String']['input']; name: Scalars['String']['input']; objectMetadataId: Scalars['String']['input']; @@ -246,13 +246,7 @@ export type CreateRemoteServerInput = { userMappingOptions?: InputMaybe<UserMappingOptions>; }; -export type CreateServerlessFunctionFromFileInput = { - description?: InputMaybe<Scalars['String']['input']>; - name: Scalars['String']['input']; -}; - export type CreateServerlessFunctionInput = { - code: Scalars['String']['input']; description?: InputMaybe<Scalars['String']['input']>; name: Scalars['String']['input']; }; @@ -359,15 +353,12 @@ export enum FieldMetadataType { Currency = 'CURRENCY', Date = 'DATE', DateTime = 'DATE_TIME', - Email = 'EMAIL', Emails = 'EMAILS', FullName = 'FULL_NAME', - Link = 'LINK', Links = 'LINKS', MultiSelect = 'MULTI_SELECT', Number = 'NUMBER', Numeric = 'NUMERIC', - Phone = 'PHONE', Phones = 'PHONES', Position = 'POSITION', Rating = 'RATING', @@ -376,6 +367,7 @@ export enum FieldMetadataType { RichText = 'RICH_TEXT', Select = 'SELECT', Text = 'TEXT', + TsVector = 'TS_VECTOR', Uuid = 'UUID' } @@ -407,6 +399,36 @@ export type GetServerlessFunctionSourceCodeInput = { version?: Scalars['String']['input']; }; +export type IndexConnection = { + __typename?: 'IndexConnection'; + /** Array of edges. */ + edges: Array<IndexEdge>; + /** Paging information */ + pageInfo: PageInfo; +}; + +export type IndexIndexFieldMetadatasConnection = { + __typename?: 'IndexIndexFieldMetadatasConnection'; + /** Array of edges. */ + edges: Array<IndexFieldEdge>; + /** Paging information */ + pageInfo: PageInfo; +}; + +export type IndexObjectMetadataConnection = { + __typename?: 'IndexObjectMetadataConnection'; + /** Array of edges. */ + edges: Array<ObjectEdge>; + /** Paging information */ + pageInfo: PageInfo; +}; + +/** Type of the index */ +export enum IndexType { + Btree = 'BTREE', + Gin = 'GIN' +} + export type InvalidatePassword = { __typename?: 'InvalidatePassword'; /** Boolean that confirms query was dispatched */ @@ -452,7 +474,6 @@ export type Mutation = { createOneRelation: Relation; createOneRemoteServer: RemoteServer; createOneServerlessFunction: ServerlessFunction; - createOneServerlessFunctionFromFile: ServerlessFunction; deactivateWorkflowVersion: Scalars['Boolean']['output']; deleteCurrentWorkspace: Workspace; deleteOneField: Field; @@ -567,12 +588,6 @@ export type MutationCreateOneServerlessFunctionArgs = { }; -export type MutationCreateOneServerlessFunctionFromFileArgs = { - file: Scalars['Upload']['input']; - input: CreateServerlessFunctionFromFileInput; -}; - - export type MutationDeactivateWorkflowVersionArgs = { workflowVersionId: Scalars['String']['input']; }; @@ -686,8 +701,8 @@ export type MutationSyncRemoteTableSchemaChangesArgs = { export type MutationTrackArgs = { - data: Scalars['JSON']['input']; - type: Scalars['String']['input']; + action: Scalars['String']['input']; + payload: Scalars['JSON']['input']; }; @@ -769,6 +784,14 @@ export type ObjectFieldsConnection = { pageInfo: PageInfo; }; +export type ObjectIndexMetadatasConnection = { + __typename?: 'ObjectIndexMetadatasConnection'; + /** Array of edges. */ + edges: Array<IndexEdge>; + /** Paging information */ + pageInfo: PageInfo; +}; + /** Onboarding status */ export enum OnboardingStatus { Completed = 'COMPLETED', @@ -843,11 +866,13 @@ export type Query = { getAvailablePackages: Scalars['JSON']['output']; getPostgresCredentials?: Maybe<PostgresCredentials>; getProductPrices: ProductPricesEntity; - getServerlessFunctionSourceCode?: Maybe<Scalars['String']['output']>; + getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']['output']>; getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal; getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal; getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal; getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; + index: Index; + indexMetadatas: IndexConnection; object: Object; objects: ObjectConnection; relation: Relation; @@ -948,6 +973,17 @@ export type QueryGetTimelineThreadsFromPersonIdArgs = { }; +export type QueryIndexArgs = { + id: Scalars['UUID']['input']; +}; + + +export type QueryIndexMetadatasArgs = { + filter?: IndexFilter; + paging?: CursorPaging; +}; + + export type QueryObjectArgs = { id: Scalars['UUID']['input']; }; @@ -1091,7 +1127,6 @@ export type ServerlessFunction = { latestVersion?: Maybe<Scalars['String']['output']>; name: Scalars['String']['output']; runtime: Scalars['String']['output']; - sourceCodeHash: Scalars['String']['output']; syncStatus: ServerlessFunctionSyncStatus; updatedAt: Scalars['DateTime']['output']; }; @@ -1193,11 +1228,6 @@ export type Support = { supportFrontChatId?: Maybe<Scalars['String']['output']>; }; -export type Telemetry = { - __typename?: 'Telemetry'; - enabled: Scalars['Boolean']['output']; -}; - export type TimelineCalendarEvent = { __typename?: 'TimelineCalendarEvent'; conferenceLink: LinksMetadata; @@ -1298,6 +1328,7 @@ export type UpdateFieldInput = { isCustom?: InputMaybe<Scalars['Boolean']['input']>; isNullable?: InputMaybe<Scalars['Boolean']['input']>; isSystem?: InputMaybe<Scalars['Boolean']['input']>; + isUnique?: InputMaybe<Scalars['Boolean']['input']>; label?: InputMaybe<Scalars['String']['input']>; name?: InputMaybe<Scalars['String']['input']>; options?: InputMaybe<Scalars['JSON']['input']>; @@ -1338,7 +1369,7 @@ export type UpdateRemoteServerInput = { }; export type UpdateServerlessFunctionInput = { - code: Scalars['String']['input']; + code: Scalars['JSON']['input']; description?: InputMaybe<Scalars['String']['input']>; /** Id of the serverless function to execute */ id: Scalars['UUID']['input']; @@ -1533,6 +1564,7 @@ export type Field = { isCustom?: Maybe<Scalars['Boolean']['output']>; isNullable?: Maybe<Scalars['Boolean']['output']>; isSystem?: Maybe<Scalars['Boolean']['output']>; + isUnique?: Maybe<Scalars['Boolean']['output']>; label: Scalars['String']['output']; name: Scalars['String']['output']; object?: Maybe<Object>; @@ -1561,6 +1593,71 @@ export type FieldFilter = { or?: InputMaybe<Array<FieldFilter>>; }; +export type Index = { + __typename?: 'index'; + createdAt: Scalars['DateTime']['output']; + id: Scalars['UUID']['output']; + indexFieldMetadatas: IndexIndexFieldMetadatasConnection; + indexType: IndexType; + indexWhereClause?: Maybe<Scalars['String']['output']>; + isCustom?: Maybe<Scalars['Boolean']['output']>; + isUnique: Scalars['Boolean']['output']; + name: Scalars['String']['output']; + objectMetadata: IndexObjectMetadataConnection; + updatedAt: Scalars['DateTime']['output']; +}; + + +export type IndexIndexFieldMetadatasArgs = { + filter?: IndexFieldFilter; + paging?: CursorPaging; +}; + + +export type IndexObjectMetadataArgs = { + filter?: ObjectFilter; + paging?: CursorPaging; +}; + +export type IndexEdge = { + __typename?: 'indexEdge'; + /** Cursor for this node. */ + cursor: Scalars['ConnectionCursor']['output']; + /** The node containing the index */ + node: Index; +}; + +export type IndexField = { + __typename?: 'indexField'; + createdAt: Scalars['DateTime']['output']; + fieldMetadataId: Scalars['UUID']['output']; + id: Scalars['UUID']['output']; + order: Scalars['Float']['output']; + updatedAt: Scalars['DateTime']['output']; +}; + +export type IndexFieldEdge = { + __typename?: 'indexFieldEdge'; + /** Cursor for this node. */ + cursor: Scalars['ConnectionCursor']['output']; + /** The node containing the indexField */ + node: IndexField; +}; + +export type IndexFieldFilter = { + and?: InputMaybe<Array<IndexFieldFilter>>; + fieldMetadataId?: InputMaybe<UuidFilterComparison>; + id?: InputMaybe<UuidFilterComparison>; + or?: InputMaybe<Array<IndexFieldFilter>>; +}; + +export type IndexFilter = { + and?: InputMaybe<Array<IndexFilter>>; + id?: InputMaybe<UuidFilterComparison>; + isCustom?: InputMaybe<BooleanFieldComparison>; + or?: InputMaybe<Array<IndexFilter>>; +}; + export type Object = { __typename?: 'object'; createdAt: Scalars['DateTime']['output']; @@ -1570,6 +1667,7 @@ export type Object = { icon?: Maybe<Scalars['String']['output']>; id: Scalars['UUID']['output']; imageIdentifierFieldMetadataId?: Maybe<Scalars['String']['output']>; + indexMetadatas: ObjectIndexMetadatasConnection; isActive: Scalars['Boolean']['output']; isCustom: Scalars['Boolean']['output']; isRemote: Scalars['Boolean']['output']; @@ -1588,6 +1686,12 @@ export type ObjectFieldsArgs = { paging?: CursorPaging; }; + +export type ObjectIndexMetadatasArgs = { + filter?: IndexFilter; + paging?: CursorPaging; +}; + export type ObjectEdge = { __typename?: 'objectEdge'; /** Cursor for this node. */ @@ -1707,7 +1811,7 @@ export type CreateOneFieldMetadataItemMutationVariables = Exact<{ }>; -export type CreateOneFieldMetadataItemMutation = { __typename?: 'Mutation', createOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, defaultValue?: any | null, options?: any | null } }; +export type CreateOneFieldMetadataItemMutation = { __typename?: 'Mutation', createOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, settings?: any | null, defaultValue?: any | null, options?: any | null } }; export type CreateOneRelationMetadataMutationVariables = Exact<{ input: CreateOneRelationInput; @@ -1722,7 +1826,7 @@ export type UpdateOneFieldMetadataItemMutationVariables = Exact<{ }>; -export type UpdateOneFieldMetadataItemMutation = { __typename?: 'Mutation', updateOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any } }; +export type UpdateOneFieldMetadataItemMutation = { __typename?: 'Mutation', updateOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, settings?: any | null } }; export type UpdateOneObjectMetadataItemMutationVariables = Exact<{ idToUpdate: Scalars['UUID']['input']; @@ -1744,7 +1848,7 @@ export type DeleteOneFieldMetadataItemMutationVariables = Exact<{ }>; -export type DeleteOneFieldMetadataItemMutation = { __typename?: 'Mutation', deleteOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any } }; +export type DeleteOneFieldMetadataItemMutation = { __typename?: 'Mutation', deleteOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, settings?: any | null } }; export type DeleteOneRelationMetadataItemMutationVariables = Exact<{ idToDelete: Scalars['UUID']['input']; @@ -1759,23 +1863,23 @@ export type ObjectMetadataItemsQueryVariables = Exact<{ }>; -export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, fields: { __typename?: 'ObjectFieldsConnection', edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, defaultValue?: any | null, options?: any | null, relationDefinition?: { __typename?: 'RelationDefinition', relationId: any, direction: RelationDefinitionType, sourceObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'field', id: any, name: string }, targetObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, targetFieldMetadata: { __typename?: 'field', id: any, name: string } } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; +export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, indexMetadatas: { __typename?: 'ObjectIndexMetadatasConnection', edges: Array<{ __typename?: 'indexEdge', node: { __typename?: 'index', id: any, createdAt: any, updatedAt: any, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, indexFieldMetadatas: { __typename?: 'IndexIndexFieldMetadatasConnection', edges: Array<{ __typename?: 'indexFieldEdge', node: { __typename?: 'indexField', id: any, createdAt: any, updatedAt: any, order: number, fieldMetadataId: any } }> } } }> }, fields: { __typename?: 'ObjectFieldsConnection', edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: any, updatedAt: any, defaultValue?: any | null, options?: any | null, settings?: any | null, relationDefinition?: { __typename?: 'RelationDefinition', relationId: any, direction: RelationDefinitionType, sourceObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'field', id: any, name: string }, targetObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, targetFieldMetadata: { __typename?: 'field', id: any, name: string } } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; -export type ServerlessFunctionFieldsFragment = { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any }; +export type ServerlessFunctionFieldsFragment = { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any }; export type CreateOneServerlessFunctionItemMutationVariables = Exact<{ input: CreateServerlessFunctionInput; }>; -export type CreateOneServerlessFunctionItemMutation = { __typename?: 'Mutation', createOneServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; +export type CreateOneServerlessFunctionItemMutation = { __typename?: 'Mutation', createOneServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; export type DeleteOneServerlessFunctionMutationVariables = Exact<{ input: DeleteServerlessFunctionInput; }>; -export type DeleteOneServerlessFunctionMutation = { __typename?: 'Mutation', deleteOneServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; +export type DeleteOneServerlessFunctionMutation = { __typename?: 'Mutation', deleteOneServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; export type ExecuteOneServerlessFunctionMutationVariables = Exact<{ input: ExecuteServerlessFunctionInput; @@ -1789,14 +1893,14 @@ export type PublishOneServerlessFunctionMutationVariables = Exact<{ }>; -export type PublishOneServerlessFunctionMutation = { __typename?: 'Mutation', publishServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; +export type PublishOneServerlessFunctionMutation = { __typename?: 'Mutation', publishServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; export type UpdateOneServerlessFunctionMutationVariables = Exact<{ input: UpdateServerlessFunctionInput; }>; -export type UpdateOneServerlessFunctionMutation = { __typename?: 'Mutation', updateOneServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; +export type UpdateOneServerlessFunctionMutation = { __typename?: 'Mutation', updateOneServerlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; export type FindManyAvailablePackagesQueryVariables = Exact<{ [key: string]: never; }>; @@ -1806,25 +1910,25 @@ export type FindManyAvailablePackagesQuery = { __typename?: 'Query', getAvailabl export type GetManyServerlessFunctionsQueryVariables = Exact<{ [key: string]: never; }>; -export type GetManyServerlessFunctionsQuery = { __typename?: 'Query', serverlessFunctions: { __typename?: 'ServerlessFunctionConnection', edges: Array<{ __typename?: 'ServerlessFunctionEdge', node: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }> } }; +export type GetManyServerlessFunctionsQuery = { __typename?: 'Query', serverlessFunctions: { __typename?: 'ServerlessFunctionConnection', edges: Array<{ __typename?: 'ServerlessFunctionEdge', node: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }> } }; export type GetOneServerlessFunctionQueryVariables = Exact<{ id: Scalars['UUID']['input']; }>; -export type GetOneServerlessFunctionQuery = { __typename?: 'Query', serverlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, sourceCodeHash: string, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; +export type GetOneServerlessFunctionQuery = { __typename?: 'Query', serverlessFunction: { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, createdAt: any, updatedAt: any } }; export type FindOneServerlessFunctionSourceCodeQueryVariables = Exact<{ input: GetServerlessFunctionSourceCodeInput; }>; -export type FindOneServerlessFunctionSourceCodeQuery = { __typename?: 'Query', getServerlessFunctionSourceCode?: string | null }; +export type FindOneServerlessFunctionSourceCodeQuery = { __typename?: 'Query', getServerlessFunctionSourceCode?: any | null }; export const RemoteServerFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}}]} as unknown as DocumentNode<RemoteServerFieldsFragment, unknown>; export const RemoteTableFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"schemaPendingUpdates"}}]}}]} as unknown as DocumentNode<RemoteTableFieldsFragment, unknown>; -export const ServerlessFunctionFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ServerlessFunctionFieldsFragment, unknown>; +export const ServerlessFunctionFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ServerlessFunctionFieldsFragment, unknown>; export const CreateServerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createServer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateRemoteServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneRemoteServer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}}]} as unknown as DocumentNode<CreateServerMutation, CreateServerMutationVariables>; export const DeleteServerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"deleteServer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneRemoteServer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeleteServerMutation, DeleteServerMutationVariables>; export const SyncRemoteTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"syncRemoteTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"syncRemoteTable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteTableFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"schemaPendingUpdates"}}]}}]} as unknown as DocumentNode<SyncRemoteTableMutation, SyncRemoteTableMutationVariables>; @@ -1835,20 +1939,20 @@ export const GetManyDatabaseConnectionsDocument = {"kind":"Document","definition export const GetManyRemoteTablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyRemoteTables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"FindManyRemoteTablesInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findDistantTablesWithStatus"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteTableFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"schemaPendingUpdates"}}]}}]} as unknown as DocumentNode<GetManyRemoteTablesQuery, GetManyRemoteTablesQueryVariables>; export const GetOneDatabaseConnectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOneDatabaseConnection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneRemoteServerById"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}}]} as unknown as DocumentNode<GetOneDatabaseConnectionQuery, GetOneDatabaseConnectionQueryVariables>; export const CreateOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneObjectInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}}]}}]}}]} as unknown as DocumentNode<CreateOneObjectMetadataItemMutation, CreateOneObjectMetadataItemMutationVariables>; -export const CreateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneFieldMetadataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}}]}}]}}]} as unknown as DocumentNode<CreateOneFieldMetadataItemMutation, CreateOneFieldMetadataItemMutationVariables>; +export const CreateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneFieldMetadataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}}]}}]}}]} as unknown as DocumentNode<CreateOneFieldMetadataItemMutation, CreateOneFieldMetadataItemMutationVariables>; export const CreateOneRelationMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneRelationMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneRelationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneRelation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"relationType"}},{"kind":"Field","name":{"kind":"Name","value":"fromObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"toObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fromFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"toFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<CreateOneRelationMetadataMutation, CreateOneRelationMetadataMutationVariables>; -export const UpdateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateFieldInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<UpdateOneFieldMetadataItemMutation, UpdateOneFieldMetadataItemMutationVariables>; +export const UpdateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateFieldInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}}]}}]}}]} as unknown as DocumentNode<UpdateOneFieldMetadataItemMutation, UpdateOneFieldMetadataItemMutationVariables>; export const UpdateOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateObjectPayload"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}}]}}]}}]} as unknown as DocumentNode<UpdateOneObjectMetadataItemMutation, UpdateOneObjectMetadataItemMutationVariables>; export const DeleteOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}}]}}]}}]} as unknown as DocumentNode<DeleteOneObjectMetadataItemMutation, DeleteOneObjectMetadataItemMutationVariables>; -export const DeleteOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<DeleteOneFieldMetadataItemMutation, DeleteOneFieldMetadataItemMutationVariables>; +export const DeleteOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}}]}}]}}]} as unknown as DocumentNode<DeleteOneFieldMetadataItemMutation, DeleteOneFieldMetadataItemMutationVariables>; export const DeleteOneRelationMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneRelationMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneRelation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<DeleteOneRelationMetadataItemMutation, DeleteOneRelationMetadataItemMutationVariables>; -export const ObjectMetadataItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ObjectMetadataItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"objectFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fieldFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"fieldFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isRemote"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fieldFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"relationDefinition"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"relationId"}},{"kind":"Field","name":{"kind":"Name","value":"direction"}},{"kind":"Field","name":{"kind":"Name","value":"sourceObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode<ObjectMetadataItemsQuery, ObjectMetadataItemsQueryVariables>; -export const CreateOneServerlessFunctionItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneServerlessFunctionItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<CreateOneServerlessFunctionItemMutation, CreateOneServerlessFunctionItemMutationVariables>; -export const DeleteOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DeleteServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<DeleteOneServerlessFunctionMutation, DeleteOneServerlessFunctionMutationVariables>; +export const ObjectMetadataItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ObjectMetadataItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"objectFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fieldFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"fieldFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isRemote"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"indexMetadatas"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"indexWhereClause"}},{"kind":"Field","name":{"kind":"Name","value":"indexType"}},{"kind":"Field","name":{"kind":"Name","value":"isUnique"}},{"kind":"Field","name":{"kind":"Name","value":"indexFieldMetadatas"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}}]}}]}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fieldFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"isUnique"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"relationDefinition"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"relationId"}},{"kind":"Field","name":{"kind":"Name","value":"direction"}},{"kind":"Field","name":{"kind":"Name","value":"sourceObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sourceFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"targetFieldMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode<ObjectMetadataItemsQuery, ObjectMetadataItemsQueryVariables>; +export const CreateOneServerlessFunctionItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneServerlessFunctionItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<CreateOneServerlessFunctionItemMutation, CreateOneServerlessFunctionItemMutationVariables>; +export const DeleteOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DeleteServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<DeleteOneServerlessFunctionMutation, DeleteOneServerlessFunctionMutationVariables>; export const ExecuteOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ExecuteOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ExecuteServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"executeOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"data"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<ExecuteOneServerlessFunctionMutation, ExecuteOneServerlessFunctionMutationVariables>; -export const PublishOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PublishServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<PublishOneServerlessFunctionMutation, PublishOneServerlessFunctionMutationVariables>; -export const UpdateOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateOneServerlessFunctionMutation, UpdateOneServerlessFunctionMutationVariables>; +export const PublishOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"PublishOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PublishServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publishServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<PublishOneServerlessFunctionMutation, PublishOneServerlessFunctionMutationVariables>; +export const UpdateOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateServerlessFunctionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateOneServerlessFunctionMutation, UpdateOneServerlessFunctionMutationVariables>; export const FindManyAvailablePackagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyAvailablePackages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAvailablePackages"}}]}}]} as unknown as DocumentNode<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>; -export const GetManyServerlessFunctionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyServerlessFunctions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverlessFunctions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<GetManyServerlessFunctionsQuery, GetManyServerlessFunctionsQueryVariables>; -export const GetOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"sourceCodeHash"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<GetOneServerlessFunctionQuery, GetOneServerlessFunctionQueryVariables>; +export const GetManyServerlessFunctionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyServerlessFunctions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverlessFunctions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"100"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<GetManyServerlessFunctionsQuery, GetManyServerlessFunctionsQueryVariables>; +export const GetOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<GetOneServerlessFunctionQuery, GetOneServerlessFunctionQueryVariables>; export const FindOneServerlessFunctionSourceCodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneServerlessFunctionSourceCode"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GetServerlessFunctionSourceCodeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getServerlessFunctionSourceCode"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<FindOneServerlessFunctionSourceCodeQuery, FindOneServerlessFunctionSourceCodeQueryVariables>; \ No newline at end of file diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 0bd71dcaed1b..9e930133b3fb 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1,5 +1,5 @@ -import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; +import { gql } from '@apollo/client'; export type Maybe<T> = T | null; export type InputMaybe<T> = Maybe<T>; export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }; @@ -158,7 +158,6 @@ export type ClientConfig = { signInPrefilled: Scalars['Boolean']; signUpDisabled: Scalars['Boolean']; support: Support; - telemetry: Telemetry; }; export type CreateServerlessFunctionFromFileInput = { @@ -264,15 +263,12 @@ export enum FieldMetadataType { Currency = 'CURRENCY', Date = 'DATE', DateTime = 'DATE_TIME', - Email = 'EMAIL', Emails = 'EMAILS', FullName = 'FULL_NAME', - Link = 'LINK', Links = 'LINKS', MultiSelect = 'MULTI_SELECT', Number = 'NUMBER', Numeric = 'NUMERIC', - Phone = 'PHONE', Phones = 'PHONES', Position = 'POSITION', Rating = 'RATING', @@ -281,6 +277,7 @@ export enum FieldMetadataType { RichText = 'RICH_TEXT', Select = 'SELECT', Text = 'TEXT', + TsVector = 'TS_VECTOR', Uuid = 'UUID' } @@ -524,6 +521,7 @@ export type MutationSignUpArgs = { export type MutationTrackArgs = { data: Scalars['JSON']; + sessionId: Scalars['String']; type: Scalars['String']; }; @@ -919,11 +917,6 @@ export type Support = { supportFrontChatId?: Maybe<Scalars['String']>; }; -export type Telemetry = { - __typename?: 'Telemetry'; - enabled: Scalars['Boolean']; -}; - export type TimelineCalendarEvent = { __typename?: 'TimelineCalendarEvent'; conferenceLink: LinksMetadata; @@ -1354,8 +1347,8 @@ export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{ export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } }; export type TrackMutationVariables = Exact<{ - type: Scalars['String']; - data: Scalars['JSON']; + action: Scalars['String']; + payload: Scalars['JSON']; }>; @@ -1511,7 +1504,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; -export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } }; +export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } }; export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>; @@ -1544,6 +1537,20 @@ export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } }; +export type ActivateWorkflowVersionMutationVariables = Exact<{ + workflowVersionId: Scalars['String']; +}>; + + +export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean }; + +export type DeactivateWorkflowVersionMutationVariables = Exact<{ + workflowVersionId: Scalars['String']; +}>; + + +export type DeactivateWorkflowVersionMutation = { __typename?: 'Mutation', deactivateWorkflowVersion: boolean }; + export type DeleteWorkspaceInvitationMutationVariables = Exact<{ appTokenId: Scalars['String']; }>; @@ -1934,8 +1941,8 @@ export type GetTimelineThreadsFromPersonIdQueryHookResult = ReturnType<typeof us export type GetTimelineThreadsFromPersonIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdLazyQuery>; export type GetTimelineThreadsFromPersonIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromPersonIdQuery, GetTimelineThreadsFromPersonIdQueryVariables>; export const TrackDocument = gql` - mutation Track($type: String!, $data: JSON!) { - track(type: $type, data: $data) { + mutation Track($action: String!, $payload: JSON!) { + track(action: $action, payload: $payload) { success } } @@ -1955,8 +1962,8 @@ export type TrackMutationFn = Apollo.MutationFunction<TrackMutation, TrackMutati * @example * const [trackMutation, { data, loading, error }] = useTrackMutation({ * variables: { - * type: // value for 'type' - * data: // value for 'data' + * action: // value for 'type' + * payload: // value for 'payload' * }, * }); */ @@ -2670,9 +2677,6 @@ export const GetClientConfigDocument = gql` signInPrefilled signUpDisabled debugMode - telemetry { - enabled - } support { supportDriver supportFrontChatId @@ -2886,6 +2890,68 @@ export function useGetCurrentUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt export type GetCurrentUserQueryHookResult = ReturnType<typeof useGetCurrentUserQuery>; export type GetCurrentUserLazyQueryHookResult = ReturnType<typeof useGetCurrentUserLazyQuery>; export type GetCurrentUserQueryResult = Apollo.QueryResult<GetCurrentUserQuery, GetCurrentUserQueryVariables>; +export const ActivateWorkflowVersionDocument = gql` + mutation ActivateWorkflowVersion($workflowVersionId: String!) { + activateWorkflowVersion(workflowVersionId: $workflowVersionId) +} + `; +export type ActivateWorkflowVersionMutationFn = Apollo.MutationFunction<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>; + +/** + * __useActivateWorkflowVersionMutation__ + * + * To run a mutation, you first call `useActivateWorkflowVersionMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useActivateWorkflowVersionMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [activateWorkflowVersionMutation, { data, loading, error }] = useActivateWorkflowVersionMutation({ + * variables: { + * workflowVersionId: // value for 'workflowVersionId' + * }, + * }); + */ +export function useActivateWorkflowVersionMutation(baseOptions?: Apollo.MutationHookOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>(ActivateWorkflowVersionDocument, options); + } +export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>; +export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>; +export type ActivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>; +export const DeactivateWorkflowVersionDocument = gql` + mutation DeactivateWorkflowVersion($workflowVersionId: String!) { + deactivateWorkflowVersion(workflowVersionId: $workflowVersionId) +} + `; +export type DeactivateWorkflowVersionMutationFn = Apollo.MutationFunction<DeactivateWorkflowVersionMutation, DeactivateWorkflowVersionMutationVariables>; + +/** + * __useDeactivateWorkflowVersionMutation__ + * + * To run a mutation, you first call `useDeactivateWorkflowVersionMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeactivateWorkflowVersionMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deactivateWorkflowVersionMutation, { data, loading, error }] = useDeactivateWorkflowVersionMutation({ + * variables: { + * workflowVersionId: // value for 'workflowVersionId' + * }, + * }); + */ +export function useDeactivateWorkflowVersionMutation(baseOptions?: Apollo.MutationHookOptions<DeactivateWorkflowVersionMutation, DeactivateWorkflowVersionMutationVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation<DeactivateWorkflowVersionMutation, DeactivateWorkflowVersionMutationVariables>(DeactivateWorkflowVersionDocument, options); + } +export type DeactivateWorkflowVersionMutationHookResult = ReturnType<typeof useDeactivateWorkflowVersionMutation>; +export type DeactivateWorkflowVersionMutationResult = Apollo.MutationResult<DeactivateWorkflowVersionMutation>; +export type DeactivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<DeactivateWorkflowVersionMutation, DeactivateWorkflowVersionMutationVariables>; export const DeleteWorkspaceInvitationDocument = gql` mutation DeleteWorkspaceInvitation($appTokenId: String!) { deleteWorkspaceInvitation(appTokenId: $appTokenId) diff --git a/packages/twenty-front/src/index.tsx b/packages/twenty-front/src/index.tsx index 06527d80050b..2a9ce791fc41 100644 --- a/packages/twenty-front/src/index.tsx +++ b/packages/twenty-front/src/index.tsx @@ -1,42 +1,13 @@ import ReactDOM from 'react-dom/client'; -import { HelmetProvider } from 'react-helmet-async'; -import { RecoilRoot } from 'recoil'; -import { IconsProvider } from 'twenty-ui'; - -import { CaptchaProvider } from '@/captcha/components/CaptchaProvider'; -import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect'; -import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; -import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; -import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import '@emotion/react'; -import { App } from './App'; - -import './index.css'; +import { App } from '@/app/components/App'; import 'react-loading-skeleton/dist/skeleton.css'; +import './index.css'; const root = ReactDOM.createRoot( document.getElementById('root') ?? document.body, ); -root.render( - <RecoilRoot> - <AppErrorBoundary> - <CaptchaProvider> - <RecoilDebugObserverEffect /> - <ApolloDevLogEffect /> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <IconsProvider> - <ExceptionHandlerProvider> - <HelmetProvider> - <App /> - </HelmetProvider> - </ExceptionHandlerProvider> - </IconsProvider> - </SnackBarProviderScope> - </CaptchaProvider> - </AppErrorBoundary> - </RecoilRoot>, -); +root.render(<App />); 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} > - <Skeleton width={96} height={16} /> + <Skeleton + width={96} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </SkeletonTheme> </StyledSkeletonTitleContainer> <StyledSkeletonContainer> 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 && <Skeleton width={48} height={13} />} + {title && ( + <Skeleton + width={48} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.xs} + /> + )} {Array.from({ length }).map((_, index) => ( - <Skeleton key={index} width={196} height={16} /> + <Skeleton + key={index} + width={196} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> ))} </SkeletonTheme> </StyledSkeletonContainer> 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} > - <Skeleton height={16} width={104} /> + <Skeleton + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + width={104} + /> </SkeletonTheme> </StyledHeaderContainer> ); @@ -73,7 +77,7 @@ const StyledSkeletonAddLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - <Skeleton width={132} height={16} /> + <Skeleton width={132} height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} /> </SkeletonTheme> ); }; diff --git a/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx b/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx index 847680fbae61..16c87e7c1732 100644 --- a/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx +++ b/packages/twenty-front/src/loading/components/__stories__/PrefetchLoading.stories.tsx @@ -38,6 +38,6 @@ export const Default: Story = { await canvas.findByText('Tasks'); await canvas.findByText('People'); await canvas.findByText('Opportunities'); - await canvas.findByText('My Customs'); + await canvas.findByText('Rockets'); }, }; diff --git a/packages/twenty-front/src/modules/accounts/constants/GmailSendScope.ts b/packages/twenty-front/src/modules/accounts/constants/GmailSendScope.ts new file mode 100644 index 000000000000..6918d126f2b3 --- /dev/null +++ b/packages/twenty-front/src/modules/accounts/constants/GmailSendScope.ts @@ -0,0 +1 @@ +export const GMAIL_SEND_SCOPE = 'https://www.googleapis.com/auth/gmail.send'; diff --git a/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts b/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts index d8f42da6d53c..f0ba4f489296 100644 --- a/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts +++ b/packages/twenty-front/src/modules/accounts/types/ConnectedAccount.ts @@ -13,5 +13,6 @@ export type ConnectedAccount = { authFailedAt: Date | null; messageChannels: MessageChannel[]; calendarChannels: CalendarChannel[]; + scopes: string[] | null; __typename: 'ConnectedAccount'; }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/DeleteRecordsActionEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/DeleteRecordsActionEffect.tsx new file mode 100644 index 000000000000..89243ead97c5 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/DeleteRecordsActionEffect.tsx @@ -0,0 +1,94 @@ +import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; +import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; +import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData'; +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import { useCallback, useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { IconTrash } from 'twenty-ui'; + +export const DeleteRecordsActionEffect = ({ + position, +}: { + position: number; +}) => { + const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); + + const contextStoreTargetedRecordIds = useRecoilValue( + contextStoreTargetedRecordIdsState, + ); + + const contextStoreCurrentObjectMetadataId = useRecoilValue( + contextStoreCurrentObjectMetadataIdState, + ); + + const { objectMetadataItem } = useObjectMetadataItemById({ + objectId: contextStoreCurrentObjectMetadataId, + }); + + const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = + useState(false); + + const { deleteTableData } = useDeleteTableData({ + objectNameSingular: objectMetadataItem?.nameSingular ?? '', + recordIndexId: objectMetadataItem?.namePlural ?? '', + }); + + const handleDeleteClick = useCallback(() => { + deleteTableData(contextStoreTargetedRecordIds); + }, [deleteTableData, contextStoreTargetedRecordIds]); + + const isRemoteObject = objectMetadataItem?.isRemote ?? false; + + const numberOfSelectedRecords = contextStoreTargetedRecordIds.length; + + const canDelete = + !isRemoteObject && numberOfSelectedRecords < DELETE_MAX_COUNT; + + useEffect(() => { + if (canDelete) { + addActionMenuEntry({ + key: 'delete', + label: 'Delete', + position, + Icon: IconTrash, + accent: 'danger', + onClick: () => { + setIsDeleteRecordsModalOpen(true); + }, + ConfirmationModal: ( + <ConfirmationModal + isOpen={isDeleteRecordsModalOpen} + setIsOpen={setIsDeleteRecordsModalOpen} + title={`Delete ${numberOfSelectedRecords} ${ + numberOfSelectedRecords === 1 ? `record` : 'records' + }`} + subtitle={`Are you sure you want to delete ${ + numberOfSelectedRecords === 1 ? 'this record' : 'these records' + }? ${ + numberOfSelectedRecords === 1 ? 'It' : 'They' + } can be recovered from the Options menu.`} + onConfirmClick={() => handleDeleteClick()} + deleteButtonText={`Delete ${ + numberOfSelectedRecords > 1 ? 'Records' : 'Record' + }`} + /> + ), + }); + } else { + removeActionMenuEntry('delete'); + } + }, [ + canDelete, + addActionMenuEntry, + removeActionMenuEntry, + isDeleteRecordsModalOpen, + numberOfSelectedRecords, + handleDeleteClick, + position, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/ExportRecordsActionEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/ExportRecordsActionEffect.tsx new file mode 100644 index 000000000000..d7b50ddaf0d3 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/ExportRecordsActionEffect.tsx @@ -0,0 +1,53 @@ +import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; +import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState'; +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { + displayedExportProgress, + useExportTableData, +} from '@/object-record/record-index/options/hooks/useExportTableData'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; +import { IconFileExport } from 'twenty-ui'; + +export const ExportRecordsActionEffect = ({ + position, +}: { + position: number; +}) => { + const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); + + const contextStoreCurrentObjectMetadataId = useRecoilValue( + contextStoreCurrentObjectMetadataIdState, + ); + + const { objectMetadataItem } = useObjectMetadataItemById({ + objectId: contextStoreCurrentObjectMetadataId, + }); + + const baseTableDataParams = { + delayMs: 100, + objectNameSingular: objectMetadataItem?.nameSingular ?? '', + recordIndexId: objectMetadataItem?.namePlural ?? '', + }; + + const { progress, download } = useExportTableData({ + ...baseTableDataParams, + filename: `${objectMetadataItem?.nameSingular}.csv`, + }); + + useEffect(() => { + addActionMenuEntry({ + key: 'export', + position, + label: displayedExportProgress(progress), + Icon: IconFileExport, + accent: 'default', + onClick: () => download(), + }); + + return () => { + removeActionMenuEntry('export'); + }; + }, [download, progress, addActionMenuEntry, removeActionMenuEntry, position]); + return null; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/ManageFavoritesActionEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/ManageFavoritesActionEffect.tsx new file mode 100644 index 000000000000..e9767b034203 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/ManageFavoritesActionEffect.tsx @@ -0,0 +1,78 @@ +import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries'; +import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; +import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui'; + +export const ManageFavoritesActionEffect = ({ + position, +}: { + position: number; +}) => { + const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); + + const contextStoreTargetedRecordIds = useRecoilValue( + contextStoreTargetedRecordIdsState, + ); + const contextStoreCurrentObjectMetadataId = useRecoilValue( + contextStoreCurrentObjectMetadataIdState, + ); + + const { favorites, createFavorite, deleteFavorite } = useFavorites(); + + const selectedRecordId = contextStoreTargetedRecordIds[0]; + + const selectedRecord = useRecoilValue( + recordStoreFamilyState(selectedRecordId), + ); + + const { objectMetadataItem } = useObjectMetadataItemById({ + objectId: contextStoreCurrentObjectMetadataId, + }); + + const foundFavorite = favorites?.find( + (favorite) => favorite.recordId === selectedRecordId, + ); + + const isFavorite = !!selectedRecordId && !!foundFavorite; + + useEffect(() => { + if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) { + return; + } + + addActionMenuEntry({ + key: 'manage-favorites', + label: isFavorite ? 'Remove from favorites' : 'Add to favorites', + position, + Icon: isFavorite ? IconHeartOff : IconHeart, + onClick: () => { + if (isFavorite && isDefined(foundFavorite?.id)) { + deleteFavorite(foundFavorite.id); + } else if (isDefined(selectedRecord)) { + createFavorite(selectedRecord, objectMetadataItem.nameSingular); + } + }, + }); + + return () => { + removeActionMenuEntry('manage-favorites'); + }; + }, [ + addActionMenuEntry, + createFavorite, + deleteFavorite, + foundFavorite?.id, + isFavorite, + objectMetadataItem, + position, + removeActionMenuEntry, + selectedRecord, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter.tsx new file mode 100644 index 000000000000..69bfd3305094 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter.tsx @@ -0,0 +1,14 @@ +import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect'; +import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect'; + +const actionEffects = [ExportRecordsActionEffect, DeleteRecordsActionEffect]; + +export const MultipleRecordsActionMenuEntriesSetter = () => { + return ( + <> + {actionEffects.map((ActionEffect, index) => ( + <ActionEffect key={index} position={index} /> + ))} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx new file mode 100644 index 000000000000..75267e445d49 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx @@ -0,0 +1,20 @@ +import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter'; +import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { useRecoilValue } from 'recoil'; + +export const RecordActionMenuEntriesSetter = () => { + const contextStoreTargetedRecordIds = useRecoilValue( + contextStoreTargetedRecordIdsState, + ); + + if (contextStoreTargetedRecordIds.length === 0) { + return null; + } + + if (contextStoreTargetedRecordIds.length === 1) { + return <SingleRecordActionMenuEntriesSetter />; + } + + return <MultipleRecordsActionMenuEntriesSetter />; +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter.tsx new file mode 100644 index 000000000000..feeba5aabc61 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter.tsx @@ -0,0 +1,18 @@ +import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect'; +import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect'; +import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect'; + +export const SingleRecordActionMenuEntriesSetter = () => { + const actionEffects = [ + ExportRecordsActionEffect, + DeleteRecordsActionEffect, + ManageFavoritesActionEffect, + ]; + return ( + <> + {actionEffects.map((ActionEffect, index) => ( + <ActionEffect key={index} position={index} /> + ))} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/ActionMenuBar.tsx b/packages/twenty-front/src/modules/action-menu/components/ActionMenuBar.tsx new file mode 100644 index 000000000000..258683347919 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/ActionMenuBar.tsx @@ -0,0 +1,53 @@ +import styled from '@emotion/styled'; + +import { ActionMenuBarEntry } from '@/action-menu/components/ActionMenuBarEntry'; +import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { useRecoilValue } from 'recoil'; + +const StyledLabel = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.md}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; +`; + +export const ActionMenuBar = () => { + const contextStoreTargetedRecordIds = useRecoilValue( + contextStoreTargetedRecordIdsState, + ); + + const actionMenuId = useAvailableComponentInstanceIdOrThrow( + ActionMenuComponentInstanceContext, + ); + + const actionMenuEntries = useRecoilComponentValueV2( + actionMenuEntriesComponentSelector, + ); + + if (actionMenuEntries.length === 0) { + return null; + } + + return ( + <BottomBar + bottomBarId={`action-bar-${actionMenuId}`} + bottomBarHotkeyScopeFromParent={{ + scope: ActionBarHotkeyScope.ActionBar, + }} + > + <StyledLabel> + {contextStoreTargetedRecordIds.length} selected: + </StyledLabel> + {actionMenuEntries.map((entry, index) => ( + <ActionMenuBarEntry key={index} entry={entry} /> + ))} + </BottomBar> + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/ActionMenuBarEntry.tsx b/packages/twenty-front/src/modules/action-menu/components/ActionMenuBarEntry.tsx new file mode 100644 index 000000000000..02802ec4a616 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/ActionMenuBarEntry.tsx @@ -0,0 +1,49 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent'; + +type ActionMenuBarEntryProps = { + entry: ActionMenuEntry; +}; + +const StyledButton = styled.div<{ accent: MenuItemAccent }>` + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${(props) => + props.accent === 'danger' + ? props.theme.color.red + : props.theme.font.color.secondary}; + cursor: pointer; + display: flex; + justify-content: center; + + padding: ${({ theme }) => theme.spacing(2)}; + transition: background 0.1s ease; + user-select: none; + + &:hover { + background: ${({ theme, accent }) => + accent === 'danger' + ? theme.background.danger + : theme.background.tertiary}; + } +`; + +const StyledButtonLabel = styled.div` + font-weight: ${({ theme }) => theme.font.weight.medium}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; + +export const ActionMenuBarEntry = ({ entry }: ActionMenuBarEntryProps) => { + const theme = useTheme(); + return ( + <StyledButton + accent={entry.accent ?? 'default'} + onClick={() => entry.onClick?.()} + > + {entry.Icon && <entry.Icon size={theme.icon.size.md} />} + <StyledButtonLabel>{entry.label}</StyledButtonLabel> + </StyledButton> + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/ActionMenuConfirmationModals.tsx b/packages/twenty-front/src/modules/action-menu/components/ActionMenuConfirmationModals.tsx new file mode 100644 index 000000000000..0b3e52032c59 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/ActionMenuConfirmationModals.tsx @@ -0,0 +1,18 @@ +import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +export const ActionMenuConfirmationModals = () => { + const actionMenuEntries = useRecoilComponentValueV2( + actionMenuEntriesComponentSelector, + ); + + return ( + <div data-select-disable> + {actionMenuEntries.map((actionMenuEntry, index) => + actionMenuEntry.ConfirmationModal ? ( + <div key={index}>{actionMenuEntry.ConfirmationModal}</div> + ) : null, + )} + </div> + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/ActionMenuDropdown.tsx b/packages/twenty-front/src/modules/action-menu/components/ActionMenuDropdown.tsx new file mode 100644 index 000000000000..18ebdac7667e --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/ActionMenuDropdown.tsx @@ -0,0 +1,88 @@ +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; + +import { PositionType } from '../types/PositionType'; + +import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState'; +import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; + +type StyledContainerProps = { + position: PositionType; +}; + +const StyledContainerActionMenuDropdown = styled.div<StyledContainerProps>` + align-items: flex-start; + background: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.light}; + border-radius: ${({ theme }) => theme.border.radius.md}; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; + display: flex; + flex-direction: column; + + left: ${(props) => `${props.position.x}px`}; + position: fixed; + top: ${(props) => `${props.position.y}px`}; + + transform: translateX(-50%); + width: auto; +`; + +export const ActionMenuDropdown = () => { + const actionMenuEntries = useRecoilComponentValueV2( + actionMenuEntriesComponentSelector, + ); + + const actionMenuId = useAvailableComponentInstanceIdOrThrow( + ActionMenuComponentInstanceContext, + ); + + const actionMenuDropdownPosition = useRecoilValue( + extractComponentState( + actionMenuDropdownPositionComponentState, + `action-menu-dropdown-${actionMenuId}`, + ), + ); + + if (actionMenuEntries.length === 0) { + return null; + } + + //TODO: remove this + const width = actionMenuEntries.some( + (actionMenuEntry) => actionMenuEntry.label === 'Remove from favorites', + ) + ? 200 + : undefined; + + return ( + <StyledContainerActionMenuDropdown + position={actionMenuDropdownPosition} + className="context-menu" + > + <Dropdown + dropdownId={`action-menu-dropdown-${actionMenuId}`} + dropdownHotkeyScope={{ + scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown, + }} + data-select-disable + dropdownMenuWidth={width} + dropdownComponents={actionMenuEntries.map((item, index) => ( + <MenuItem + key={index} + LeftIcon={item.Icon} + onClick={item.onClick} + accent={item.accent} + text={item.label} + /> + ))} + /> + </StyledContainerActionMenuDropdown> + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/ActionMenuEffect.tsx b/packages/twenty-front/src/modules/action-menu/components/ActionMenuEffect.tsx new file mode 100644 index 000000000000..60355cc9256f --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/ActionMenuEffect.tsx @@ -0,0 +1,46 @@ +import { useActionMenu } from '@/action-menu/hooks/useActionMenu'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; + +export const ActionMenuEffect = () => { + const contextStoreTargetedRecordIds = useRecoilValue( + contextStoreTargetedRecordIdsState, + ); + + const actionMenuId = useAvailableComponentInstanceIdOrThrow( + ActionMenuComponentInstanceContext, + ); + + const { openActionBar, closeActionBar } = useActionMenu(actionMenuId); + + const isDropdownOpen = useRecoilValue( + extractComponentState( + isDropdownOpenComponentState, + `action-menu-dropdown-${actionMenuId}`, + ), + ); + + useEffect(() => { + if (contextStoreTargetedRecordIds.length > 0 && !isDropdownOpen) { + // We only handle opening the ActionMenuBar here, not the Dropdown. + // The Dropdown is already managed by sync handlers for events like + // right-click to open and click outside to close. + openActionBar(); + } + if (contextStoreTargetedRecordIds.length === 0) { + closeActionBar(); + } + }, [ + contextStoreTargetedRecordIds, + openActionBar, + closeActionBar, + isDropdownOpen, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuBar.stories.tsx b/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuBar.stories.tsx new file mode 100644 index 000000000000..34d709d1685d --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuBar.stories.tsx @@ -0,0 +1,111 @@ +import { expect, jest } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { RecoilRoot } from 'recoil'; + +import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar'; +import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; +import { userEvent, waitFor, within } from '@storybook/test'; +import { IconCheckbox, IconTrash } from 'twenty-ui'; + +const deleteMock = jest.fn(); +const markAsDoneMock = jest.fn(); + +const meta: Meta<typeof ActionMenuBar> = { + title: 'Modules/ActionMenu/ActionMenuBar', + component: ActionMenuBar, + decorators: [ + (Story) => ( + <RecoilRoot + initializeState={({ set }) => { + set(contextStoreTargetedRecordIdsState, ['1', '2', '3']); + set( + actionMenuEntriesComponentState.atomFamily({ + instanceId: 'story-action-menu', + }), + new Map([ + [ + 'delete', + { + key: 'delete', + label: 'Delete', + position: 0, + Icon: IconTrash, + onClick: deleteMock, + }, + ], + [ + 'markAsDone', + { + key: 'markAsDone', + label: 'Mark as done', + position: 1, + Icon: IconCheckbox, + onClick: markAsDoneMock, + }, + ], + ]), + ); + set( + isBottomBarOpenedComponentState.atomFamily({ + instanceId: 'action-bar-story-action-menu', + }), + true, + ); + }} + > + <ActionMenuComponentInstanceContext.Provider + value={{ instanceId: 'story-action-menu' }} + > + <Story /> + </ActionMenuComponentInstanceContext.Provider> + </RecoilRoot> + ), + ], + args: { + actionMenuId: 'story-action-menu', + }, +}; + +export default meta; + +type Story = StoryObj<typeof ActionMenuBar>; + +export const Default: Story = { + args: { + actionMenuId: 'story-action-menu', + }, +}; + +export const WithCustomSelection: Story = { + args: { + actionMenuId: 'story-action-menu', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const selectionText = await canvas.findByText('3 selected:'); + expect(selectionText).toBeInTheDocument(); + }, +}; + +export const WithButtonClicks: Story = { + args: { + actionMenuId: 'story-action-menu', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const deleteButton = await canvas.findByText('Delete'); + await userEvent.click(deleteButton); + + const markAsDoneButton = await canvas.findByText('Mark as done'); + await userEvent.click(markAsDoneButton); + + await waitFor(() => { + expect(deleteMock).toHaveBeenCalled(); + expect(markAsDoneMock).toHaveBeenCalled(); + }); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuBarEntry.stories.tsx b/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuBarEntry.stories.tsx new file mode 100644 index 000000000000..a9c7b26b8430 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuBarEntry.stories.tsx @@ -0,0 +1,62 @@ +import { expect, jest } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/testing-library'; + +import { ComponentDecorator, IconCheckbox, IconTrash } from 'twenty-ui'; +import { ActionMenuBarEntry } from '../ActionMenuBarEntry'; + +const meta: Meta<typeof ActionMenuBarEntry> = { + title: 'Modules/ActionMenu/ActionMenuBarEntry', + component: ActionMenuBarEntry, + decorators: [ComponentDecorator], +}; + +export default meta; + +type Story = StoryObj<typeof ActionMenuBarEntry>; + +const deleteMock = jest.fn(); +const markAsDoneMock = jest.fn(); + +export const Default: Story = { + args: { + entry: { + key: 'delete', + label: 'Delete', + position: 0, + Icon: IconTrash, + onClick: deleteMock, + }, + }, +}; + +export const WithDangerAccent: Story = { + args: { + entry: { + key: 'delete', + label: 'Delete', + position: 0, + Icon: IconTrash, + onClick: deleteMock, + accent: 'danger', + }, + }, +}; + +export const WithInteraction: Story = { + args: { + entry: { + key: 'markAsDone', + label: 'Mark as done', + position: 0, + Icon: IconCheckbox, + onClick: markAsDoneMock, + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const button = await canvas.findByText('Mark as done'); + await userEvent.click(button); + expect(markAsDoneMock).toHaveBeenCalled(); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuDropdown.stories.tsx b/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuDropdown.stories.tsx new file mode 100644 index 000000000000..53a0714cee9e --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/components/__stories__/ActionMenuDropdown.stories.tsx @@ -0,0 +1,117 @@ +import { expect, jest } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/testing-library'; +import { RecoilRoot } from 'recoil'; + +import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown'; +import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState'; +import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; +import { IconCheckbox, IconHeart, IconTrash } from 'twenty-ui'; + +const deleteMock = jest.fn(); +const markAsDoneMock = jest.fn(); +const addToFavoritesMock = jest.fn(); + +const meta: Meta<typeof ActionMenuDropdown> = { + title: 'Modules/ActionMenu/ActionMenuDropdown', + component: ActionMenuDropdown, + decorators: [ + (Story) => ( + <RecoilRoot + initializeState={({ set }) => { + set( + extractComponentState( + actionMenuDropdownPositionComponentState, + 'action-menu-dropdown-story', + ), + { x: 10, y: 10 }, + ); + set( + actionMenuEntriesComponentState.atomFamily({ + instanceId: 'story-action-menu', + }), + new Map([ + [ + 'delete', + { + key: 'delete', + label: 'Delete', + position: 0, + Icon: IconTrash, + onClick: deleteMock, + }, + ], + [ + 'markAsDone', + { + key: 'markAsDone', + label: 'Mark as done', + position: 1, + Icon: IconCheckbox, + onClick: markAsDoneMock, + }, + ], + [ + 'addToFavorites', + { + key: 'addToFavorites', + label: 'Add to favorites', + position: 2, + Icon: IconHeart, + onClick: addToFavoritesMock, + }, + ], + ]), + ); + set( + extractComponentState( + isDropdownOpenComponentState, + 'action-menu-dropdown-story-action-menu', + ), + true, + ); + }} + > + <ActionMenuComponentInstanceContext.Provider + value={{ instanceId: 'story-action-menu' }} + > + <Story /> + </ActionMenuComponentInstanceContext.Provider> + </RecoilRoot> + ), + ], +}; + +export default meta; + +type Story = StoryObj<typeof ActionMenuDropdown>; + +export const Default: Story = { + args: { + actionMenuId: 'story', + }, +}; + +export const WithInteractions: Story = { + args: { + actionMenuId: 'story', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const deleteButton = await canvas.findByText('Delete'); + await userEvent.click(deleteButton); + expect(deleteMock).toHaveBeenCalled(); + + const markAsDoneButton = await canvas.findByText('Mark as done'); + await userEvent.click(markAsDoneButton); + expect(markAsDoneMock).toHaveBeenCalled(); + + const addToFavoritesButton = await canvas.findByText('Add to favorites'); + await userEvent.click(addToFavoritesButton); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts b/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts new file mode 100644 index 000000000000..0f37475adedc --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts @@ -0,0 +1,83 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { useActionMenu } from '../useActionMenu'; + +const openBottomBar = jest.fn(); +const closeBottomBar = jest.fn(); +const openDropdown = jest.fn(); +const closeDropdown = jest.fn(); + +jest.mock('@/ui/layout/bottom-bar/hooks/useBottomBar', () => ({ + useBottomBar: jest.fn(() => ({ + openBottomBar: openBottomBar, + closeBottomBar: closeBottomBar, + })), +})); + +jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({ + useDropdownV2: jest.fn(() => ({ + openDropdown: openDropdown, + closeDropdown: closeDropdown, + })), +})); + +describe('useActionMenu', () => { + const actionMenuId = 'test-action-menu'; + + it('should return the correct functions', () => { + const { result } = renderHook(() => useActionMenu(actionMenuId)); + + expect(result.current).toHaveProperty('openActionMenuDropdown'); + expect(result.current).toHaveProperty('openActionBar'); + expect(result.current).toHaveProperty('closeActionBar'); + expect(result.current).toHaveProperty('closeActionMenuDropdown'); + }); + + it('should call the correct functions when opening action menu dropdown', () => { + const { result } = renderHook(() => useActionMenu(actionMenuId)); + + act(() => { + result.current.openActionMenuDropdown(); + }); + + expect(closeBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`); + expect(openDropdown).toHaveBeenCalledWith( + `action-menu-dropdown-${actionMenuId}`, + ); + }); + + it('should call the correct functions when opening action bar', () => { + const { result } = renderHook(() => useActionMenu(actionMenuId)); + + act(() => { + result.current.openActionBar(); + }); + + expect(closeDropdown).toHaveBeenCalledWith( + `action-menu-dropdown-${actionMenuId}`, + ); + expect(openBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`); + }); + + it('should call the correct function when closing action menu dropdown', () => { + const { result } = renderHook(() => useActionMenu(actionMenuId)); + + act(() => { + result.current.closeActionMenuDropdown(); + }); + + expect(closeDropdown).toHaveBeenCalledWith( + `action-menu-dropdown-${actionMenuId}`, + ); + }); + + it('should call the correct function when closing action bar', () => { + const { result } = renderHook(() => useActionMenu(actionMenuId)); + + act(() => { + result.current.closeActionBar(); + }); + + expect(closeBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts new file mode 100644 index 000000000000..881cadd694e1 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts @@ -0,0 +1,32 @@ +import { useBottomBar } from '@/ui/layout/bottom-bar/hooks/useBottomBar'; +import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; + +export const useActionMenu = (actionMenuId: string) => { + const { openDropdown, closeDropdown } = useDropdownV2(); + const { openBottomBar, closeBottomBar } = useBottomBar(); + + const openActionMenuDropdown = () => { + closeBottomBar(`action-bar-${actionMenuId}`); + openDropdown(`action-menu-dropdown-${actionMenuId}`); + }; + + const openActionBar = () => { + closeDropdown(`action-menu-dropdown-${actionMenuId}`); + openBottomBar(`action-bar-${actionMenuId}`); + }; + + const closeActionMenuDropdown = () => { + closeDropdown(`action-menu-dropdown-${actionMenuId}`); + }; + + const closeActionBar = () => { + closeBottomBar(`action-bar-${actionMenuId}`); + }; + + return { + openActionMenuDropdown, + openActionBar, + closeActionBar, + closeActionMenuDropdown, + }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntries.ts b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntries.ts new file mode 100644 index 000000000000..5f375eca178f --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntries.ts @@ -0,0 +1,28 @@ +import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; +import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; + +export const useActionMenuEntries = () => { + const setActionMenuEntries = useSetRecoilComponentStateV2( + actionMenuEntriesComponentState, + ); + + const addActionMenuEntry = (entry: ActionMenuEntry) => { + setActionMenuEntries( + (prevEntries) => new Map([...prevEntries, [entry.key, entry]]), + ); + }; + + const removeActionMenuEntry = (key: string) => { + setActionMenuEntries((prevEntries) => { + const newMap = new Map(prevEntries); + newMap.delete(key); + return newMap; + }); + }; + + return { + addActionMenuEntry, + removeActionMenuEntry, + }; +}; diff --git a/packages/twenty-front/src/modules/action-menu/states/actionMenuDropdownPositionComponentState.ts b/packages/twenty-front/src/modules/action-menu/states/actionMenuDropdownPositionComponentState.ts new file mode 100644 index 000000000000..f2f8f06b1372 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/states/actionMenuDropdownPositionComponentState.ts @@ -0,0 +1,11 @@ +import { PositionType } from '@/action-menu/types/PositionType'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const actionMenuDropdownPositionComponentState = + createComponentState<PositionType>({ + key: 'actionMenuDropdownPositionComponentState', + defaultValue: { + x: null, + y: null, + }, + }); diff --git a/packages/twenty-front/src/modules/action-menu/states/actionMenuEntriesComponentSelector.ts b/packages/twenty-front/src/modules/action-menu/states/actionMenuEntriesComponentSelector.ts new file mode 100644 index 000000000000..921f97b38f52 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/states/actionMenuEntriesComponentSelector.ts @@ -0,0 +1,22 @@ +import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2'; +import { isDefined } from 'twenty-ui'; + +export const actionMenuEntriesComponentSelector = createComponentSelectorV2< + ActionMenuEntry[] +>({ + key: 'actionMenuEntriesComponentSelector', + instanceContext: ActionMenuComponentInstanceContext, + get: + ({ instanceId }) => + ({ get }) => + Array.from( + get( + actionMenuEntriesComponentState.atomFamily({ instanceId }), + ).values(), + ) + .filter(isDefined) + .sort((a, b) => a.position - b.position), +}); diff --git a/packages/twenty-front/src/modules/action-menu/states/actionMenuEntriesComponentState.ts b/packages/twenty-front/src/modules/action-menu/states/actionMenuEntriesComponentState.ts new file mode 100644 index 000000000000..ebf9c2cedeb4 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/states/actionMenuEntriesComponentState.ts @@ -0,0 +1,11 @@ +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { ActionMenuEntry } from '../types/ActionMenuEntry'; + +export const actionMenuEntriesComponentState = createComponentStateV2< + Map<string, ActionMenuEntry> +>({ + key: 'actionMenuEntriesComponentState', + defaultValue: new Map(), + componentInstanceContext: ActionMenuComponentInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/action-menu/states/contexts/ActionMenuComponentInstanceContext.ts b/packages/twenty-front/src/modules/action-menu/states/contexts/ActionMenuComponentInstanceContext.ts new file mode 100644 index 000000000000..1e64690c744d --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/states/contexts/ActionMenuComponentInstanceContext.ts @@ -0,0 +1,4 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const ActionMenuComponentInstanceContext = + createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/action-menu/types/ActionBarHotKeyScope.ts b/packages/twenty-front/src/modules/action-menu/types/ActionBarHotKeyScope.ts new file mode 100644 index 000000000000..fcadbee366f0 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/types/ActionBarHotKeyScope.ts @@ -0,0 +1,3 @@ +export enum ActionBarHotkeyScope { + ActionBar = 'action-bar', +} diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/types/ActionBarItemAccent.ts b/packages/twenty-front/src/modules/action-menu/types/ActionBarItemAccent.ts similarity index 100% rename from packages/twenty-front/src/modules/ui/navigation/action-bar/types/ActionBarItemAccent.ts rename to packages/twenty-front/src/modules/action-menu/types/ActionBarItemAccent.ts diff --git a/packages/twenty-front/src/modules/action-menu/types/ActionMenuDropdownHotKeyScope.ts b/packages/twenty-front/src/modules/action-menu/types/ActionMenuDropdownHotKeyScope.ts new file mode 100644 index 000000000000..9c0e2df42edf --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/types/ActionMenuDropdownHotKeyScope.ts @@ -0,0 +1,3 @@ +export enum ActionMenuDropdownHotkeyScope { + ActionMenuDropdown = 'action-menu-dropdown', +} diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/types/ContextMenuEntry.ts b/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts similarity index 83% rename from packages/twenty-front/src/modules/ui/navigation/context-menu/types/ContextMenuEntry.ts rename to packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts index 416a41419f62..4fe180955238 100644 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/types/ContextMenuEntry.ts +++ b/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts @@ -3,8 +3,10 @@ import { IconComponent } from 'twenty-ui'; import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent'; -export type ContextMenuEntry = { +export type ActionMenuEntry = { + key: string; label: string; + position: number; Icon: IconComponent; accent?: MenuItemAccent; onClick?: (event?: MouseEvent<HTMLElement>) => void; diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/types/PositionType.ts b/packages/twenty-front/src/modules/action-menu/types/PositionType.ts similarity index 100% rename from packages/twenty-front/src/modules/ui/navigation/context-menu/types/PositionType.ts rename to packages/twenty-front/src/modules/action-menu/types/PositionType.ts diff --git a/packages/twenty-front/src/modules/activities/components/ActivityList.tsx b/packages/twenty-front/src/modules/activities/components/ActivityList.tsx new file mode 100644 index 000000000000..b8b8b2f61d5f --- /dev/null +++ b/packages/twenty-front/src/modules/activities/components/ActivityList.tsx @@ -0,0 +1,16 @@ +import { Card } from '@/ui/layout/card/components/Card'; +import styled from '@emotion/styled'; + +const StyledList = styled(Card)` + & > :not(:last-child) { + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + } + + width: calc(100% - 2px); + + overflow: auto; +`; + +export const ActivityList = ({ children }: React.PropsWithChildren) => { + return <StyledList>{children}</StyledList>; +}; diff --git a/packages/twenty-front/src/modules/activities/components/ActivityRow.tsx b/packages/twenty-front/src/modules/activities/components/ActivityRow.tsx new file mode 100644 index 000000000000..00fdbb1a68f8 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/components/ActivityRow.tsx @@ -0,0 +1,35 @@ +import { CardContent } from '@/ui/layout/card/components/CardContent'; +import styled from '@emotion/styled'; +import React from 'react'; + +const StyledRowContent = styled(CardContent)<{ + clickable?: boolean; +}>` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + height: ${({ theme }) => theme.spacing(12)}; + padding: ${({ theme }) => theme.spacing(0, 4)}; + cursor: ${({ clickable }) => (clickable === true ? 'pointer' : 'default')}; +`; + +export const ActivityRow = ({ + children, + onClick, + disabled, +}: React.PropsWithChildren<{ + onClick?: (event: React.MouseEvent<HTMLDivElement>) => void; + disabled?: boolean; +}>) => { + const handleClick = (event: React.MouseEvent<HTMLDivElement>) => { + if (disabled !== true) { + onClick?.(event); + } + }; + + return ( + <StyledRowContent onClick={handleClick} clickable={disabled !== true}> + {children} + </StyledRowContent> + ); +}; 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} > <StyledSkeletonContainer> - <Skeleton width={440} height={16} /> + <Skeleton + width={440} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> {withSubSections && skeletonItems.map(({ id }, index) => ( <StyledSkeletonSubSection key={id}> - <SkeletonColumnLoader height={index === 1 ? 120 : 84} /> + <SkeletonColumnLoader + height={ + index === 1 + ? SKELETON_LOADER_HEIGHT_SIZES.columns.m + : SKELETON_LOADER_HEIGHT_SIZES.columns.s + } + /> <StyledSkeletonSubSectionContent> - <Skeleton width={400} height={24} /> - <Skeleton width={400} height={24} /> - {index === 1 && <Skeleton width={400} height={24} />} + <Skeleton + width={400} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} + /> + <Skeleton + width={400} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} + /> + {index === 1 && ( + <Skeleton + width={400} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} + /> + )} </StyledSkeletonSubSectionContent> </StyledSkeletonSubSection> ))} diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx index 32dde14ba3bc..4f4a64b38686 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { EmailThreadMessageBody } from '@/activities/emails/components/EmailThreadMessageBody'; import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/EmailThreadMessageBodyPreview'; @@ -30,6 +30,7 @@ const StyledThreadMessageBody = styled.div` type EmailThreadMessageProps = { body: string; sentAt: string; + sender: EmailThreadMessageParticipant; participants: EmailThreadMessageParticipant[]; isExpanded?: boolean; }; @@ -37,17 +38,17 @@ type EmailThreadMessageProps = { export const EmailThreadMessage = ({ body, sentAt, + sender, participants, isExpanded = false, }: EmailThreadMessageProps) => { const [isOpen, setIsOpen] = useState(isExpanded); - const from = participants.find((participant) => participant.role === 'from'); const receivers = participants.filter( (participant) => participant.role !== 'from', ); - if (!from || receivers.length === 0) { + if (!sender || receivers.length === 0) { return null; } @@ -57,7 +58,7 @@ export const EmailThreadMessage = ({ style={{ cursor: isOpen ? 'auto' : 'pointer' }} > <StyledThreadMessageHeader onClick={() => isOpen && setIsOpen(false)}> - <EmailThreadMessageSender sender={from} sentAt={sentAt} /> + <EmailThreadMessageSender sender={sender} sentAt={sentAt} /> {isOpen && <EmailThreadMessageReceivers receivers={receivers} />} </StyledThreadMessageHeader> <StyledThreadMessageBody> diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx index bb7f8c1407f4..cfa2aa672853 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx @@ -1,30 +1,15 @@ import styled from '@emotion/styled'; -import { useRef } from 'react'; import { useRecoilCallback } from 'recoil'; import { Avatar, GRAY_SCALE } from 'twenty-ui'; +import { ActivityRow } from '@/activities/components/ActivityRow'; import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared'; import { useEmailThread } from '@/activities/emails/hooks/useEmailThread'; import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState'; -import { CardContent } from '@/ui/layout/card/components/CardContent'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql'; import { formatToHumanReadableDate } from '~/utils/date-utils'; -const StyledCardContent = styled(CardContent)<{ - visibility: MessageChannelVisibility; -}>` - align-items: center; - display: flex; - gap: ${({ theme }) => theme.spacing(2)}; - height: ${({ theme }) => theme.spacing(12)}; - padding: ${({ theme }) => theme.spacing(0, 4)}; - cursor: ${({ visibility }) => - visibility === MessageChannelVisibility.ShareEverything - ? 'pointer' - : 'default'}; -`; - const StyledHeading = styled.div<{ unread: boolean }>` display: flex; overflow: hidden; @@ -82,16 +67,10 @@ const StyledReceivedAt = styled.div` `; type EmailThreadPreviewProps = { - divider?: boolean; thread: TimelineThread; }; -export const EmailThreadPreview = ({ - divider, - thread, -}: EmailThreadPreviewProps) => { - const cardRef = useRef<HTMLDivElement>(null); - +export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => { const { openEmailThread } = useEmailThread(); const visibility = thread.visibility; @@ -143,12 +122,12 @@ export const EmailThreadPreview = ({ ], ); + const isDisabled = visibility !== MessageChannelVisibility.ShareEverything; + return ( - <StyledCardContent - ref={cardRef} + <ActivityRow onClick={(event) => handleThreadClick(event)} - divider={divider} - visibility={visibility} + disabled={isDisabled} > <StyledHeading unread={!thread.read}> <StyledParticipantsContainer> @@ -201,6 +180,6 @@ export const EmailThreadPreview = ({ <StyledReceivedAt> {formatToHumanReadableDate(thread.lastMessageReceivedAt)} </StyledReceivedAt> - </StyledCardContent> + </ActivityRow> ); }; diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx index 17f44524fea6..8a3eef7ea33f 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; import { H1Title, H1TitleFontColor } from 'twenty-ui'; +import { ActivityList } from '@/activities/components/ActivityList'; import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { SkeletonLoader } from '@/activities/components/SkeletonLoader'; import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview'; @@ -18,7 +19,6 @@ import { AnimatedPlaceholderEmptyTitle, EMPTY_PLACEHOLDER_TRANSITION_PROPS, } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; -import { Card } from '@/ui/layout/card/components/Card'; import { Section } from '@/ui/layout/section/components/Section'; import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql'; @@ -106,15 +106,11 @@ export const EmailThreads = ({ fontColor={H1TitleFontColor.Primary} /> {!firstQueryLoading && ( - <Card> - {timelineThreads?.map((thread: TimelineThread, index: number) => ( - <EmailThreadPreview - key={index} - divider={index < timelineThreads.length - 1} - thread={thread} - /> + <ActivityList> + {timelineThreads?.map((thread: TimelineThread) => ( + <EmailThreadPreview key={thread.id} thread={thread} /> ))} - </Card> + </ActivityList> )} <CustomResolverFetchMoreLoader loading={isFetchingMore || firstQueryLoading} diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts b/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts index d141d867b223..f818332fddf4 100644 --- a/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts +++ b/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts @@ -47,11 +47,7 @@ export const fetchAllThreadMessagesOperationSignatureFactory: RecordGqlOperation id: true, role: true, displayName: true, - participant: { - id: true, - email: true, - name: true, - }, + handle: true, person: true, workspaceMember: true, }, diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx index a830e8367f95..3ef96d57310e 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx @@ -1,9 +1,9 @@ -import { useState } from 'react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { IconArrowsVertical } from 'twenty-ui'; import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage'; -import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage'; +import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender'; import { Button } from '@/ui/input/button/components/Button'; const StyledButtonContainer = styled.div` @@ -14,7 +14,7 @@ const StyledButtonContainer = styled.div` export const IntermediaryMessages = ({ messages, }: { - messages: EmailThreadMessageType[]; + messages: EmailThreadMessageWithSender[]; }) => { const [areMessagesOpen, setAreMessagesOpen] = useState(false); @@ -26,6 +26,7 @@ export const IntermediaryMessages = ({ messages.map((message) => ( <EmailThreadMessage key={message.id} + sender={message.sender} participants={message.messageParticipants} body={message.text} sentAt={message.receivedAt} diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx index 87c986c06b41..42367f2f9adf 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx @@ -55,23 +55,11 @@ export const RightDrawerEmailThread = () => { messageChannelLoading, } = useRightDrawerEmailThread(); - const visibleMessages = useMemo(() => { - return messages.filter(({ messageParticipants }) => { - const from = messageParticipants.find( - (participant) => participant.role === 'from', - ); - const receivers = messageParticipants.filter( - (participant) => participant.role !== 'from', - ); - return from && receivers.length > 0; - }); - }, [messages]); - useEffect(() => { - if (!visibleMessages[0]?.messageThread) { + if (!messages[0]?.messageThread) { return; } - setMessageThread(visibleMessages[0]?.messageThread); + setMessageThread(messages[0]?.messageThread); }); const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener( @@ -93,17 +81,17 @@ export const RightDrawerEmailThread = () => { ), }); - const visibleMessagesCount = visibleMessages.length; - const is5OrMoreMessages = visibleMessagesCount >= 5; - const firstMessages = visibleMessages.slice( + const messagesCount = messages.length; + const is5OrMoreMessages = messagesCount >= 5; + const firstMessages = messages.slice( 0, - is5OrMoreMessages ? 2 : visibleMessagesCount - 1, + is5OrMoreMessages ? 2 : messagesCount - 1, ); const intermediaryMessages = is5OrMoreMessages - ? visibleMessages.slice(2, visibleMessagesCount - 1) + ? messages.slice(2, messagesCount - 1) : []; - const lastMessage = visibleMessages[visibleMessagesCount - 1]; - const subject = visibleMessages[0]?.subject; + const lastMessage = messages[messagesCount - 1]; + const subject = messages[0]?.subject; const canReply = useMemo(() => { return ( @@ -119,7 +107,7 @@ export const RightDrawerEmailThread = () => { const url = `https://mail.google.com/mail/?authuser=${connectedAccountHandle}#all/${messageThreadExternalId}`; window.open(url, '_blank'); }; - if (!thread) { + if (!thread || !messages.length) { return null; } return ( @@ -136,6 +124,7 @@ export const RightDrawerEmailThread = () => { {firstMessages.map((message) => ( <EmailThreadMessage key={message.id} + sender={message.sender} participants={message.messageParticipants} body={message.text} sentAt={message.receivedAt} @@ -144,6 +133,7 @@ export const RightDrawerEmailThread = () => { <IntermediaryMessages messages={intermediaryMessages} /> <EmailThreadMessage key={lastMessage.id} + sender={lastMessage.sender} participants={lastMessage.messageParticipants} body={lastMessage.text} sentAt={lastMessage.receivedAt} diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useOpenEmailThreadRightDrawer.test.ts b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useOpenEmailThreadRightDrawer.test.ts index 6e77fc50907d..8660d7617de1 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useOpenEmailThreadRightDrawer.test.ts +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useOpenEmailThreadRightDrawer.test.ts @@ -1,5 +1,5 @@ -import { act } from 'react-dom/test-utils'; import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer'; import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope'; 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 }) => ( - <MockedProvider mocks={[]} addTypename={false}> - <RecoilRoot>{children}</RecoilRoot> - </MockedProvider> - ), + 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/emails/right-drawer/hooks/useRightDrawerEmailThread.ts b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts index 23d004f05152..d1d67695a1c5 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts @@ -6,6 +6,8 @@ import { EmailThread } from '@/activities/emails/types/EmailThread'; import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage'; import { MessageChannel } from '@/accounts/types/MessageChannel'; +import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant'; +import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender'; import { MessageChannelMessageAssociation } from '@/activities/emails/types/MessageChannelMessageAssociation'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; @@ -13,6 +15,7 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { isDefined } from 'twenty-ui'; export const useRightDrawerEmailThread = () => { const viewableRecordId = useRecoilValue(viewableRecordIdState); @@ -74,6 +77,30 @@ export const useRightDrawerEmailThread = () => { } }, [messages, isMessagesFetchComplete]); + // TODO: introduce nested filters so we can retrieve the message sender directly from the message query + const { records: messageSenders } = + useFindManyRecords<EmailThreadMessageParticipant>({ + filter: { + messageId: { + in: messages.map(({ id }) => id), + }, + role: { + eq: 'from', + }, + }, + objectNameSingular: CoreObjectNameSingular.MessageParticipant, + recordGqlFields: { + id: true, + role: true, + displayName: true, + messageId: true, + handle: true, + person: true, + workspaceMember: true, + }, + skip: messages.length === 0, + }); + const { records: messageChannelMessageAssociationData } = useFindManyRecords<MessageChannelMessageAssociation>({ filter: { @@ -123,9 +150,24 @@ export const useRightDrawerEmailThread = () => { const connectedAccountHandle = messageChannelData.length > 0 ? messageChannelData[0].handle : null; + const messagesWithSender: EmailThreadMessageWithSender[] = messages + .map((message) => { + const sender = messageSenders.find( + (messageSender) => messageSender.messageId === message.id, + ); + if (!sender) { + return null; + } + return { + ...message, + sender, + }; + }) + .filter(isDefined); + return { thread, - messages, + messages: messagesWithSender, messageThreadExternalId, connectedAccountHandle, threadLoading: messagesLoading, diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts index ed81fa848fba..72dfa74dfe38 100644 --- a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts +++ b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts @@ -3,9 +3,12 @@ import { Person } from '@/people/types/Person'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; export type EmailThreadMessageParticipant = { + id: string; displayName: string; handle: string; role: EmailParticipantRole; + messageId: string; person: Person; workspaceMember: WorkspaceMember; + __typename: 'EmailThreadMessageParticipant'; }; diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts new file mode 100644 index 000000000000..fe6916da2451 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts @@ -0,0 +1,6 @@ +import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage'; +import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant'; + +export type EmailThreadMessageWithSender = EmailThreadMessage & { + sender: EmailThreadMessageParticipant; +}; diff --git a/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts b/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts index 7b4d8c7a8a63..28c9713087e4 100644 --- a/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts +++ b/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts @@ -4,9 +4,12 @@ import { getDisplayNameFromParticipant } from '../getDisplayNameFromParticipant' describe('getDisplayNameFromParticipant', () => { const participantWithName: EmailThreadMessageParticipant = { + id: '2cac0ba7-0e60-46c6-86e7-e5b0bc55b7cf', + __typename: 'EmailThreadMessageParticipant', displayName: '', handle: '', role: 'from', + messageId: '638f52d1-fd55-4a2b-b0f3-9858ea3b2e91', person: { __typename: 'Person', id: '1', diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx index e7706f3026a0..903924fa93af 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx @@ -6,6 +6,7 @@ import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttac import { Attachment } from '@/activities/files/types/Attachment'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; +import { ActivityList } from '@/activities/components/ActivityList'; import { AttachmentRow } from './AttachmentRow'; type AttachmentListProps = { @@ -22,6 +23,9 @@ const StyledContainer = styled.div` flex-direction: column; justify-content: center; padding: ${({ theme }) => theme.spacing(2, 6, 6)}; + + width: calc(100% - ${({ theme }) => theme.spacing(12)}); + height: 100%; `; @@ -44,21 +48,11 @@ const StyledCount = styled.span` margin-left: ${({ theme }) => theme.spacing(2)}; `; -const StyledAttachmentContainer = styled.div` - align-items: flex-start; - align-self: stretch; - background: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.md}; - display: flex; - flex-flow: column nowrap; - justify-content: center; - width: 100%; -`; - const StyledDropZoneContainer = styled.div` height: 100%; width: 100%; + + overflow: auto; `; export const AttachmentList = ({ @@ -91,11 +85,11 @@ export const AttachmentList = ({ onUploadFile={onUploadFile} /> ) : ( - <StyledAttachmentContainer> + <ActivityList> {attachments.map((attachment) => ( <AttachmentRow key={attachment.id} attachment={attachment} /> ))} - </StyledAttachmentContainer> + </ActivityList> )} </StyledDropZoneContainer> </StyledContainer> diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx index 478cacc723e4..f0eca529d762 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx @@ -1,3 +1,4 @@ +import { ActivityRow } from '@/activities/components/ActivityRow'; import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown'; import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon'; import { Attachment } from '@/activities/files/types/Attachment'; @@ -13,26 +14,20 @@ import { TextInput } from '@/ui/input/components/TextInput'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useMemo, useState } from 'react'; -import { IconCalendar } from 'twenty-ui'; +import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui'; import { formatToHumanReadableDate } from '~/utils/date-utils'; import { getFileAbsoluteURI } from '~/utils/file/getFileAbsoluteURI'; - -const StyledRow = styled.div` - align-items: center; - align-self: stretch; - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - color: ${({ theme }) => theme.font.color.primary}; - display: flex; - justify-content: space-between; - padding: ${({ theme }) => theme.spacing(2)}; - height: 32px; -`; +import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension'; const StyledLeftContent = styled.div` align-items: center; display: flex; gap: ${({ theme }) => theme.spacing(3)}; + + width: 100%; + overflow: auto; + flex: 1; `; const StyledRightContent = styled.div` @@ -52,15 +47,28 @@ const StyledLink = styled.a` color: ${({ theme }) => theme.font.color.primary}; display: flex; text-decoration: none; + + width: 100%; + :hover { color: ${({ theme }) => theme.font.color.secondary}; } `; +const StyledLinkContainer = styled.div` + overflow: auto; + width: 100%; +`; + export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => { const theme = useTheme(); const [isEditing, setIsEditing] = useState(false); - const [attachmentName, setAttachmentName] = useState(attachment.name); + + const { name: originalFileName, extension: attachmentFileExtension } = + getFileNameAndExtension(attachment.name); + + const [attachmentFileName, setAttachmentFileName] = + useState(originalFileName); const fieldContext = useMemo( () => ({ recoilScopeId: attachment?.id ?? '' }), @@ -83,38 +91,60 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => { setIsEditing(true); }; - const handleOnBlur = () => { + const saveAttachmentName = () => { setIsEditing(false); + + const newFileName = `${attachmentFileName}${attachmentFileExtension}`; + updateOneAttachment({ idToUpdate: attachment.id, - updateOneRecordInput: { name: attachmentName }, + updateOneRecordInput: { name: newFileName }, }); }; - const handleOnChange = (newName: string) => { - setAttachmentName(newName); + const handleOnBlur = () => { + saveAttachmentName(); + }; + + const handleOnChange = (newFileName: string) => { + setAttachmentFileName(newFileName); + }; + + const handleOnKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + saveAttachmentName(); + } + }; + + const handleDownload = () => { + downloadFile( + attachment.fullPath, + `${attachmentFileName}${attachmentFileExtension}`, + ); }; return ( <FieldContext.Provider value={fieldContext as GenericFieldContextType}> - <StyledRow> + <ActivityRow disabled> <StyledLeftContent> <AttachmentIcon attachmentType={attachment.type} /> {isEditing ? ( <TextInput - value={attachmentName} + value={attachmentFileName} onChange={handleOnChange} onBlur={handleOnBlur} autoFocus - fullWidth + onKeyDown={handleOnKeyDown} /> ) : ( - <StyledLink - href={getFileAbsoluteURI(attachment.fullPath)} - target="__blank" - > - {attachment.name} - </StyledLink> + <StyledLinkContainer> + <StyledLink + href={getFileAbsoluteURI(attachment.fullPath)} + target="__blank" + > + <OverflowingTextWithTooltip text={attachment.name} /> + </StyledLink> + </StyledLinkContainer> )} </StyledLeftContent> <StyledRightContent> @@ -125,13 +155,11 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => { <AttachmentDropdown scopeKey={attachment.id} onDelete={handleDelete} - onDownload={() => { - downloadFile(attachment.fullPath, attachment.name); - }} + onDownload={handleDownload} onRename={handleRename} /> </StyledRightContent> - </StyledRow> + </ActivityRow> </FieldContext.Provider> ); }; 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 aa8f2461208d..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 @@ -7,11 +7,10 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; -const mockObjectMetadataItems = getObjectMetadataItemsMock(); const cache = new InMemoryCache(); @@ -141,7 +140,7 @@ describe('useActivityTargetObjectRecords', () => { act(() => { result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]); - result.current.setObjectMetadataItems(mockObjectMetadataItems); + result.current.setObjectMetadataItems(generatedMockObjectMetadataItems); }); const activityTargetObjectRecords = 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 e2db015a1a4a..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 - status + assignee { + __typename + id + name { + firstName + lastName + } + } assigneeId - updatedAt + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } body createdAt dueAt id + status title + updatedAt } } `, @@ -56,15 +84,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - {children} - </SnackBarProviderScope> - </MockedProvider> - </RecoilRoot> -); +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 9b03ac5e5b61..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,15 +1,15 @@ -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'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import gql from 'graphql-tag'; import pick from 'lodash.pick'; +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,15 +61,11 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <MockedProvider addTypename={false} mocks={mocks}> - {children} - </MockedProvider> - </RecoilRoot> -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); -const mockObjectMetadataItems = getObjectMetadataItemsMock(); +const mockObjectMetadataItems = generatedMockObjectMetadataItems; describe('useOpenCreateActivityDrawer', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index 400c1f398a95..1cc4af08a81e 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; }) => { + setIsNewViewableRecordLoading(true); + openRightDrawer(RightDrawerPages.ViewRecord); + 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<typeof TaskList> = { 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/ObjectTasks.tsx b/packages/twenty-front/src/modules/activities/tasks/components/ObjectTasks.tsx index a798a818d865..9e2dd7a4bc54 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/ObjectTasks.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/ObjectTasks.tsx @@ -20,7 +20,7 @@ export const ObjectTasks = ({ return ( <StyledContainer> <ObjectFilterDropdownScope filterScopeId="entity-tasks-filter-scope"> - <TaskGroups targetableObjects={[targetableObject]} showAddButton /> + <TaskGroups targetableObjects={[targetableObject]} /> </ObjectFilterDropdownScope> </StyledContainer> ); 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 a6e4999773bc..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,18 +27,15 @@ import { TaskList } from './TaskList'; const StyledContainer = styled.div` display: flex; flex-direction: column; + width: 100%; `; type TaskGroupsProps = { filterDropdownId?: string; targetableObjects?: ActivityTargetableObject[]; - showAddButton?: boolean; }; -export const TaskGroups = ({ - targetableObjects, - showAddButton, -}: TaskGroupsProps) => { +export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => { const { tasks, tasksLoading } = useTasks({ targetableObjects: targetableObjects ?? [], }); @@ -93,7 +90,11 @@ export const TaskGroups = ({ const sortedTasksByStatus = Object.entries( groupBy(tasks, ({ status }) => status), - ).toSorted(([statusA], [statusB]) => statusB.localeCompare(statusA)); + ).sort(([statusA], [statusB]) => statusB.localeCompare(statusA)); + + const hasTodoStatus = sortedTasksByStatus.some( + ([status]) => status === 'TODO', + ); return ( <StyledContainer> @@ -103,7 +104,7 @@ export const TaskGroups = ({ title={status} tasks={tasksByStatus} button={ - showAddButton && ( + (status === 'TODO' || !hasTodoStatus) && ( <AddTaskButton activityTargetableObjects={targetableObjects} /> ) } 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..56082db0dc4c 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; import { ReactElement } from 'react'; +import { ActivityList } from '@/activities/components/ActivityList'; import { Task } from '@/activities/types/Task'; import { TaskRow } from './TaskRow'; @@ -12,11 +13,14 @@ type TaskListProps = { const StyledContainer = styled.div` align-items: flex-start; + width: 100%; align-self: stretch; display: flex; flex-direction: column; justify-content: center; - padding: 8px 24px; + padding: 8px ${({ theme }) => theme.spacing(6)}; + + width: calc(100% - ${({ theme }) => theme.spacing(12)}); `; const StyledTitleBar = styled.div` @@ -38,13 +42,6 @@ const StyledCount = styled.span` margin-left: ${({ theme }) => theme.spacing(2)}; `; -const StyledTaskRows = styled.div` - background-color: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.light}; - border-radius: ${({ theme }) => theme.border.radius.md}; - width: 100%; -`; - export const TaskList = ({ title, tasks, button }: TaskListProps) => ( <> {tasks && tasks.length > 0 && ( @@ -57,11 +54,11 @@ export const TaskList = ({ title, tasks, button }: TaskListProps) => ( )} {button} </StyledTitleBar> - <StyledTaskRows> + <ActivityList> {tasks.map((task) => ( <TaskRow key={task.id} task={task} /> ))} - </StyledTaskRows> + </ActivityList> </StyledContainer> )} </> diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx index efd4323143b0..ad46a8a43b15 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx @@ -8,28 +8,12 @@ import { getActivitySummary } from '@/activities/utils/getActivitySummary'; import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox'; import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils'; +import { ActivityRow } from '@/activities/components/ActivityRow'; import { Task } from '@/activities/types/Task'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFieldContext } from '@/object-record/hooks/useFieldContext'; import { useCompleteTask } from '../hooks/useCompleteTask'; -const StyledContainer = styled.div` - align-items: center; - justify-content: space-between; - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - cursor: pointer; - display: flex; - height: ${({ theme }) => theme.spacing(12)}; - min-width: calc(100% - ${({ theme }) => theme.spacing(8)}); - max-width: calc(100% - ${({ theme }) => theme.spacing(8)}); - padding: 0 ${({ theme }) => theme.spacing(4)}; - overflow: hidden; - max-inline-size: 60px; - &:last-child { - border-bottom: 0; - } -`; - const StyledTaskBody = styled.div` color: ${({ theme }) => theme.font.color.tertiary}; display: flex; @@ -105,7 +89,7 @@ export const TaskRow = ({ task }: { task: Task }) => { }); return ( - <StyledContainer + <ActivityRow onClick={() => { openActivityRightDrawer(task.id); }} @@ -130,6 +114,14 @@ export const TaskRow = ({ task }: { task: Task }) => { </StyledTaskBody> </StyledLeftSideContainer> <StyledRightSideContainer> + {task.dueAt && ( + <StyledDueDate + isPast={hasDatePassed(task.dueAt) && task.status === 'TODO'} + > + <IconCalendar size={theme.icon.size.md} /> + {beautifyExactDate(task.dueAt)} + </StyledDueDate> + )} {TaskTargetsContextProvider && ( <TaskTargetsContextProvider> <ActivityTargetsInlineCell @@ -141,15 +133,7 @@ export const TaskRow = ({ task }: { task: Task }) => { /> </TaskTargetsContextProvider> )} - <StyledDueDate - isPast={ - !!task.dueAt && hasDatePassed(task.dueAt) && task.status === 'TODO' - } - > - <IconCalendar size={theme.icon.size.md} /> - {task.dueAt && beautifyExactDate(task.dueAt)} - </StyledDueDate> </StyledRightSideContainer> - </StyledContainer> + </ActivityRow> ); }; 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 baf35a95680e..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,20 +27,123 @@ const mocks: MockedResponse[] = [ mutation UpdateOneTask($idToUpdate: ID!, $input: TaskUpdateInput!) { updateTask(id: $idToUpdate, data: $input) { __typename - status + assignee { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } assigneeId - updatedAt + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } body createdAt - dueAt - position - id - title createdBy { source workspaceMemberId name } + 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 } } `, @@ -53,7 +155,7 @@ const mocks: MockedResponse[] = [ result: jest.fn(() => ({ data: { updateTask: { - __typename: 'Activity', + __typename: 'Task', createdAt: '2024-03-15T07:33:14.212Z', reminderAt: null, authorId: '123', @@ -71,13 +173,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +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 }) => ( - <Wrappers>{children}</Wrappers> -); - 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..fe0b549d68da 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<typeof EventRowMainObjectUpdated> = { title: 'Modules/TimelineActivities/Rows/MainObject/EventRowMainObjectUpdated', @@ -35,7 +35,9 @@ const meta: Meta<typeof EventRowMainObjectUpdated> = { }, }, } as TimelineActivity, - mainObjectMetadataItem: mockedPersonObjectMetadataItem, + mainObjectMetadataItem: generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ), }, decorators: [ ComponentDecorator, diff --git a/packages/twenty-front/src/modules/analytics/graphql/queries/track.ts b/packages/twenty-front/src/modules/analytics/graphql/queries/track.ts index 3aa7bba0fcf4..04a5d4487293 100644 --- a/packages/twenty-front/src/modules/analytics/graphql/queries/track.ts +++ b/packages/twenty-front/src/modules/analytics/graphql/queries/track.ts @@ -1,8 +1,8 @@ import { gql } from '@apollo/client'; export const TRACK = gql` - mutation Track($type: String!, $data: JSON!) { - track(type: $type, data: $data) { + mutation Track($type: String!, $sessionId: String!, $data: JSON!) { + track(type: $type, sessionId: $sessionId, data: $data) { success } } diff --git a/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx b/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx index 9b60f1ffa646..a49b85612f46 100644 --- a/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx +++ b/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx @@ -1,8 +1,8 @@ -import { ReactNode } from 'react'; import { gql } from '@apollo/client'; import { MockedProvider, MockedResponse } from '@apollo/client/testing'; import { expect } from '@storybook/test'; import { act, renderHook, waitFor } from '@testing-library/react'; +import { ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; import { useEventTracker } from '../useEventTracker'; @@ -11,15 +11,23 @@ const mocks: MockedResponse[] = [ { request: { query: gql` - mutation Track($type: String!, $data: JSON!) { - track(type: $type, data: $data) { + mutation Track($action: String!, $payload: JSON!) { + track(action: $action, payload: $payload) { success } } `, variables: { - type: 'exampleType', - data: { location: { pathname: '/examplePath' } }, + action: 'exampleType', + payload: { + sessionId: 'exampleId', + pathname: '', + userAgent: '', + timeZone: '', + locale: '', + href: '', + referrer: '', + }, }, }, result: jest.fn(() => ({ @@ -43,7 +51,15 @@ const Wrapper = ({ children }: { children: ReactNode }) => ( describe('useEventTracker', () => { it('should make the call to track the event', async () => { const eventType = 'exampleType'; - const eventData = { location: { pathname: '/examplePath' } }; + const eventData = { + sessionId: 'exampleId', + pathname: '', + userAgent: '', + timeZone: '', + locale: '', + href: '', + referrer: '', + }; const { result } = renderHook(() => useEventTracker(), { wrapper: Wrapper, }); diff --git a/packages/twenty-front/src/modules/analytics/hooks/useEventTracker.ts b/packages/twenty-front/src/modules/analytics/hooks/useEventTracker.ts index 88d1d656740b..faaa3ea5b634 100644 --- a/packages/twenty-front/src/modules/analytics/hooks/useEventTracker.ts +++ b/packages/twenty-front/src/modules/analytics/hooks/useEventTracker.ts @@ -1,32 +1,46 @@ import { useCallback } from 'react'; -import { useRecoilValue } from 'recoil'; - -import { telemetryState } from '@/client-config/states/telemetryState'; import { useTrackMutation } from '~/generated/graphql'; - -interface EventLocation { +export interface EventData { pathname: string; + userAgent: string; + timeZone: string; + locale: string; + href: string; + referrer: string; } +export const ANALYTICS_COOKIE_NAME = 'analyticsCookie'; +export const getSessionId = (): string => { + const cookie: { [key: string]: string } = {}; + document.cookie.split(';').forEach((el) => { + const [key, value] = el.split('='); + cookie[key.trim()] = value; + }); + return cookie[ANALYTICS_COOKIE_NAME]; +}; -export interface EventData { - location: EventLocation; -} +export const setSessionId = (domain?: string): void => { + const sessionId = getSessionId() || crypto.randomUUID(); + const baseCookie = `${ANALYTICS_COOKIE_NAME}=${sessionId}; Max-Age=1800; path=/; secure`; + const cookie = domain ? baseCookie + `; domain=${domain}` : baseCookie; + + document.cookie = cookie; +}; export const useEventTracker = () => { - const telemetry = useRecoilValue(telemetryState); const [createEventMutation] = useTrackMutation(); return useCallback( - (eventType: string, eventData: EventData) => { - if (telemetry.enabled) { - createEventMutation({ - variables: { - type: eventType, - data: eventData, + (eventAction: string, eventPayload: EventData) => { + createEventMutation({ + variables: { + action: eventAction, + payload: { + sessionId: getSessionId(), + ...eventPayload, }, - }); - } + }, + }); }, - [createEventMutation, telemetry], + [createEventMutation], ); }; diff --git a/packages/twenty-front/src/modules/apollo/hooks/__tests__/useApolloFactory.test.tsx b/packages/twenty-front/src/modules/apollo/hooks/__tests__/useApolloFactory.test.tsx index 59f99306a524..5e19a8309c64 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/__tests__/useApolloFactory.test.tsx +++ b/packages/twenty-front/src/modules/apollo/hooks/__tests__/useApolloFactory.test.tsx @@ -1,7 +1,7 @@ -import { MemoryRouter, useLocation } from 'react-router-dom'; import { ApolloError, gql } from '@apollo/client'; import { act, renderHook } from '@testing-library/react'; import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'; +import { MemoryRouter, useLocation } from 'react-router-dom'; import { RecoilRoot } from 'recoil'; import { useApolloFactory } from '../useApolloFactory'; @@ -77,8 +77,8 @@ describe('useApolloFactory', () => { await act(async () => { await result.current.factory.mutate({ mutation: gql` - mutation Track($type: String!, $data: JSON!) { - track(type: $type, data: $data) { + mutation Track($type: String!, $sessionId: String!, $data: JSON!) { + track(type: $type, sessionId: $sessionId, data: $data) { success } } diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts similarity index 68% rename from packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts rename to packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts index 1a3a1aa8903b..8ed05ecd020a 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts @@ -1,30 +1,28 @@ import { ApolloCache, StoreObject } from '@apollo/client'; import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; -import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; import { isDefined } from '~/utils/isDefined'; -import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; -export const triggerDeleteRecordsOptimisticEffect = ({ +export const triggerDestroyRecordsOptimisticEffect = ({ cache, objectMetadataItem, - recordsToDelete, + recordsToDestroy, objectMetadataItems, }: { cache: ApolloCache<unknown>; objectMetadataItem: ObjectMetadataItem; - recordsToDelete: RecordGqlNode[]; + recordsToDestroy: RecordGqlNode[]; objectMetadataItems: ObjectMetadataItem[]; }) => { cache.modify<StoreObject>({ fields: { [objectMetadataItem.namePlural]: ( rootQueryCachedResponse, - { DELETE, readField, storeFieldName }, + { readField }, ) => { const rootQueryCachedResponseIsNotACachedObjectRecordConnection = !isObjectRecordConnectionWithRefs( @@ -38,13 +36,7 @@ export const triggerDeleteRecordsOptimisticEffect = ({ const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse; - const { fieldVariables: rootQueryVariables } = - parseApolloStoreFieldName<CachedObjectRecordQueryVariables>( - storeFieldName, - ); - - const recordIdsToDelete = recordsToDelete.map(({ id }) => id); - + const recordIdsToDestroy = recordsToDestroy.map(({ id }) => id); const cachedEdges = readField<RecordGqlRefEdge[]>( 'edges', rootQueryCachedObjectRecordConnection, @@ -59,40 +51,32 @@ export const triggerDeleteRecordsOptimisticEffect = ({ cachedEdges?.filter((cachedEdge) => { const nodeId = readField<string>('id', cachedEdge.node); - return nodeId && !recordIdsToDelete.includes(nodeId); + return nodeId && !recordIdsToDestroy.includes(nodeId); }) || []; if (nextCachedEdges.length === cachedEdges?.length) return rootQueryCachedObjectRecordConnection; - // TODO: same as in update, should we trigger DELETE ? - if ( - isDefined(rootQueryVariables?.first) && - cachedEdges?.length === rootQueryVariables.first - ) { - return DELETE; - } - return { ...rootQueryCachedObjectRecordConnection, edges: nextCachedEdges, totalCount: isDefined(totalCount) - ? totalCount - recordIdsToDelete.length + ? totalCount - recordIdsToDestroy.length : undefined, }; }, }, }); - recordsToDelete.forEach((recordToDelete) => { + recordsToDestroy.forEach((recordToDestroy) => { triggerUpdateRelationsOptimisticEffect({ cache, sourceObjectMetadataItem: objectMetadataItem, - currentSourceRecord: recordToDelete, + currentSourceRecord: recordToDestroy, updatedSourceRecord: null, objectMetadataItems, }); - cache.evict({ id: cache.identify(recordToDelete) }); + cache.evict({ id: cache.identify(recordToDestroy) }); }); }; diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts index 3c09c213f68b..15878796f5bd 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts @@ -65,50 +65,45 @@ export const triggerUpdateRecordOptimisticEffect = ({ const rootQueryFilter = rootQueryVariables?.filter; const rootQueryOrderBy = rootQueryVariables?.orderBy; - const shouldTryToMatchFilter = isDefined(rootQueryFilter); - - if (shouldTryToMatchFilter) { - const updatedRecordMatchesThisRootQueryFilter = - isRecordMatchingFilter({ - record: updatedRecord, - filter: rootQueryFilter, - objectMetadataItem, - }); + const updatedRecordMatchesThisRootQueryFilter = isRecordMatchingFilter({ + record: updatedRecord, + filter: rootQueryFilter ?? {}, + objectMetadataItem, + }); + + const updatedRecordIndexInRootQueryEdges = + rootQueryCurrentEdges.findIndex( + (cachedEdge) => + readField('id', cachedEdge.node) === updatedRecord.id, + ); - const updatedRecordIndexInRootQueryEdges = - rootQueryCurrentEdges.findIndex( - (cachedEdge) => - readField('id', cachedEdge.node) === updatedRecord.id, - ); - - const updatedRecordFoundInRootQueryEdges = - updatedRecordIndexInRootQueryEdges > -1; - - const updatedRecordShouldBeAddedToRootQueryEdges = - updatedRecordMatchesThisRootQueryFilter && - !updatedRecordFoundInRootQueryEdges; - - const updatedRecordShouldBeRemovedFromRootQueryEdges = - !updatedRecordMatchesThisRootQueryFilter && - updatedRecordFoundInRootQueryEdges; - - if (updatedRecordShouldBeAddedToRootQueryEdges) { - const updatedRecordNodeReference = toReference(updatedRecord); - - if (isDefined(updatedRecordNodeReference)) { - rootQueryNextEdges.push({ - __typename: getEdgeTypename(objectMetadataItem.nameSingular), - node: updatedRecordNodeReference, - cursor: '', - }); - } - } + const updatedRecordFoundInRootQueryEdges = + updatedRecordIndexInRootQueryEdges > -1; + + const updatedRecordShouldBeAddedToRootQueryEdges = + updatedRecordMatchesThisRootQueryFilter && + !updatedRecordFoundInRootQueryEdges; + + const updatedRecordShouldBeRemovedFromRootQueryEdges = + !updatedRecordMatchesThisRootQueryFilter && + updatedRecordFoundInRootQueryEdges; - if (updatedRecordShouldBeRemovedFromRootQueryEdges) { - rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1); + if (updatedRecordShouldBeAddedToRootQueryEdges) { + const updatedRecordNodeReference = toReference(updatedRecord); + + if (isDefined(updatedRecordNodeReference)) { + rootQueryNextEdges.push({ + __typename: getEdgeTypename(objectMetadataItem.nameSingular), + node: updatedRecordNodeReference, + cursor: '', + }); } } + if (updatedRecordShouldBeRemovedFromRootQueryEdges) { + rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1); + } + const rootQueryNextEdgesShouldBeSorted = isDefined(rootQueryOrderBy); if ( diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts index 535f33db4f8a..4e44e098adcb 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -1,7 +1,7 @@ import { ApolloCache } from '@apollo/client'; import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect'; import { CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -122,10 +122,10 @@ export const triggerUpdateRelationsOptimisticEffect = ({ ); if (shouldCascadeDeleteTargetRecords) { - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem: fullTargetObjectMetadataItem, - recordsToDelete: targetRecordsToDetachFrom, + recordsToDestroy: targetRecordsToDetachFrom, objectMetadataItems, }); } else { diff --git a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts index d0ba37512438..9136b83fcd04 100644 --- a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts +++ b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts @@ -41,8 +41,8 @@ const makeRequest = async () => { await client.mutate({ mutation: gql` - mutation Track($type: String!, $data: JSON!) { - track(type: $type, data: $data) { + mutation Track($type: String!, $sessionId: String!, $data: JSON!) { + track(type: $type, sessionId: $sessionId, data: $data) { success } } diff --git a/packages/twenty-front/src/modules/app/components/App.tsx b/packages/twenty-front/src/modules/app/components/App.tsx new file mode 100644 index 000000000000..f760ee9f6fb4 --- /dev/null +++ b/packages/twenty-front/src/modules/app/components/App.tsx @@ -0,0 +1,32 @@ +import { AppRouter } from '@/app/components/AppRouter'; +import { CaptchaProvider } from '@/captcha/components/CaptchaProvider'; +import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect'; +import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; +import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; +import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider'; +import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { HelmetProvider } from 'react-helmet-async'; +import { RecoilRoot } from 'recoil'; +import { IconsProvider } from 'twenty-ui'; + +export const App = () => { + return ( + <RecoilRoot> + <AppErrorBoundary> + <CaptchaProvider> + <RecoilDebugObserverEffect /> + <ApolloDevLogEffect /> + <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> + <IconsProvider> + <ExceptionHandlerProvider> + <HelmetProvider> + <AppRouter /> + </HelmetProvider> + </ExceptionHandlerProvider> + </IconsProvider> + </SnackBarProviderScope> + </CaptchaProvider> + </AppErrorBoundary> + </RecoilRoot> + ); +}; diff --git a/packages/twenty-front/src/modules/app/components/AppRouter.tsx b/packages/twenty-front/src/modules/app/components/AppRouter.tsx new file mode 100644 index 000000000000..d8985e676332 --- /dev/null +++ b/packages/twenty-front/src/modules/app/components/AppRouter.tsx @@ -0,0 +1,27 @@ +import { createAppRouter } from '@/app/utils/createAppRouter'; +import { billingState } from '@/client-config/states/billingState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { RouterProvider } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; + +export const AppRouter = () => { + const billing = useRecoilValue(billingState); + const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED'); + const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED'); + const isServerlessFunctionSettingsEnabled = useIsFeatureEnabled( + 'IS_FUNCTION_SETTINGS_ENABLED', + ); + + const isBillingPageEnabled = + billing?.isBillingEnabled && !isFreeAccessEnabled; + + return ( + <RouterProvider + router={createAppRouter( + isBillingPageEnabled, + isCRMMigrationEnabled, + isServerlessFunctionSettingsEnabled, + )} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx new file mode 100644 index 000000000000..e5a24da4057a --- /dev/null +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -0,0 +1,66 @@ +import { ApolloProvider } from '@/apollo/components/ApolloProvider'; +import { CommandMenuEffect } from '@/app/effect-components/CommandMenuEffect'; +import { GotoHotkeys } from '@/app/effect-components/GotoHotkeysEffect'; +import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect'; +import { AuthProvider } from '@/auth/components/AuthProvider'; +import { ChromeExtensionSidecarEffect } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect'; +import { ChromeExtensionSidecarProvider } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider'; +import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; +import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; +import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect'; +import { ApolloMetadataClientProvider } from '@/object-metadata/components/ApolloMetadataClientProvider'; +import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; +import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; +import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager'; +import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope'; +import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider'; +import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider'; +import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; +import { UserProvider } from '@/users/components/UserProvider'; +import { UserProviderEffect } from '@/users/components/UserProviderEffect'; +import { StrictMode } from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; +import { getPageTitleFromPath } from '~/utils/title-utils'; + +export const AppRouterProviders = () => { + const { pathname } = useLocation(); + const pageTitle = getPageTitleFromPath(pathname); + + return ( + <ApolloProvider> + <ClientConfigProviderEffect /> + <ClientConfigProvider> + <ChromeExtensionSidecarEffect /> + <ChromeExtensionSidecarProvider> + <UserProviderEffect /> + <UserProvider> + <AuthProvider> + <ApolloMetadataClientProvider> + <ObjectMetadataItemsProvider> + <PrefetchDataProvider> + <AppThemeProvider> + <SnackBarProvider> + <DialogManagerScope dialogManagerScopeId="dialog-manager"> + <DialogManager> + <StrictMode> + <PromiseRejectionEffect /> + <CommandMenuEffect /> + <GotoHotkeys /> + <PageTitle title={pageTitle} /> + <Outlet /> + </StrictMode> + </DialogManager> + </DialogManagerScope> + </SnackBarProvider> + </AppThemeProvider> + </PrefetchDataProvider> + <PageChangeEffect /> + </ObjectMetadataItemsProvider> + </ApolloMetadataClientProvider> + </AuthProvider> + </UserProvider> + </ChromeExtensionSidecarProvider> + </ClientConfigProvider> + </ApolloProvider> + ); +}; diff --git a/packages/twenty-front/src/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx similarity index 92% rename from packages/twenty-front/src/SettingsRoutes.tsx rename to packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 05e852e2bed0..5eede03959b5 100644 --- a/packages/twenty-front/src/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -1,6 +1,7 @@ import { lazy, Suspense } from 'react'; import { Route, Routes } from 'react-router-dom'; +import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; import { AppPath } from '@/types/AppPath'; import { SettingsPath } from '@/types/SettingsPath'; @@ -64,7 +65,7 @@ const SettingsDevelopersApiKeysNew = lazy(() => const SettingsDevelopersWebhooksNew = lazy(() => import( - '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew' + '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew' ).then((module) => ({ default: module.SettingsDevelopersWebhooksNew, })), @@ -164,7 +165,7 @@ const SettingsObjects = lazy(() => const SettingsDevelopersWebhooksDetail = lazy(() => import( - '~/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail' + '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail' ).then((module) => ({ default: module.SettingsDevelopersWebhooksDetail, })), @@ -202,22 +203,21 @@ const SettingsIntegrationShowDatabaseConnection = lazy(() => })), ); -const SettingsObjectNewFieldStep1 = lazy(() => +const SettingsObjectNewFieldSelect = lazy(() => import( - '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1' + '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect' ).then((module) => ({ - default: module.SettingsObjectNewFieldStep1, + default: module.SettingsObjectNewFieldSelect, })), ); -const SettingsObjectNewFieldStep2 = lazy(() => +const SettingsObjectNewFieldConfigure = lazy(() => import( - '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2' + '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure' ).then((module) => ({ - default: module.SettingsObjectNewFieldStep2, + default: module.SettingsObjectNewFieldConfigure, })), ); - const SettingsObjectFieldEdit = lazy(() => import('~/pages/settings/data-model/SettingsObjectFieldEdit').then( (module) => ({ @@ -245,7 +245,7 @@ export const SettingsRoutes = ({ isCRMMigrationEnabled, isServerlessFunctionSettingsEnabled, }: SettingsRoutesProps) => ( - <Suspense fallback={null}> + <Suspense fallback={<SettingsSkeletonLoader />}> <Routes> <Route path={SettingsPath.ProfilePage} element={<SettingsProfile />} /> <Route path={SettingsPath.Appearance} element={<SettingsAppearance />} /> @@ -345,12 +345,12 @@ export const SettingsRoutes = ({ element={<SettingsIntegrationShowDatabaseConnection />} /> <Route - path={SettingsPath.ObjectNewFieldStep1} - element={<SettingsObjectNewFieldStep1 />} + path={SettingsPath.ObjectNewFieldSelect} + element={<SettingsObjectNewFieldSelect />} /> <Route - path={SettingsPath.ObjectNewFieldStep2} - element={<SettingsObjectNewFieldStep2 />} + path={SettingsPath.ObjectNewFieldConfigure} + element={<SettingsObjectNewFieldConfigure />} /> <Route path={SettingsPath.ObjectFieldEdit} diff --git a/packages/twenty-front/src/effect-components/CommandMenuEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/CommandMenuEffect.tsx similarity index 89% rename from packages/twenty-front/src/effect-components/CommandMenuEffect.tsx rename to packages/twenty-front/src/modules/app/effect-components/CommandMenuEffect.tsx index ece319312e91..b210ae724276 100644 --- a/packages/twenty-front/src/effect-components/CommandMenuEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/CommandMenuEffect.tsx @@ -7,7 +7,7 @@ import { commandMenuCommandsState } from '@/command-menu/states/commandMenuComma export const CommandMenuEffect = () => { const setCommands = useSetRecoilState(commandMenuCommandsState); - const commands = COMMAND_MENU_COMMANDS; + const commands = Object.values(COMMAND_MENU_COMMANDS); useEffect(() => { setCommands(commands); }, [commands, setCommands]); diff --git a/packages/twenty-front/src/modules/app/effect-components/GoToHotkeyItemEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/GoToHotkeyItemEffect.tsx new file mode 100644 index 000000000000..a0b545302501 --- /dev/null +++ b/packages/twenty-front/src/modules/app/effect-components/GoToHotkeyItemEffect.tsx @@ -0,0 +1,12 @@ +import { useGoToHotkeys } from '@/ui/utilities/hotkey/hooks/useGoToHotkeys'; + +export const GoToHotkeyItemEffect = (props: { + hotkey: string; + pathToNavigateTo: string; +}) => { + const { hotkey, pathToNavigateTo } = props; + + useGoToHotkeys(hotkey, pathToNavigateTo); + + return <></>; +}; diff --git a/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx new file mode 100644 index 000000000000..15d371f9f44a --- /dev/null +++ b/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx @@ -0,0 +1,18 @@ +import { GoToHotkeyItemEffect } from '@/app/effect-components/GoToHotkeyItemEffect'; +import { useNonSystemActiveObjectMetadataItems } from '@/object-metadata/hooks/useNonSystemActiveObjectMetadataItems'; +import { useGoToHotkeys } from '@/ui/utilities/hotkey/hooks/useGoToHotkeys'; + +export const GotoHotkeys = () => { + const { nonSystemActiveObjectMetadataItems } = + useNonSystemActiveObjectMetadataItems(); + + // Hardcoded since settings is static + useGoToHotkeys('s', '/settings/profile'); + + return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => ( + <GoToHotkeyItemEffect + hotkey={objectMetadataItem.namePlural[0]} + pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`} + /> + )); +}; diff --git a/packages/twenty-front/src/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx similarity index 84% rename from packages/twenty-front/src/effect-components/PageChangeEffect.tsx rename to packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index bf083a157e5a..a8b05f4c0904 100644 --- a/packages/twenty-front/src/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -4,11 +4,16 @@ import { useRecoilValue } from 'recoil'; import { IconCheckbox } from 'twenty-ui'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; -import { useEventTracker } from '@/analytics/hooks/useEventTracker'; +import { + setSessionId, + useEventTracker, +} from '@/analytics/hooks/useEventTracker'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { CommandType } from '@/command-menu/types/Command'; +import { useNonSystemActiveObjectMetadataItems } from '@/object-metadata/hooks/useNonSystemActiveObjectMetadataItems'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { AppBasePath } from '@/types/AppBasePath'; @@ -40,7 +45,9 @@ export const PageChangeEffect = () => { const eventTracker = useEventTracker(); - const { addToCommandMenu, setToInitialCommandMenu } = useCommandMenu(); + const { addToCommandMenu, setObjectsInCommandMenu } = useCommandMenu(); + + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const openCreateActivity = useOpenCreateActivityDrawer({ activityObjectNameSingular: CoreObjectNameSingular.Task, @@ -143,8 +150,11 @@ export const PageChangeEffect = () => { } }, [isMatchingLocation, setHotkeyScope]); + const { nonSystemActiveObjectMetadataItems } = + useNonSystemActiveObjectMetadataItems(); + useEffect(() => { - setToInitialCommandMenu(); + setObjectsInCommandMenu(nonSystemActiveObjectMetadataItems); addToCommandMenu([ { @@ -159,14 +169,24 @@ export const PageChangeEffect = () => { }), }, ]); - }, [addToCommandMenu, setToInitialCommandMenu, openCreateActivity]); + }, [ + nonSystemActiveObjectMetadataItems, + addToCommandMenu, + setObjectsInCommandMenu, + openCreateActivity, + objectMetadataItems, + ]); useEffect(() => { setTimeout(() => { + setSessionId(); eventTracker('pageview', { - location: { - pathname: location.pathname, - }, + pathname: location.pathname, + locale: navigator.language, + userAgent: window.navigator.userAgent, + href: window.location.href, + referrer: document.referrer, + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }); }, 500); }, [eventTracker, location.pathname]); diff --git a/packages/twenty-front/src/modules/app/utils/createAppRouter.tsx b/packages/twenty-front/src/modules/app/utils/createAppRouter.tsx new file mode 100644 index 000000000000..0ddb70ac1a34 --- /dev/null +++ b/packages/twenty-front/src/modules/app/utils/createAppRouter.tsx @@ -0,0 +1,78 @@ +import { AppRouterProviders } from '@/app/components/AppRouterProviders'; +import { SettingsRoutes } from '@/app/components/SettingsRoutes'; +import { VerifyEffect } from '@/auth/components/VerifyEffect'; +import indexAppPath from '@/navigation/utils/indexAppPath'; +import { AppPath } from '@/types/AppPath'; +import { BlankLayout } from '@/ui/layout/page/BlankLayout'; +import { DefaultLayout } from '@/ui/layout/page/DefaultLayout'; +import { + createBrowserRouter, + createRoutesFromElements, + Route, +} from 'react-router-dom'; +import { Authorize } from '~/pages/auth/Authorize'; +import { Invite } from '~/pages/auth/Invite'; +import { PasswordReset } from '~/pages/auth/PasswordReset'; +import { SignInUp } from '~/pages/auth/SignInUp'; +import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect'; +import { NotFound } from '~/pages/not-found/NotFound'; +import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage'; +import { RecordShowPage } from '~/pages/object-record/RecordShowPage'; +import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan'; +import { CreateProfile } from '~/pages/onboarding/CreateProfile'; +import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace'; +import { InviteTeam } from '~/pages/onboarding/InviteTeam'; +import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess'; +import { SyncEmails } from '~/pages/onboarding/SyncEmails'; + +export const createAppRouter = ( + isBillingEnabled?: boolean, + isCRMMigrationEnabled?: boolean, + isServerlessFunctionSettingsEnabled?: boolean, +) => + createBrowserRouter( + createRoutesFromElements( + <Route + element={<AppRouterProviders />} + // To switch state to `loading` temporarily to enable us + // to set scroll position before the page is rendered + loader={async () => Promise.resolve(null)} + > + <Route element={<DefaultLayout />}> + <Route path={AppPath.Verify} element={<VerifyEffect />} /> + <Route path={AppPath.SignInUp} element={<SignInUp />} /> + <Route path={AppPath.Invite} element={<Invite />} /> + <Route path={AppPath.ResetPassword} element={<PasswordReset />} /> + <Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} /> + <Route path={AppPath.CreateProfile} element={<CreateProfile />} /> + <Route path={AppPath.SyncEmails} element={<SyncEmails />} /> + <Route path={AppPath.InviteTeam} element={<InviteTeam />} /> + <Route path={AppPath.PlanRequired} element={<ChooseYourPlan />} /> + <Route + path={AppPath.PlanRequiredSuccess} + element={<PaymentSuccess />} + /> + <Route path={indexAppPath.getIndexAppPath()} element={<></>} /> + <Route path={AppPath.Impersonate} element={<ImpersonateEffect />} /> + <Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} /> + <Route path={AppPath.RecordShowPage} element={<RecordShowPage />} /> + <Route + path={AppPath.SettingsCatchAll} + element={ + <SettingsRoutes + isBillingEnabled={isBillingEnabled} + isCRMMigrationEnabled={isCRMMigrationEnabled} + isServerlessFunctionSettingsEnabled={ + isServerlessFunctionSettingsEnabled + } + /> + } + /> + <Route path={AppPath.NotFoundWildcard} element={<NotFound />} /> + </Route> + <Route element={<BlankLayout />}> + <Route path={AppPath.Authorize} element={<Authorize />} /> + </Route> + </Route>, + ), + ); diff --git a/packages/twenty-front/src/modules/auth/hooks/__test__/useAuth.test.tsx b/packages/twenty-front/src/modules/auth/hooks/__test__/useAuth.test.tsx index ac52204f4cc4..60e4025a814f 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__test__/useAuth.test.tsx +++ b/packages/twenty-front/src/modules/auth/hooks/__test__/useAuth.test.tsx @@ -1,8 +1,8 @@ -import { ReactNode } from 'react'; import { useApolloClient } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; import { expect } from '@storybook/test'; import { act, renderHook } from '@testing-library/react'; +import { ReactNode } from 'react'; import { RecoilRoot, useRecoilValue } from 'recoil'; import { iconsState } from 'twenty-ui'; @@ -12,7 +12,6 @@ import { billingState } from '@/client-config/states/billingState'; import { isDebugModeState } from '@/client-config/states/isDebugModeState'; import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; import { supportChatState } from '@/client-config/states/supportChatState'; -import { telemetryState } from '@/client-config/states/telemetryState'; import { email, mocks, password, results, token } from '../__mocks__/useAuth'; @@ -81,7 +80,6 @@ describe('useAuth', () => { const billing = useRecoilValue(billingState); const isSignInPrefilled = useRecoilValue(isSignInPrefilledState); const supportChat = useRecoilValue(supportChatState); - const telemetry = useRecoilValue(telemetryState); const isDebugMode = useRecoilValue(isDebugModeState); return { ...useAuth(), @@ -92,7 +90,6 @@ describe('useAuth', () => { billing, isSignInPrefilled, supportChat, - telemetry, isDebugMode, }, }; @@ -126,9 +123,6 @@ describe('useAuth', () => { supportDriver: 'none', supportFrontChatId: null, }); - expect(state.telemetry).toEqual({ - enabled: true, - }); expect(state.isDebugMode).toBe(false); }); diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 677932161d78..7a7de0807f1b 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -21,7 +21,6 @@ import { isClientConfigLoadedState } from '@/client-config/states/isClientConfig import { isDebugModeState } from '@/client-config/states/isDebugModeState'; import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; import { supportChatState } from '@/client-config/states/supportChatState'; -import { telemetryState } from '@/client-config/states/telemetryState'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { @@ -224,7 +223,6 @@ export const useAuth = () => { .getLoadable(isSignInPrefilledState) .getValue(); const supportChat = snapshot.getLoadable(supportChatState).getValue(); - const telemetry = snapshot.getLoadable(telemetryState).getValue(); const isDebugMode = snapshot.getLoadable(isDebugModeState).getValue(); const captchaProvider = snapshot .getLoadable(captchaProviderState) @@ -242,7 +240,6 @@ export const useAuth = () => { set(billingState, billing); set(isSignInPrefilledState, isSignInPrefilled); set(supportChatState, supportChat); - set(telemetryState, telemetry); set(isDebugModeState, isDebugMode); set(captchaProviderState, captchaProvider); set(isClientConfigLoadedState, isClientConfigLoaded); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx index 2068e8f730b0..65021f7c6824 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx @@ -76,6 +76,10 @@ export const SignInUpForm = () => { if (signInUpStep === SignInUpStep.Init) { continueWithEmail(); } else if (signInUpStep === SignInUpStep.Email) { + if (isDefined(form?.formState?.errors?.email)) { + setShowErrors(true); + return; + } continueWithCredentials(); } else if (signInUpStep === SignInUpStep.Password) { if (!form.formState.isSubmitting) { @@ -238,6 +242,10 @@ export const SignInUpForm = () => { return; } if (signInUpStep === SignInUpStep.Email) { + if (isDefined(form?.formState?.errors?.email)) { + setShowErrors(true); + return; + } continueWithCredentials(); return; } 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 ( - <StyledContainer> - <img - src="/images/integrations/twenty-logo.svg" - alt="twenty-icon" - height={40} - width={40} - /> - <h3>{message}</h3> - </StyledContainer> - ); -}; +// const StyledContainer = styled.div` +// align-items: center; +// display: flex; +// flex-direction: column; +// height: 100vh; +// justify-content: center; +// `; + +// const AppInaccessible = ({ message }: { message: string }) => { +// return ( +// <StyledContainer> +// <img +// src="/images/integrations/twenty-logo.svg" +// alt="twenty-icon" +// height={40} +// width={40} +// /> +// <h3>{message}</h3> +// </StyledContainer> +// ); +// }; export const ChromeExtensionSidecarProvider: React.FC< React.PropsWithChildren > = ({ children }) => { - const isLoadingTokensFromExtension = useRecoilValue( - isLoadingTokensFromExtensionState, - ); - const chromeExtensionId = useRecoilValue(chromeExtensionIdState); - - if (!isInFrame()) return <>{children}</>; - - if (!isDefined(chromeExtensionId)) - return ( - <AppInaccessible message={`Twenty is not accessible inside an iframe.`} /> - ); - - if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) - return ( - <AppInaccessible - message={`Unauthorized access from iframe origin. If you're trying to access from chrome extension, - please check your chrome extension ID on your server. - `} - /> - ); - - return isLoadingTokensFromExtension && <>{children}</>; + return <>{children}</>; + + // TODO: this is conflictting with storybook tests + // if (!isInFrame()) return <>{children}</>; + + // if (!isDefined(chromeExtensionId)) + // return ( + // <AppInaccessible message={`Twenty is not accessible inside an iframe.`} /> + // ); + + // if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) + // return ( + // <AppInaccessible + // message={`Unauthorized access from iframe origin. If you're trying to access from chrome extension, + // please check your chrome extension ID on your server. + // `} + // /> + // ); + + // return isLoadingTokensFromExtension && <>{children}</>; }; diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx index 8bec4cc7dbd6..9eccbeb98e10 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -12,7 +12,6 @@ import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilled import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState'; import { sentryConfigState } from '@/client-config/states/sentryConfigState'; import { supportChatState } from '@/client-config/states/supportChatState'; -import { telemetryState } from '@/client-config/states/telemetryState'; import { useGetClientConfigQuery } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; @@ -24,7 +23,6 @@ export const ClientConfigProviderEffect = () => { const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState); const setBilling = useSetRecoilState(billingState); - const setTelemetry = useSetRecoilState(telemetryState); const setSupportChat = useSetRecoilState(supportChatState); const setSentryConfig = useSetRecoilState(sentryConfigState); @@ -56,7 +54,6 @@ export const ClientConfigProviderEffect = () => { setIsSignUpDisabled(data?.clientConfig.signUpDisabled); setBilling(data?.clientConfig.billing); - setTelemetry(data?.clientConfig.telemetry); setSupportChat(data?.clientConfig.support); setSentryConfig({ @@ -79,7 +76,6 @@ export const ClientConfigProviderEffect = () => { setIsDebugMode, setIsSignInPrefilled, setIsSignUpDisabled, - setTelemetry, setSupportChat, setBilling, setSentryConfig, diff --git a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts index 3143bbc5f65a..e702acefa4f1 100644 --- a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts @@ -16,9 +16,6 @@ export const GET_CLIENT_CONFIG = gql` signInPrefilled signUpDisabled debugMode - telemetry { - enabled - } support { supportDriver supportFrontChatId diff --git a/packages/twenty-front/src/modules/client-config/states/telemetryState.ts b/packages/twenty-front/src/modules/client-config/states/telemetryState.ts deleted file mode 100644 index f074ad218d5c..000000000000 --- a/packages/twenty-front/src/modules/client-config/states/telemetryState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createState } from 'twenty-ui'; - -import { Telemetry } from '~/generated/graphql'; - -export const telemetryState = createState<Telemetry>({ - key: 'telemetryState', - defaultValue: { enabled: true }, -}); diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx index 2b7d13f8c52b..25e5294e2729 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx @@ -1,5 +1,3 @@ -import styled from '@emotion/styled'; - import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer'; import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; @@ -16,7 +14,9 @@ import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeybo import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { useSearchRecords } from '@/object-record/hooks/useSearchRecords'; import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables'; +import { Opportunity } from '@/opportunities/Opportunity'; import { Person } from '@/people/types/Person'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; @@ -27,13 +27,14 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; import { useMemo, useRef } from 'react'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; import { Avatar, IconNotes, IconSparkles, IconX, isDefined } from 'twenty-ui'; +import { useDebounce } from 'use-debounce'; import { getLogoUrlFromDomainName } from '~/utils'; -import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; const SEARCH_BAR_HEIGHT = 56; const SEARCH_BAR_PADDING = 3; @@ -129,7 +130,6 @@ const StyledEmpty = styled.div` export const CommandMenu = () => { const { toggleCommandMenu, onItemClick, closeCommandMenu } = useCommandMenu(); const commandMenuRef = useRef<HTMLDivElement>(null); - const openActivityRightDrawer = useOpenActivityRightDrawer({ objectNameSingular: CoreObjectNameSingular.Note, }); @@ -137,9 +137,9 @@ export const CommandMenu = () => { const [commandMenuSearch, setCommandMenuSearch] = useRecoilState( commandMenuSearchState, ); + const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms const commandMenuCommands = useRecoilValue(commandMenuCommandsState); const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu(); - const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { setCommandMenuSearch(event.target.value); }; @@ -165,63 +165,45 @@ export const CommandMenu = () => { [closeCommandMenu], ); - const { records: people } = useFindManyRecords<Person>({ - skip: !isCommandMenuOpened, - objectNameSingular: CoreObjectNameSingular.Person, - filter: commandMenuSearch - ? makeOrFilterVariables([ - ...generateILikeFiltersForCompositeFields(commandMenuSearch, 'name', [ - 'firstName', - 'lastName', - ]), - ...generateILikeFiltersForCompositeFields( - commandMenuSearch, - 'emails', - ['primaryEmail'], - ), - { phone: { ilike: `%${commandMenuSearch}%` } }, - ]) - : undefined, - limit: 3, - }); - - const { records: companies } = useFindManyRecords<Company>({ - skip: !isCommandMenuOpened, - objectNameSingular: CoreObjectNameSingular.Company, - filter: commandMenuSearch - ? { - name: { ilike: `%${commandMenuSearch}%` }, - } - : undefined, - limit: 3, - }); - - const { records: notes } = useFindManyRecords<Note>({ + const { loading: isPeopleLoading, records: people } = + useSearchRecords<Person>({ + skip: !isCommandMenuOpened, + objectNameSingular: CoreObjectNameSingular.Person, + limit: 3, + searchInput: deferredCommandMenuSearch ?? undefined, + }); + + const { loading: isCompaniesLoading, records: companies } = + useSearchRecords<Company>({ + skip: !isCommandMenuOpened, + objectNameSingular: CoreObjectNameSingular.Company, + limit: 3, + searchInput: deferredCommandMenuSearch ?? undefined, + }); + + const { loading: isNotesLoading, records: notes } = useFindManyRecords<Note>({ skip: !isCommandMenuOpened, objectNameSingular: CoreObjectNameSingular.Note, - filter: commandMenuSearch + filter: deferredCommandMenuSearch ? makeOrFilterVariables([ - { title: { ilike: `%${commandMenuSearch}%` } }, - { body: { ilike: `%${commandMenuSearch}%` } }, + { title: { ilike: `%${deferredCommandMenuSearch}%` } }, + { body: { ilike: `%${deferredCommandMenuSearch}%` } }, ]) : undefined, limit: 3, }); - const { records: opportunities } = useFindManyRecords({ - skip: !isCommandMenuOpened, - objectNameSingular: CoreObjectNameSingular.Opportunity, - filter: commandMenuSearch - ? { - name: { ilike: `%${commandMenuSearch}%` }, - } - : undefined, - limit: 3, - }); + const { loading: isOpportunitiesLoading, records: opportunities } = + useSearchRecords<Opportunity>({ + skip: !isCommandMenuOpened, + objectNameSingular: CoreObjectNameSingular.Opportunity, + limit: 3, + searchInput: deferredCommandMenuSearch ?? undefined, + }); const peopleCommands = useMemo( () => - people.map(({ id, name: { firstName, lastName } }) => ({ + people?.map(({ id, name: { firstName, lastName } }) => ({ id, label: `${firstName} ${lastName}`, to: `object/person/${id}`, @@ -231,7 +213,7 @@ export const CommandMenu = () => { const companyCommands = useMemo( () => - companies.map(({ id, name }) => ({ + companies?.map(({ id, name }) => ({ id, label: name ?? '', to: `object/company/${id}`, @@ -241,7 +223,7 @@ export const CommandMenu = () => { const opportunityCommands = useMemo( () => - opportunities.map(({ id, name }) => ({ + opportunities?.map(({ id, name }) => ({ id, label: name ?? '', to: `object/opportunity/${id}`, @@ -251,7 +233,7 @@ export const CommandMenu = () => { const noteCommands = useMemo( () => - notes.map((note) => ({ + notes?.map((note) => ({ id: note.id, label: note.title ?? '', to: '', @@ -261,12 +243,20 @@ export const CommandMenu = () => { ); const otherCommands = useMemo(() => { - return [ - ...peopleCommands, - ...companyCommands, - ...opportunityCommands, - ...noteCommands, - ] as Command[]; + const commandsArray: Command[] = []; + if (peopleCommands?.length > 0) { + commandsArray.push(...(peopleCommands as Command[])); + } + if (companyCommands?.length > 0) { + commandsArray.push(...(companyCommands as Command[])); + } + if (opportunityCommands?.length > 0) { + commandsArray.push(...(opportunityCommands as Command[])); + } + if (noteCommands?.length > 0) { + commandsArray.push(...(noteCommands as Command[])); + } + return commandsArray; }, [peopleCommands, companyCommands, noteCommands, opportunityCommands]); const checkInShortcuts = (cmd: Command, search: string) => { @@ -284,17 +274,17 @@ export const CommandMenu = () => { const matchingNavigateCommand = commandMenuCommands.filter( (cmd) => - (commandMenuSearch.length > 0 - ? checkInShortcuts(cmd, commandMenuSearch) || - checkInLabels(cmd, commandMenuSearch) + (deferredCommandMenuSearch.length > 0 + ? checkInShortcuts(cmd, deferredCommandMenuSearch) || + checkInLabels(cmd, deferredCommandMenuSearch) : true) && cmd.type === CommandType.Navigate, ); const matchingCreateCommand = commandMenuCommands.filter( (cmd) => - (commandMenuSearch.length > 0 - ? checkInShortcuts(cmd, commandMenuSearch) || - checkInLabels(cmd, commandMenuSearch) + (deferredCommandMenuSearch.length > 0 + ? checkInShortcuts(cmd, deferredCommandMenuSearch) || + checkInLabels(cmd, deferredCommandMenuSearch) : true) && cmd.type === CommandType.Create, ); @@ -314,7 +304,7 @@ export const CommandMenu = () => { label: 'Open Copilot', type: CommandType.Navigate, onCommandClick: () => { - setCopilotQuery(commandMenuSearch); + setCopilotQuery(deferredCommandMenuSearch); openCopilotRightDrawer(); }, }; @@ -325,10 +315,23 @@ export const CommandMenu = () => { .map((cmd) => cmd.id) .concat(matchingCreateCommand.map((cmd) => cmd.id)) .concat(matchingNavigateCommand.map((cmd) => cmd.id)) - .concat(people.map((person) => person.id)) - .concat(companies.map((company) => company.id)) - .concat(opportunities.map((opportunity) => opportunity.id)) - .concat(notes.map((note) => note.id)); + .concat(people?.map((person) => person.id)) + .concat(companies?.map((company) => company.id)) + .concat(opportunities?.map((opportunity) => opportunity.id)) + .concat(notes?.map((note) => note.id)); + + const isNoResults = + !matchingCreateCommand.length && + !matchingNavigateCommand.length && + !people?.length && + !companies?.length && + !notes?.length && + !opportunities?.length; + const isLoading = + isPeopleLoading || + isNotesLoading || + isOpportunitiesLoading || + isCompaniesLoading; return ( <> @@ -372,14 +375,9 @@ export const CommandMenu = () => { } }} > - {!matchingCreateCommand.length && - !matchingNavigateCommand.length && - !people.length && - !companies.length && - !notes.length && - !opportunities.length && ( - <StyledEmpty>No results found</StyledEmpty> - )} + {isNoResults && !isLoading && ( + <StyledEmpty>No results found</StyledEmpty> + )} {isCopilotEnabled && ( <CommandGroup heading="Copilot"> <SelectableItem itemId={copilotCommand.id}> @@ -387,8 +385,8 @@ export const CommandMenu = () => { id={copilotCommand.id} Icon={copilotCommand.Icon} label={`${copilotCommand.label} ${ - commandMenuSearch.length > 2 - ? `"${commandMenuSearch}"` + deferredCommandMenuSearch.length > 2 + ? `"${deferredCommandMenuSearch}"` : '' }`} onClick={copilotCommand.onCommandClick} @@ -429,7 +427,7 @@ export const CommandMenu = () => { ))} </CommandGroup> <CommandGroup heading="People"> - {people.map((person) => ( + {people?.map((person) => ( <SelectableItem itemId={person.id} key={person.id}> <CommandMenuItem id={person.id} @@ -455,7 +453,7 @@ export const CommandMenu = () => { ))} </CommandGroup> <CommandGroup heading="Companies"> - {companies.map((company) => ( + {companies?.map((company) => ( <SelectableItem itemId={company.id} key={company.id}> <CommandMenuItem id={company.id} @@ -476,7 +474,7 @@ export const CommandMenu = () => { ))} </CommandGroup> <CommandGroup heading="Opportunities"> - {opportunities.map((opportunity) => ( + {opportunities?.map((opportunity) => ( <SelectableItem itemId={opportunity.id} key={opportunity.id} @@ -484,14 +482,14 @@ export const CommandMenu = () => { <CommandMenuItem id={opportunity.id} key={opportunity.id} - label={opportunity.name} + label={opportunity.name ?? ''} to={`object/opportunity/${opportunity.id}`} Icon={() => ( <Avatar type="rounded" avatarUrl={null} placeholderColorSeed={opportunity.id} - placeholder={opportunity.name} + placeholder={opportunity.name ?? ''} /> )} /> @@ -499,7 +497,7 @@ export const CommandMenu = () => { ))} </CommandGroup> <CommandGroup heading="Notes"> - {notes.map((note) => ( + {notes?.map((note) => ( <SelectableItem itemId={note.id} key={note.id}> <CommandMenuItem id={note.id} diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx index 16c8bf7f4104..a7e1dc95e33d 100644 --- a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import { Meta, StoryObj } from '@storybook/react'; import { expect, userEvent, within } from '@storybook/test'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { IconCheckbox, IconNotes } from 'twenty-ui'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; @@ -20,6 +20,7 @@ import { } from '~/testing/mock-data/users'; import { sleep } from '~/utils/sleep'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CommandMenu } from '../CommandMenu'; const companiesMock = getCompaniesMock(); @@ -35,14 +36,21 @@ const meta: Meta<typeof CommandMenu> = { const setCurrentWorkspaceMember = useSetRecoilState( currentWorkspaceMemberState, ); - const { addToCommandMenu, setToInitialCommandMenu, openCommandMenu } = + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const { addToCommandMenu, setObjectsInCommandMenu, openCommandMenu } = useCommandMenu(); setCurrentWorkspace(mockDefaultWorkspace); setCurrentWorkspaceMember(mockedWorkspaceMemberData); useEffect(() => { - setToInitialCommandMenu(); + const nonSystemActiveObjects = objectMetadataItems.filter( + (object) => !object.isSystem && object.isActive, + ); + + setObjectsInCommandMenu(nonSystemActiveObjects); + addToCommandMenu([ { id: 'create-task', @@ -62,7 +70,12 @@ const meta: Meta<typeof CommandMenu> = { }, ]); openCommandMenu(); - }, [addToCommandMenu, setToInitialCommandMenu, openCommandMenu]); + }, [ + addToCommandMenu, + setObjectsInCommandMenu, + openCommandMenu, + objectMetadataItems, + ]); return <Story />; }, diff --git a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuCommands.ts b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuCommands.ts index 3c7f03168d98..711fbff881e9 100644 --- a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuCommands.ts +++ b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuCommands.ts @@ -8,8 +8,8 @@ import { import { Command, CommandType } from '../types/Command'; -export const COMMAND_MENU_COMMANDS: Command[] = [ - { +export const COMMAND_MENU_COMMANDS: { [key: string]: Command } = { + people: { id: 'go-to-people', to: '/objects/people', label: 'Go to People', @@ -18,7 +18,7 @@ export const COMMAND_MENU_COMMANDS: Command[] = [ secondHotKey: 'P', Icon: IconUser, }, - { + companies: { id: 'go-to-companies', to: '/objects/companies', label: 'Go to Companies', @@ -27,7 +27,7 @@ export const COMMAND_MENU_COMMANDS: Command[] = [ secondHotKey: 'C', Icon: IconBuildingSkyscraper, }, - { + opportunities: { id: 'go-to-activities', to: '/objects/opportunities', label: 'Go to Opportunities', @@ -36,7 +36,7 @@ export const COMMAND_MENU_COMMANDS: Command[] = [ secondHotKey: 'O', Icon: IconTargetArrow, }, - { + settings: { id: 'go-to-settings', to: '/settings/profile', label: 'Go to Settings', @@ -45,7 +45,7 @@ export const COMMAND_MENU_COMMANDS: Command[] = [ secondHotKey: 'S', Icon: IconSettings, }, - { + tasks: { id: 'go-to-tasks', to: '/objects/tasks', label: 'Go to Tasks', @@ -54,4 +54,4 @@ export const COMMAND_MENU_COMMANDS: Command[] = [ secondHotKey: 'T', Icon: IconCheckbox, }, -]; +}; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__test__/useCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__test__/useCommandMenu.test.tsx index e1ee5501382f..9d7e103398cc 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/__test__/useCommandMenu.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/hooks/__test__/useCommandMenu.test.tsx @@ -1,6 +1,6 @@ +import { renderHook } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import { MemoryRouter } from 'react-router-dom'; -import { renderHook } from '@testing-library/react'; import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; @@ -107,13 +107,40 @@ describe('useCommandMenu', () => { expect(onClickMock).toHaveBeenCalledTimes(1); }); - it('should setToInitialCommandMenu command menu', () => { + it('should setObjectsInCommandMenu command menu', () => { const { result } = renderHooks(); act(() => { - result.current.commandMenu.setToInitialCommandMenu(); + result.current.commandMenu.setObjectsInCommandMenu([]); + }); + + expect(result.current.commandMenuCommands.length).toBe(1); + + act(() => { + result.current.commandMenu.setObjectsInCommandMenu([ + { + id: 'b88745ce-9021-4316-a018-8884e02d05ca', + nameSingular: 'task', + namePlural: 'tasks', + labelSingular: 'Task', + labelPlural: 'Tasks', + description: 'A task', + icon: 'IconCheckbox', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + createdAt: '2024-09-12T20:23:46.041Z', + updatedAt: '2024-09-13T08:36:53.426Z', + labelIdentifierFieldMetadataId: + 'ab7901eb-43e1-4dc7-8f3b-cdee2857eb9a', + imageIdentifierFieldMetadataId: null, + fields: [], + indexMetadatas: [], + }, + ]); }); - expect(result.current.commandMenuCommands.length).toBe(5); + expect(result.current.commandMenuCommands.length).toBe(2); }); }); diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts index 1a8e085064e2..d19c314a1c8b 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -1,6 +1,6 @@ +import { isNonEmptyString } from '@sniptt/guards'; import { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; @@ -9,10 +9,13 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { isDefined } from '~/utils/isDefined'; -import { COMMAND_MENU_COMMANDS } from '../constants/CommandMenuCommands'; +import { COMMAND_MENU_COMMANDS } from '@/command-menu/constants/CommandMenuCommands'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { ALL_ICONS } from '@ui/display/icon/providers/internal/AllIcons'; +import { sortByProperty } from '~/utils/array/sortByProperty'; import { commandMenuCommandsState } from '../states/commandMenuCommandsState'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; -import { Command } from '../types/Command'; +import { Command, CommandType } from '../types/Command'; export const useCommandMenu = () => { const navigate = useNavigate(); @@ -70,8 +73,27 @@ export const useCommandMenu = () => { [setCommands], ); - const setToInitialCommandMenu = () => { - setCommands(COMMAND_MENU_COMMANDS); + const setObjectsInCommandMenu = (menuItems: ObjectMetadataItem[]) => { + const formattedItems = [ + ...[ + ...menuItems.map( + (item) => + ({ + id: item.id, + to: `/objects/${item.namePlural}`, + label: `Go to ${item.labelPlural}`, + type: CommandType.Navigate, + firstHotKey: 'G', + secondHotKey: item.labelPlural[0], + Icon: ALL_ICONS[ + (item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight' + ], + }) as Command, + ), + ].sort(sortByProperty('label', 'asc')), + COMMAND_MENU_COMMANDS.settings, + ]; + setCommands(formattedItems); }; const onItemClick = useCallback( @@ -96,6 +118,6 @@ export const useCommandMenu = () => { toggleCommandMenu, addToCommandMenu, onItemClick, - setToInitialCommandMenu, + setObjectsInCommandMenu, }; }; diff --git a/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentObjectMetadataIdState.ts b/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentObjectMetadataIdState.ts new file mode 100644 index 000000000000..3227e53807df --- /dev/null +++ b/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentObjectMetadataIdState.ts @@ -0,0 +1,8 @@ +import { createState } from 'twenty-ui'; + +export const contextStoreCurrentObjectMetadataIdState = createState< + string | null +>({ + key: 'contextStoreCurrentObjectMetadataIdState', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentViewIdState.ts b/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentViewIdState.ts new file mode 100644 index 000000000000..41af1cc1357b --- /dev/null +++ b/packages/twenty-front/src/modules/context-store/states/contextStoreCurrentViewIdState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const contextStoreCurrentViewIdState = createState<string | null>({ + key: 'contextStoreCurrentViewIdState', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/context-store/states/contextStoreTargetedRecordIdsState.ts b/packages/twenty-front/src/modules/context-store/states/contextStoreTargetedRecordIdsState.ts new file mode 100644 index 000000000000..df0c3451172c --- /dev/null +++ b/packages/twenty-front/src/modules/context-store/states/contextStoreTargetedRecordIdsState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const contextStoreTargetedRecordIdsState = createState<string[]>({ + key: 'contextStoreTargetedRecordIdsState', + defaultValue: [], +}); diff --git a/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx b/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx index 1b500320199c..a385b1fb0261 100644 --- a/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; @@ -20,7 +20,7 @@ export const PromiseRejectionEffect = () => { }, ); } else { - enqueueSnackBar(`Error: ${event.reason}`, { + enqueueSnackBar(`${error.message}`, { variant: SnackBarVariant.Error, }); } 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} > <StyledSkeletonContainer> - <Skeleton width={56} height={13} /> + <Skeleton + width={56} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.xs} + /> <StyledSkeletonColumn> - <Skeleton width={196} height={16} /> - <Skeleton width={196} height={16} /> + <Skeleton + width={196} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> + <Skeleton + width={196} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </StyledSkeletonColumn> </StyledSkeletonContainer> </SkeletonTheme> diff --git a/packages/twenty-front/src/modules/favorites/components/WorkspaceFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/WorkspaceFavorites.tsx index b89290526c98..cf106211405b 100644 --- a/packages/twenty-front/src/modules/favorites/components/WorkspaceFavorites.tsx +++ b/packages/twenty-front/src/modules/favorites/components/WorkspaceFavorites.tsx @@ -1,7 +1,6 @@ -import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { useFilteredObjectMetadataItemsForWorkspaceFavorites } from '@/navigation/hooks/useObjectMetadataItemsInWorkspaceFavorites'; import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems'; import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader'; -import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; @@ -9,34 +8,18 @@ import { View } from '@/views/types/View'; export const WorkspaceFavorites = () => { const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); - const loading = useIsPrefetchLoading(); - - const { workspaceFavorites } = useFavorites(); - - const workspaceFavoriteIds = new Set( - workspaceFavorites.map((favorite) => favorite.recordId), - ); - const favoriteViewObjectMetadataIds = views.reduce<string[]>((acc, view) => { - if (workspaceFavoriteIds.has(view.id)) { - acc.push(view.objectMetadataId); - } - return acc; - }, []); - - const { objectMetadataItems } = useFilteredObjectMetadataItems(); - - const objectMetadataItemsToDisplay = objectMetadataItems.filter((item) => - favoriteViewObjectMetadataIds.includes(item.id), - ); + const { activeObjectMetadataItems: objectMetadataItemsToDisplay } = + useFilteredObjectMetadataItemsForWorkspaceFavorites(); + const loading = useIsPrefetchLoading(); if (loading) { return <NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader />; } return ( <NavigationDrawerSectionForObjectMetadataItems - sectionTitle={'Workspace Favorites'} + sectionTitle={'Workspace'} objectMetadataItems={objectMetadataItemsToDisplay} views={views} isRemote={false} 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 3956803dc55f..84cf2310c86b 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts @@ -1,7 +1,6 @@ import { gql } from '@apollo/client'; import { AvatarType } from 'twenty-ui'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; export const mockId = '8f3b2121-f194-4ba4-9fbf-2d5a37126806'; @@ -86,158 +85,236 @@ export const mocks = [ mutation CreateOneFavorite($input: FavoriteCreateInput!) { createFavorite(data: $input) { __typename - taskId - myCustomObjectId - workspaceMemberId - workspaceMember { + company { __typename - userId - updatedAt - dateFormat - id - locale - avatarUrl - timeZone - name { - firstName - lastName + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode } - userEmail createdAt - timeFormat - colorScheme - } - companyId - myCustomObject { - __typename createdBy { source workspaceMemberId name } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name position + tagline updatedAt - name - myCustomField - id - createdAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } - updatedAt + companyId + createdAt + deletedAt id - opportunity { + note { __typename - companyId - closeDate - stage + body + createdAt createdBy { source workspaceMemberId name } + deletedAt id + position + title updatedAt - name - createdAt - pointOfContactId + } + noteId + opportunity { + __typename amount { amountMicros currencyCode } - position - } - noteId - note { - __typename + closeDate + companyId + createdAt createdBy { source workspaceMemberId name } + deletedAt + id + name + pointOfContactId position - body + stage updatedAt - createdAt - title - id } - personId - task { + opportunityId + person { __typename - status - assigneeId - updatedAt - body + avatarUrl + city + companyId createdAt - dueAt - position - id - title createdBy { source workspaceMemberId name } - } - opportunityId - position - createdAt - company { - __typename - id - visaSponsorship - createdBy { - source - workspaceMemberId - name + deletedAt + emails { + primaryEmail + additionalEmails } - domainName { + id + intro + jobTitle + linkedinLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - introVideo { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones } position - annualRecurringRevenue { - amountMicros - currencyCode + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones } - employees - linkedinLink { + workPreference + xLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - workPolicy - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng + } + personId + position + rocket { + __typename + createdAt + createdBy { + source + workspaceMemberId + name } + deletedAt + id name + position updatedAt - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + } + rocketId + task { + __typename + assigneeId + body + createdAt + createdBy { + source + workspaceMemberId + name } - myCustomField + deletedAt + dueAt + id + position + status + title + updatedAt + } + taskId + updatedAt + view { + __typename createdAt - accountOwnerId - tagline - idealCustomerProfile + deletedAt + icon + id + isCompact + kanbanFieldMetadataId + key + name + objectMetadataId + position + type + updatedAt } - person { - ${PERSON_FRAGMENT} + 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 } } `, @@ -263,7 +340,9 @@ export const mocks = [ query: gql` mutation DeleteOneFavorite($idToDelete: ID!) { deleteFavorite(id: $idToDelete) { + __typename id + deletedAt } } `, @@ -286,158 +365,236 @@ export const mocks = [ ) { updateFavorite(id: $idToUpdate, data: $input) { __typename - taskId - myCustomObjectId - workspaceMemberId - workspaceMember { - __typename - userId - updatedAt - dateFormat - id - locale - avatarUrl - timeZone - name { - firstName - lastName - } - userEmail - createdAt - timeFormat - colorScheme - } - companyId - myCustomObject { - __typename - createdBy { - source - workspaceMemberId - name - } - position - updatedAt - name - myCustomField - id - createdAt - } - updatedAt - id - opportunity { - __typename - companyId - closeDate - stage - createdBy { - source - workspaceMemberId - name - } - id - updatedAt - name - createdAt - pointOfContactId - amount { - amountMicros - currencyCode - } - position - } - noteId - note { - __typename - createdBy { - source - workspaceMemberId - name - } - position - body - updatedAt - createdAt - title - id - } - personId - task { - __typename - status - assigneeId - updatedAt - body - createdAt - dueAt - position - id - title - createdBy { - source - workspaceMemberId - name - } - } - opportunityId - position - createdAt - company { - __typename - id - visaSponsorship - 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 - } - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - introVideo { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - position - annualRecurringRevenue { - amountMicros - currencyCode - } - employees - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - workPolicy - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } - name - updatedAt - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - myCustomField - createdAt - accountOwnerId - tagline - idealCustomerProfile - } - person { - ${PERSON_FRAGMENT} - } } } `, 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 c5fc1e29e1e1..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 { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { favoriteId, favoriteTargetObjectRecord, @@ -29,17 +27,9 @@ jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: () => ({ records: initialFavorites }), })); -const mockObjectMetadataItems = getObjectMetadataItemsMock(); - -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </SnackBarProviderScope> - </RecoilRoot> -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFavorites', () => { it('should fetch favorites successfully', async () => { @@ -51,7 +41,7 @@ describe('useFavorites', () => { setCurrentWorkspaceMember(mockWorkspaceMember); const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFavorites(); }, @@ -72,7 +62,7 @@ describe('useFavorites', () => { setCurrentWorkspaceMember(mockWorkspaceMember); const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFavorites(); }, @@ -100,7 +90,7 @@ describe('useFavorites', () => { setCurrentWorkspaceMember(mockWorkspaceMember); const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFavorites(); }, @@ -125,7 +115,7 @@ describe('useFavorites', () => { setCurrentWorkspaceMember(mockWorkspaceMember); const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFavorites(); }, diff --git a/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases.tsx b/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases.tsx index 03e7e04802f9..c3f381c1bcd5 100644 --- a/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases.tsx @@ -1,6 +1,6 @@ import { InformationBanner } from '@/information-banner/components/InformationBanner'; -import { InformationBannerKeys } from '@/information-banner/enums/InformationBannerKeys.enum'; import { useAccountToReconnect } from '@/information-banner/hooks/useAccountToReconnect'; +import { InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys'; import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth'; import { IconRefresh } from 'twenty-ui'; diff --git a/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions.tsx b/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions.tsx index 45a4ca00b20b..7f74a129b652 100644 --- a/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions.tsx @@ -1,6 +1,6 @@ import { InformationBanner } from '@/information-banner/components/InformationBanner'; -import { InformationBannerKeys } from '@/information-banner/enums/InformationBannerKeys.enum'; import { useAccountToReconnect } from '@/information-banner/hooks/useAccountToReconnect'; +import { InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys'; import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth'; import { IconRefresh } from 'twenty-ui'; diff --git a/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts b/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts index e73031015a44..ea74778ff536 100644 --- a/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts +++ b/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts @@ -1,6 +1,6 @@ import { ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { currentUserState } from '@/auth/states/currentUserState'; -import { InformationBannerKeys } from '@/information-banner/enums/InformationBannerKeys.enum'; +import { InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { useRecoilValue } from 'recoil'; diff --git a/packages/twenty-front/src/modules/information-banner/enums/InformationBannerKeys.enum.ts b/packages/twenty-front/src/modules/information-banner/types/InformationBannerKeys.ts similarity index 100% rename from packages/twenty-front/src/modules/information-banner/enums/InformationBannerKeys.enum.ts rename to packages/twenty-front/src/modules/information-banner/types/InformationBannerKeys.ts diff --git a/packages/twenty-front/src/modules/localization/utils/__tests__/findAvailableTimeZoneOption.test.ts b/packages/twenty-front/src/modules/localization/utils/__tests__/findAvailableTimeZoneOption.test.ts new file mode 100644 index 000000000000..fa2bc25078e6 --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/__tests__/findAvailableTimeZoneOption.test.ts @@ -0,0 +1,15 @@ +import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableTimeZoneOption'; + +describe('findAvailableTimeZoneOption', () => { + it('should find the matching available IANA time zone select option from a given IANA time zone', () => { + const ianaTimeZone = 'Europe/Paris'; + const expectedOption = { + label: '(GMT+02:00) Central European Summer Time - Paris', + value: 'Europe/Paris', + }; + + const option = findAvailableTimeZoneOption(ianaTimeZone); + + expect(option).toEqual(expectedOption); + }); +}); diff --git a/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts b/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts index 0580333afb90..01bad17167a5 100644 --- a/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts +++ b/packages/twenty-front/src/modules/localization/utils/detectTimeFormat.ts @@ -5,6 +5,10 @@ export const detectTimeFormat = () => { const isHour12 = Intl.DateTimeFormat(navigator.language, { hour: 'numeric', }).resolvedOptions().hour12; - if (isDefined(isHour12) && isHour12) return TimeFormat.HOUR_12; + + if (isDefined(isHour12) && isHour12) { + return TimeFormat.HOUR_12; + } + return TimeFormat.HOUR_24; }; diff --git a/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts new file mode 100644 index 000000000000..2bcc93a30357 --- /dev/null +++ b/packages/twenty-front/src/modules/localization/utils/formatDateISOStringToRelativeDate.ts @@ -0,0 +1,25 @@ +import { + differenceInDays, + formatDistance, + isToday, + startOfDay, +} from 'date-fns'; + +export const formatDateISOStringToRelativeDate = ( + isoDate: string, + isDayMaximumPrecision = false, +) => { + const now = new Date(); + const targetDate = new Date(isoDate); + + if (isDayMaximumPrecision && isToday(targetDate)) return 'Today'; + + const isWithin24h = Math.abs(differenceInDays(targetDate, now)) < 1; + + if (isDayMaximumPrecision || !isWithin24h) + return formatDistance(startOfDay(targetDate), startOfDay(now), { + addSuffix: true, + }); + + return formatDistance(targetDate, now, { addSuffix: true }); +}; diff --git a/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx index da636fe025e5..a6d3b1045cc9 100644 --- a/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx @@ -4,7 +4,6 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SettingsNavigationDrawerItems } from '@/settings/components/SettingsNavigationDrawerItems'; import { SupportDropdown } from '@/support/components/SupportDropdown'; -import { GithubVersionLink } from '@/ui/navigation/link/components/GithubVersionLink'; import { NavigationDrawer, NavigationDrawerProps, @@ -16,6 +15,7 @@ import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI'; import { useIsSettingsPage } from '../hooks/useIsSettingsPage'; import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState'; +import { AdvancedSettingsToggle } from '@/ui/navigation/link/components/AdvancedSettingsToggle'; import { MainNavigationDrawerItems } from './MainNavigationDrawerItems'; export type AppNavigationDrawerProps = { @@ -44,7 +44,7 @@ export const AppNavigationDrawer = ({ isSubMenu: true, title: 'Exit Settings', children: <SettingsNavigationDrawerItems />, - footer: <GithubVersionLink />, + footer: <AdvancedSettingsToggle />, } : { logo: diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx index 88f18e97e66b..867b5b49f558 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx @@ -5,6 +5,7 @@ import { IconSearch, IconSettings } from 'twenty-ui'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { CurrentWorkspaceMemberFavorites } from '@/favorites/components/CurrentWorkspaceMemberFavorites'; import { WorkspaceFavorites } from '@/favorites/components/WorkspaceFavorites'; +import { NavigationDrawerOpenedSection } from '@/object-metadata/components/NavigationDrawerOpenedSection'; import { NavigationDrawerSectionForObjectMetadataItemsWrapper } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsWrapper'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; @@ -44,6 +45,8 @@ export const MainNavigationDrawerItems = () => { </NavigationDrawerSection> )} + {isWorkspaceFavoriteEnabled && <NavigationDrawerOpenedSection />} + <CurrentWorkspaceMemberFavorites /> {isWorkspaceFavoriteEnabled ? ( 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 8efee5d0c42d..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 @@ -4,16 +4,17 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { - COMPANY_OBJECT_METADATA_ID, - getObjectMetadataItemsMock, -} from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { AppPath } from '@/types/AppPath'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedUserData } from '~/testing/mock-data/users'; jest.mock('@/prefetch/hooks/usePrefetchedData'); const setupMockPrefetchedData = (viewId?: string) => { + const companyObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); + jest.mocked(usePrefetchedData).mockReturnValue({ isDataPrefetched: true, records: viewId @@ -21,7 +22,7 @@ const setupMockPrefetchedData = (viewId?: string) => { { id: viewId, __typename: 'object', - objectMetadataId: COMPANY_OBJECT_METADATA_ID, + objectMetadataId: companyObjectMetadata?.id, }, ] : [], @@ -36,7 +37,7 @@ const renderHooks = (withCurrentUser: boolean) => { objectMetadataItemsState, ); - setObjectMetadataItems(getObjectMetadataItemsMock()); + setObjectMetadataItems(generatedMockObjectMetadataItems); if (withCurrentUser) { setCurrentUser(mockedUserData); diff --git a/packages/twenty-front/src/modules/navigation/hooks/useObjectMetadataItemsInWorkspaceFavorites.ts b/packages/twenty-front/src/modules/navigation/hooks/useObjectMetadataItemsInWorkspaceFavorites.ts new file mode 100644 index 000000000000..1c8abefe350e --- /dev/null +++ b/packages/twenty-front/src/modules/navigation/hooks/useObjectMetadataItemsInWorkspaceFavorites.ts @@ -0,0 +1,35 @@ +import { useFavorites } from '@/favorites/hooks/useFavorites'; +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { View } from '@/views/types/View'; + +export const useFilteredObjectMetadataItemsForWorkspaceFavorites = () => { + const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); + + const { workspaceFavorites } = useFavorites(); + + const workspaceFavoriteIds = new Set( + workspaceFavorites.map((favorite) => favorite.recordId), + ); + + const favoriteViewObjectMetadataIds = new Set( + views.reduce<string[]>((acc, view) => { + if (workspaceFavoriteIds.has(view.id)) { + acc.push(view.objectMetadataId); + } + return acc; + }, []), + ); + + const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); + + const activeObjectMetadataItemsInWorkspaceFavorites = + activeObjectMetadataItems.filter((item) => + favoriteViewObjectMetadataIds.has(item.id), + ); + + return { + activeObjectMetadataItems: activeObjectMetadataItemsInWorkspaceFavorites, + }; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx new file mode 100644 index 000000000000..fb17b643078f --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerOpenedSection.tsx @@ -0,0 +1,57 @@ +import { useParams } from 'react-router-dom'; + +import { useFilteredObjectMetadataItemsForWorkspaceFavorites } from '@/navigation/hooks/useObjectMetadataItemsInWorkspaceFavorites'; +import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems'; +import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader'; +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { View } from '@/views/types/View'; + +export const NavigationDrawerOpenedSection = () => { + const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); + const filteredActiveObjectMetadataItems = activeObjectMetadataItems.filter( + (item) => !item.isRemote, + ); + + const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); + const loading = useIsPrefetchLoading(); + + const currentObjectNamePlural = useParams().objectNamePlural; + + const { activeObjectMetadataItems: workspaceFavoritesObjectMetadataItems } = + useFilteredObjectMetadataItemsForWorkspaceFavorites(); + + if (!currentObjectNamePlural) { + return; + } + + const objectMetadataItem = filteredActiveObjectMetadataItems.find( + (item) => item.namePlural === currentObjectNamePlural, + ); + + if (!objectMetadataItem) { + return; + } + + const shouldDisplayObjectInOpenedSection = + !workspaceFavoritesObjectMetadataItems + .map((item) => item.id) + .includes(objectMetadataItem.id); + + if (loading) { + return <NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader />; + } + + return ( + shouldDisplayObjectInOpenedSection && ( + <NavigationDrawerSectionForObjectMetadataItems + sectionTitle={'Opened'} + objectMetadataItems={[objectMetadataItem]} + views={views} + isRemote={false} + /> + ) + ); +}; 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} > <StyledSkeletonColumn> - <Skeleton width={196} height={16} /> - <Skeleton width={196} height={16} /> - <Skeleton width={196} height={16} /> + <Skeleton + width={196} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> + <Skeleton + width={196} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> + <Skeleton + width={196} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </StyledSkeletonColumn> </SkeletonTheme> ); 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 b1bb3a151ad9..aeb16de86a66 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx @@ -1,13 +1,13 @@ import { useEffect } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilCallback, useRecoilValue } from 'recoil'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { WorkspaceActivationStatus } from '~/generated/graphql'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -16,35 +16,36 @@ export const ObjectMetadataItemsLoadEffect = () => { const currentWorkspace = useRecoilValue(currentWorkspaceState); const isLoggedIn = useIsLogged(); - const { objectMetadataItems: newObjectMetadataItems, loading } = + const { objectMetadataItems: newObjectMetadataItems } = useFindManyObjectMetadataItems({ skip: !isLoggedIn, }); - const [objectMetadataItems, setObjectMetadataItems] = useRecoilState( - objectMetadataItemsState, + const updateObjectMetadataItems = useRecoilCallback( + ({ set, snapshot }) => + () => { + const toSetObjectMetadataItems = + isUndefinedOrNull(currentUser) || + currentWorkspace?.activationStatus !== + WorkspaceActivationStatus.Active + ? generatedMockObjectMetadataItems + : newObjectMetadataItems; + + if ( + !isDeeplyEqual( + snapshot.getLoadable(objectMetadataItemsState).getValue(), + toSetObjectMetadataItems, + ) + ) { + set(objectMetadataItemsState, toSetObjectMetadataItems); + } + }, + [currentUser, currentWorkspace?.activationStatus, newObjectMetadataItems], ); useEffect(() => { - const toSetObjectMetadataItems = - isUndefinedOrNull(currentUser) || - currentWorkspace?.activationStatus !== WorkspaceActivationStatus.Active - ? getObjectMetadataItemsMock() - : newObjectMetadataItems; - if ( - !loading && - !isDeeplyEqual(objectMetadataItems, toSetObjectMetadataItems) - ) { - setObjectMetadataItems(toSetObjectMetadataItems); - } - }, [ - currentUser, - currentWorkspace?.activationStatus, - loading, - newObjectMetadataItems, - objectMetadataItems, - setObjectMetadataItems, - ]); + updateObjectMetadataItems(); + }, [updateObjectMetadataItems]); return <></>; }; diff --git a/packages/twenty-front/src/modules/object-metadata/constants/SortableFieldMetadataTypes.ts b/packages/twenty-front/src/modules/object-metadata/constants/SortableFieldMetadataTypes.ts index 282710253650..f3599783cf7e 100644 --- a/packages/twenty-front/src/modules/object-metadata/constants/SortableFieldMetadataTypes.ts +++ b/packages/twenty-front/src/modules/object-metadata/constants/SortableFieldMetadataTypes.ts @@ -7,8 +7,6 @@ export const SORTABLE_FIELD_METADATA_TYPES = [ FieldMetadataType.Text, FieldMetadataType.Boolean, FieldMetadataType.Select, - FieldMetadataType.Phone, - FieldMetadataType.Email, FieldMetadataType.Emails, FieldMetadataType.FullName, FieldMetadataType.Rating, diff --git a/packages/twenty-front/src/modules/object-metadata/graphql/mutations.ts b/packages/twenty-front/src/modules/object-metadata/graphql/mutations.ts index ab37d5249158..80814a64b7ec 100644 --- a/packages/twenty-front/src/modules/object-metadata/graphql/mutations.ts +++ b/packages/twenty-front/src/modules/object-metadata/graphql/mutations.ts @@ -35,6 +35,7 @@ export const CREATE_ONE_FIELD_METADATA_ITEM = gql` isNullable createdAt updatedAt + settings defaultValue options } @@ -73,6 +74,7 @@ export const UPDATE_ONE_FIELD_METADATA_ITEM = gql` isNullable createdAt updatedAt + settings } } `; @@ -136,6 +138,7 @@ export const DELETE_ONE_FIELD_METADATA_ITEM = gql` isNullable createdAt updatedAt + settings } } `; diff --git a/packages/twenty-front/src/modules/object-metadata/graphql/queries.ts b/packages/twenty-front/src/modules/object-metadata/graphql/queries.ts index 8dbaad032e4c..35beb162d350 100644 --- a/packages/twenty-front/src/modules/object-metadata/graphql/queries.ts +++ b/packages/twenty-front/src/modules/object-metadata/graphql/queries.ts @@ -24,6 +24,30 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql` updatedAt labelIdentifierFieldMetadataId imageIdentifierFieldMetadataId + indexMetadatas(paging: { first: 100 }) { + edges { + node { + id + createdAt + updatedAt + name + indexWhereClause + indexType + isUnique + indexFieldMetadatas(paging: { first: 100 }) { + edges { + node { + id + createdAt + updatedAt + order + fieldMetadataId + } + } + } + } + } + } fields(paging: { first: 1000 }, filter: $fieldFilter) { edges { node { @@ -37,10 +61,12 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql` isActive isSystem isNullable + isUnique createdAt updatedAt defaultValue options + settings relationDefinition { relationId direction 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 0a73b0d2a42b..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 @@ -17,6 +17,7 @@ const baseFields = ` isNullable createdAt updatedAt + settings `; export const queries = { @@ -47,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 } @@ -73,6 +85,7 @@ export const variables = { label: 'fieldLabel', name: 'fieldLabel', options: undefined, + settings: undefined, objectMetadataId, type: 'TEXT', }, @@ -96,6 +109,7 @@ const defaultResponseData = { isNullable: false, createdAt: '1977-09-28T13:56:55.157Z', updatedAt: '1996-10-10T08:27:57.117Z', + settings: undefined, }; const fieldRelationResponseData = { 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 19e8b6c30bac..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 { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('useColumnDefinitionsFromFieldMetadata', () => { it('should return empty definitions if no object is passed', () => { @@ -22,22 +22,24 @@ describe('useColumnDefinitionsFromFieldMetadata', () => { }); it('should return expected definitions', () => { - const mockObjectMetadataItems = getObjectMetadataItemsMock(); + const companyObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); const { result } = renderHook( (objectMetadataItem?: Nullable<ObjectMetadataItem>) => { return useColumnDefinitionsFromFieldMetadata(objectMetadataItem); }, { - initialProps: mockObjectMetadataItems[1], + initialProps: companyObjectMetadata, }, ); const { columnDefinitions, filterDefinitions, sortDefinitions } = result.current; - expect(columnDefinitions.length).toBe(5); - expect(filterDefinitions.length).toBe(4); - expect(sortDefinitions.length).toBe(4); + expect(columnDefinitions.length).toBe(21); + expect(filterDefinitions.length).toBe(14); + expect(sortDefinitions.length).toBe(14); }); }); 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 }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +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 9bb8f016965f..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 { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { @@ -34,14 +34,12 @@ const Wrapper = ({ children }: { children: ReactNode }) => ( </RecoilRoot> ); -const mockObjectMetadataItems = getObjectMetadataItemsMock(); - describe('useFilteredObjectMetadataItems', () => { it('should findActiveObjectMetadataItemBySlug', async () => { const { result } = renderHook( () => { const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFilteredObjectMetadataItems(); }, @@ -61,7 +59,7 @@ describe('useFilteredObjectMetadataItems', () => { const { result } = renderHook( () => { const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFilteredObjectMetadataItems(); }, @@ -78,10 +76,14 @@ describe('useFilteredObjectMetadataItems', () => { }); it('should findObjectMetadataItemById', async () => { + const peopleObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'people', + ); + const { result } = renderHook( () => { const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFilteredObjectMetadataItems(); }, @@ -92,7 +94,7 @@ describe('useFilteredObjectMetadataItems', () => { act(() => { const res = result.current.findObjectMetadataItemById( - 'ff2881da-89f6-4f15-8f0a-e3f355ea3b94', + peopleObjectMetadata?.id, ); expect(res).toBeDefined(); expect(res?.namePlural).toBe('people'); @@ -103,7 +105,7 @@ describe('useFilteredObjectMetadataItems', () => { const { result } = renderHook( () => { const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFilteredObjectMetadataItems(); }, 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 }) => ( - <RecoilRoot> - <MockedProvider addTypename={false}>{children}</MockedProvider> - </RecoilRoot> -); +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 1a4c0e2cbfcc..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,9 +3,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; - -const mockObjectMetadataItems = getObjectMetadataItemsMock(); +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('useGetObjectRecordIdentifierByNameSingular', () => { it('should work as expected', async () => { @@ -19,7 +17,7 @@ describe('useGetObjectRecordIdentifierByNameSingular', () => { }) => { const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useGetObjectRecordIdentifierByNameSingular()( record, 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 d92e0707f1f1..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 { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( <RecoilRoot> @@ -15,8 +15,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => ( describe('useGetRelationMetadata', () => { it('should return correct properties', async () => { - const objectMetadataItems = getObjectMetadataItemsMock(); - const objectMetadata = objectMetadataItems.find( + const objectMetadata = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; const fieldMetadataItem = objectMetadata.fields.find( @@ -28,7 +27,7 @@ describe('useGetRelationMetadata', () => { const setMetadataItems = useSetRecoilState(objectMetadataItemsState); useEffect(() => { - setMetadataItems(objectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); }, [setMetadataItems]); return useGetRelationMetadata(); @@ -45,9 +44,10 @@ describe('useGetRelationMetadata', () => { relationType, } = result.current({ fieldMetadataItem }) ?? {}; - const expectedRelationObjectMetadataItem = objectMetadataItems.find( - (item) => item.nameSingular === 'opportunity', - ); + const expectedRelationObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); const expectedRelationFieldMetadataItem = expectedRelationObjectMetadataItem?.fields.find( (field) => field.name === 'pointOfContact', 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 ea205704d5f1..f3d33774b119 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,18 +1,19 @@ -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; +import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <MockedProvider addTypename={false}>{children}</MockedProvider> - </RecoilRoot> -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); // Split into tests for each new hook describe('useObjectMetadataItem', () => { + const opportunityObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); it('should return correct properties', async () => { const { result } = renderHook( () => useObjectMetadataItem({ objectNameSingular: 'opportunity' }), @@ -23,6 +24,17 @@ describe('useObjectMetadataItem', () => { const { objectMetadataItem } = result.current; - expect(objectMetadataItem.id).toBe('b95b3f38-9fc2-4d7e-a823-7791cf13d089'); + expect(objectMetadataItem.id).toBe(opportunityObjectMetadata?.id); + }); + + it('should throw an error when invalid object name singular is provided', async () => { + expect(() => + renderHook( + () => useObjectMetadataItem({ objectNameSingular: 'invalid-object' }), + { + wrapper: Wrapper, + }, + ), + ).toThrow(ObjectMetadataItemNotFoundError); }); }); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItemById.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItemById.test.ts new file mode 100644 index 000000000000..ceff2e45541a --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItemById.test.ts @@ -0,0 +1,48 @@ +import { renderHook } from '@testing-library/react'; + +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + +describe('useObjectMetadataItemById', () => { + const opportunityObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + + if (!opportunityObjectMetadata) { + throw new Error('Opportunity object metadata not found'); + } + + it('should return correct properties', async () => { + const { result } = renderHook( + () => + useObjectMetadataItemById({ + objectId: opportunityObjectMetadata.id, + }), + { + wrapper: Wrapper, + }, + ); + + const { objectMetadataItem } = result.current; + + expect(objectMetadataItem?.id).toBe(opportunityObjectMetadata.id); + }); + + it('should return null when invalid ID is provided', async () => { + const { result } = renderHook( + () => useObjectMetadataItemById({ objectId: 'invalid-id' }), + { + wrapper: Wrapper, + }, + ); + + const { objectMetadataItem } = result.current; + + expect(objectMetadataItem).toBeNull(); + }); +}); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItem.ts index 75f9f5a2e489..3f39f89279cb 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItem.ts @@ -1,6 +1,6 @@ import { useDeleteOneRelationMetadataItem } from '@/object-metadata/hooks/useDeleteOneRelationMetadataItem'; -import { Field } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { Field } from '~/generated/graphql'; import { FieldMetadataItem } from '../types/FieldMetadataItem'; import { formatFieldMetadataItemInput } from '../utils/formatFieldMetadataItemInput'; @@ -18,7 +18,13 @@ export const useFieldMetadataItem = () => { const createMetadataField = ( input: Pick< Field, - 'label' | 'icon' | 'description' | 'defaultValue' | 'type' | 'options' + | 'label' + | 'icon' + | 'description' + | 'defaultValue' + | 'type' + | 'options' + | 'settings' > & { objectMetadataId: string; }, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts index 4bf7c29d9298..884dfbcff695 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; import { useQuery } from '@apollo/client'; +import { useMemo } from 'react'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -41,12 +41,9 @@ export const useFindManyObjectMetadataItems = ({ skip: skip || !apolloMetadataClient, onError: (error) => { logError('useFindManyObjectMetadataItems error : ' + error); - enqueueSnackBar( - `Error during useFindManyObjectMetadataItems, ${error.message}`, - { - variant: SnackBarVariant.Error, - }, - ); + enqueueSnackBar(`${error.message}`, { + variant: SnackBarVariant.Error, + }); }, }); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetStandardObjectIcon.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetStandardObjectIcon.ts new file mode 100644 index 000000000000..92b014d730a7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetStandardObjectIcon.ts @@ -0,0 +1,37 @@ +import { useTheme } from '@emotion/react'; +import { IconCheckbox, IconComponent, IconNotes } from 'twenty-ui'; + +export const useGetStandardObjectIcon = (objectNameSingular: string) => { + const theme = useTheme(); + + const getIconForObjectType = ( + objectType: string, + ): IconComponent | undefined => { + switch (objectType) { + case 'note': + return IconNotes; + case 'task': + return IconCheckbox; + default: + return undefined; + } + }; + + const getIconColorForObjectType = (objectType: string): string => { + switch (objectType) { + case 'note': + return theme.color.yellow; + case 'task': + return theme.color.blue; + default: + return 'currentColor'; + } + }; + + const { Icon, IconColor } = { + Icon: getIconForObjectType(objectNameSingular), + IconColor: getIconColorForObjectType(objectNameSingular), + }; + + return { Icon, IconColor }; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useNonSystemActiveObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useNonSystemActiveObjectMetadataItems.ts new file mode 100644 index 000000000000..a33b80e1d125 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useNonSystemActiveObjectMetadataItems.ts @@ -0,0 +1,20 @@ +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useMemo } from 'react'; +import { useRecoilValue } from 'recoil'; + +export const useNonSystemActiveObjectMetadataItems = () => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const nonSystemActiveObjectMetadataItems = useMemo( + () => + objectMetadataItems.filter( + (objectMetadataItem) => + !objectMetadataItem.isSystem && objectMetadataItem.isActive, + ), + [objectMetadataItems], + ); + + return { + nonSystemActiveObjectMetadataItems, + }; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts deleted file mode 100644 index 0f3295dc5c29..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useObjectIsRemote = (objectMetadataItem: ObjectMetadataItem) => { - return objectMetadataItem.isRemote ?? false; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 5659c41a72fd..0a8d3e8600a0 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -1,40 +1,23 @@ import { useRecoilValue } from 'recoil'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { isDefined } from '~/utils/isDefined'; -import { WorkspaceActivationStatus } from '~/generated/graphql'; import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier'; export const useObjectMetadataItem = ({ objectNameSingular, }: ObjectMetadataItemIdentifier) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - - // Todo: deprecate this logic as mocked objectMetadataItems are load in ObjectMetadataItemsLoadEffect anyway - const mockObjectMetadataItems = getObjectMetadataItemsMock(); - - let objectMetadataItem = useRecoilValue( + const objectMetadataItem = useRecoilValue( objectMetadataItemFamilySelector({ objectName: objectNameSingular, objectNameType: 'singular', }), ); - let objectMetadataItems = useRecoilValue(objectMetadataItemsState); - - if (currentWorkspace?.activationStatus !== WorkspaceActivationStatus.Active) { - objectMetadataItem = - mockObjectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.nameSingular === objectNameSingular, - ) ?? null; - objectMetadataItems = mockObjectMetadataItems; - } + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); if (!isDefined(objectMetadataItem)) { throw new ObjectMetadataItemNotFoundError( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts new file mode 100644 index 000000000000..72c559364226 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts @@ -0,0 +1,26 @@ +import { useRecoilValue } from 'recoil'; + +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { isDefined } from '~/utils/isDefined'; + +export const useObjectMetadataItemById = ({ + objectId, +}: { + objectId: string | null; +}) => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const objectMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => objectMetadataItem.id === objectId, + ); + + if (!isDefined(objectMetadataItem)) { + return { + objectMetadataItem: null, + }; + } + + return { + objectMetadataItem, + }; +}; 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 ad9d8641b916..88ac0baab21d 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts @@ -2,8 +2,8 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { WorkspaceActivationStatus } from '~/generated/graphql'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDefined } from '~/utils/isDefined'; export const useObjectNamePluralFromSingular = ({ @@ -12,7 +12,6 @@ export const useObjectNamePluralFromSingular = ({ objectNameSingular: string; }) => { const currentWorkspace = useRecoilValue(currentWorkspaceState); - const mockObjectMetadataItems = getObjectMetadataItemsMock(); let objectMetadataItem = useRecoilValue( objectMetadataItemFamilySelector({ @@ -23,7 +22,7 @@ export const useObjectNamePluralFromSingular = ({ if (currentWorkspace?.activationStatus !== WorkspaceActivationStatus.Active) { objectMetadataItem = - mockObjectMetadataItems.find( + generatedMockObjectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.nameSingular === objectNameSingular, ) ?? null; 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 3b4a244942d5..2e5127d8fcd9 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts @@ -2,8 +2,8 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { WorkspaceActivationStatus } from '~/generated/graphql'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDefined } from '~/utils/isDefined'; export const useObjectNameSingularFromPlural = ({ @@ -13,8 +13,6 @@ export const useObjectNameSingularFromPlural = ({ }) => { const currentWorkspace = useRecoilValue(currentWorkspaceState); - const mockObjectMetadataItems = getObjectMetadataItemsMock(); - let objectMetadataItem = useRecoilValue( objectMetadataItemFamilySelector({ objectName: objectNamePlural, @@ -24,7 +22,7 @@ export const useObjectNameSingularFromPlural = ({ if (currentWorkspace?.activationStatus !== WorkspaceActivationStatus.Active) { objectMetadataItem = - mockObjectMetadataItems.find( + generatedMockObjectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.namePlural === objectNamePlural, ) ?? null; diff --git a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts index 68b0d6ebf6cd..03164e28ac27 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts @@ -32,4 +32,5 @@ export enum CoreObjectNameSingular { Workflow = 'workflow', MessageChannelMessageAssociation = 'messageChannelMessageAssociation', WorkflowVersion = 'workflowVersion', + WorkflowRun = 'workflowRun', } 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 ed4529ff1102..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; @@ -36,4 +36,7 @@ export type FieldMetadataItem = Omit< 'id' | 'nameSingular' | 'namePlural' >; } | null; + settings?: { + displayAsRelativeDate?: boolean; + }; }; diff --git a/packages/twenty-front/src/modules/object-metadata/types/IndexFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/IndexFieldMetadataItem.ts new file mode 100644 index 000000000000..c6b30db7a8dc --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/types/IndexFieldMetadataItem.ts @@ -0,0 +1,5 @@ +import { IndexField as GeneratedIndexField } from '~/generated-metadata/graphql'; + +export type IndexFieldMetadataItem = Omit<GeneratedIndexField, '__typename'> & { + __typename?: string; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/types/IndexMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/IndexMetadataItem.ts new file mode 100644 index 000000000000..33e5260dd49c --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/types/IndexMetadataItem.ts @@ -0,0 +1,10 @@ +import { IndexFieldMetadataItem } from '@/object-metadata/types/IndexFieldMetadataItem'; +import { Index as GeneratedIndex } from '~/generated-metadata/graphql'; + +export type IndexMetadataItem = Omit< + GeneratedIndex, + '__typename' | 'indexFieldMetadatas' | 'objectMetadata' +> & { + __typename?: string; + indexFieldMetadatas: IndexFieldMetadataItem[]; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/types/ObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/ObjectMetadataItem.ts index 9bc2cfaa0a82..61c0fc495fea 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/ObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/ObjectMetadataItem.ts @@ -1,11 +1,13 @@ import { Object as GeneratedObject } from '~/generated-metadata/graphql'; +import { IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem'; import { FieldMetadataItem } from './FieldMetadataItem'; export type ObjectMetadataItem = Omit< GeneratedObject, - '__typename' | 'fields' | 'dataSourceId' + '__typename' | 'fields' | 'dataSourceId' | 'indexMetadatas' > & { __typename?: string; fields: FieldMetadataItem[]; + indexMetadatas: IndexMetadataItem[]; }; 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 07e88a26f066..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,14 +1,12 @@ import { getObjectMetadataItemByNameSingular } from '@/object-metadata/utils/getObjectMetadataItemBySingularName'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; - -const mockObjectMetadataItems = getObjectMetadataItemsMock(); +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectMetadataItemBySingularName', () => { it('should work as expected', () => { - const firstObjectMetadataItem = mockObjectMetadataItems[0]; + const firstObjectMetadataItem = generatedMockObjectMetadataItems[0]; const foundObjectMetadataItem = getObjectMetadataItemByNameSingular({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, objectNameSingular: firstObjectMetadataItem.nameSingular, }); 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 baa61c853751..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,11 +1,9 @@ -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { getOrderByFieldForObjectMetadataItem } from '@/object-metadata/utils/getObjectOrderByField'; - -const mockObjectMetadataItems = getObjectMetadataItemsMock(); +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectOrderByField', () => { it('should work as expected', () => { - const objectMetadataItem = mockObjectMetadataItems.find( + const objectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; const res = getOrderByFieldForObjectMetadataItem(objectMetadataItem); 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 fdd16fdb1afa..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,11 +1,9 @@ -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; - -const mockObjectMetadataItems = getObjectMetadataItemsMock(); +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectSlug', () => { it('should work as expected', () => { - const objectMetadataItem = mockObjectMetadataItems.find( + const objectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; 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 e2dff5d09609..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,11 +1,9 @@ -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; - -const mockObjectMetadataItems = getObjectMetadataItemsMock(); +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('isObjectMetadataAvailableForRelation', () => { it('should work as expected', () => { - const objectMetadataItem = mockObjectMetadataItems.find( + const objectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; 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 da578f8973f8..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,10 +1,8 @@ -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { normalizeGQLField } from '~/utils/normalizeGQLField'; -const mockObjectMetadataItems = getObjectMetadataItemsMock(); - -const personObjectMetadataItem = mockObjectMetadataItems.find( +const personObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', ); @@ -15,7 +13,7 @@ if (!personObjectMetadataItem) { describe('mapFieldMetadataToGraphQLQuery', () => { it('should return fieldName if simpleValue', async () => { const res = mapFieldMetadataToGraphQLQuery({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, field: personObjectMetadataItem.fields.find( (field) => field.name === 'id', )!, @@ -24,7 +22,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => { }); it('should return fieldName if composite', async () => { const res = mapFieldMetadataToGraphQLQuery({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, field: personObjectMetadataItem.fields.find( (field) => field.name === 'name', )!, @@ -40,7 +38,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => { it('should return non relation subFields if relation', async () => { const res = mapFieldMetadataToGraphQLQuery({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, field: personObjectMetadataItem.fields.find( (field) => field.name === 'company', )!, @@ -96,7 +94,7 @@ idealCustomerProfile it('should return only return relation subFields that are in recordGqlFields', async () => { const res = mapFieldMetadataToGraphQLQuery({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, relationrecordFields: { accountOwner: { id: true, name: true }, people: true, @@ -197,6 +195,10 @@ name lastName } phone +{ + primaryPhoneNumber + primaryPhoneCountryCode +} linkedinLink { primaryLinkUrl 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 5a00909648f2..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,10 +1,8 @@ -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery'; -const mockObjectMetadataItems = getObjectMetadataItemsMock(); - -const personObjectMetadataItem = mockObjectMetadataItems.find( +const personObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', ); @@ -15,7 +13,7 @@ if (!personObjectMetadataItem) { describe('mapObjectMetadataToGraphQLQuery', () => { it('should query only specified recordGqlFields', async () => { const res = mapObjectMetadataToGraphQLQuery({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, objectMetadataItem: personObjectMetadataItem, recordGqlFields: { company: true, @@ -46,7 +44,11 @@ describe('mapObjectMetadataToGraphQLQuery', () => { primaryEmail additionalEmails } - phone + phone + { + primaryPhoneNumber + primaryPhoneCountryCode + } createdAt avatarUrl jobTitle @@ -118,7 +120,7 @@ describe('mapObjectMetadataToGraphQLQuery', () => { it('should load only specified operation fields nested', async () => { const res = mapObjectMetadataToGraphQLQuery({ - objectMetadataItems: mockObjectMetadataItems, + objectMetadataItems: generatedMockObjectMetadataItems, objectMetadataItem: personObjectMetadataItem, recordGqlFields: { company: { id: true }, id: true, name: true }, }); 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 7d26d1150410..f372cd2eb3ac 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts @@ -37,6 +37,7 @@ export const formatFieldMetadataItemAsFieldDefinition = ({ targetFieldMetadataName: field.relationDefinition?.targetFieldMetadata?.name ?? '', options: field.options, + settings: field.settings, isNullable: field.isNullable, }; @@ -53,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/formatFieldMetadataItemInput.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts index 15bc319a93f3..5900beed1faa 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts @@ -5,7 +5,13 @@ export const formatFieldMetadataItemInput = ( input: Partial< Pick< FieldMetadataItem, - 'type' | 'label' | 'defaultValue' | 'icon' | 'description' | 'options' + | 'type' + | 'label' + | 'defaultValue' + | 'icon' + | 'description' + | 'options' + | 'settings' > >, ) => { @@ -18,5 +24,6 @@ export const formatFieldMetadataItemInput = ( label, name: label ? computeMetadataNameFromLabelOrThrow(label) : undefined, options: input.options, + settings: input.settings, }; }; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts index 7ebeb0afee26..a110acdceba4 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts @@ -26,10 +26,8 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({ FieldMetadataType.DateTime, FieldMetadataType.Date, FieldMetadataType.Text, - FieldMetadataType.Email, FieldMetadataType.Emails, FieldMetadataType.Number, - FieldMetadataType.Link, FieldMetadataType.Links, FieldMetadataType.FullName, FieldMetadataType.Address, @@ -68,8 +66,6 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => { return 'DATE_TIME'; case FieldMetadataType.Date: return 'DATE'; - case FieldMetadataType.Link: - return 'LINK'; case FieldMetadataType.Links: return 'LINKS'; case FieldMetadataType.FullName: @@ -78,12 +74,8 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => { return 'NUMBER'; case FieldMetadataType.Currency: return 'CURRENCY'; - case FieldMetadataType.Email: - return 'EMAIL'; case FieldMetadataType.Emails: return 'EMAILS'; - case FieldMetadataType.Phone: - return 'PHONE'; case FieldMetadataType.Phones: return 'PHONES'; case FieldMetadataType.Relation: diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts deleted file mode 100644 index 52f781ccc203..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const getDisabledFieldMetadataItems = ( - objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>, -) => - objectMetadataItem.fields.filter( - (fieldMetadataItem) => - !fieldMetadataItem.isActive && !fieldMetadataItem.isSystem, - ); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts b/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts deleted file mode 100644 index 36612a698995..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts +++ /dev/null @@ -1,13353 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID = '39403bee-314b-4f14-bc91-70d500397517'; -export const COMPANY_OBJECT_METADATA_ID = 'f1231579-8e7d-4b84-9a60-41844902f2c4'; - -export const getObjectMetadataItemsMock = () => { - const mockArray = [ - { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "person", - "namePlural": "people", - "labelSingular": "Person", - "labelPlural": "People", - "description": "A person", - "icon": "IconUser", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "f01f1b33-0a27-49a7-b119-5f9bd58477a5", - "imageIdentifierFieldMetadataId": "ef9ff5ea-8aff-4d91-9ab6-6dc38d3eccbe", - "fields": [ - { - "__typename": "field", - "id": "102963b7-3e77-4293-a1e6-1ab59a02b663", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6697751c-3150-483b-8167-c5bc1d620c10", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject Foreign Key", - "description": null, - "icon": null, - "isCustom": true, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:10:31.391Z", - "updatedAt": "2024-08-05T17:10:31.391Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e1ecbeb4-76cb-4f9a-8829-ac0665854c69", - "type": "RELATION", - "name": "pointOfContactForOpportunities", - "label": "Linked Opportunities", - "description": "List of opportunities for which that person is the point of contact", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "3e039f55-e535-406a-8a80-185123910b7a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e1ecbeb4-76cb-4f9a-8829-ac0665854c69", - "name": "pointOfContactForOpportunities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "dc7898b0-d2b7-4910-bedc-a6fe8eb4c41e", - "name": "pointOfContact" - } - } - }, - { - "__typename": "field", - "id": "194ff398-99f9-4cbb-b87a-e44408f9c1ed", - "type": "PHONE", - "name": "whatsapp", - "label": "Whatsapp", - "description": "Contact's Whatsapp Number", - "icon": "IconBrandWhatsapp", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:39:01.956Z", - "updatedAt": "2024-08-05T16:39:01.956Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d2729410-3db8-44b0-b88f-fa3ba9b10650", - "type": "LINKS", - "name": "linkedinLink", - "label": "Linkedin", - "description": "Contactโ€™s Linkedin account", - "icon": "IconBrandLinkedin", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f01f1b33-0a27-49a7-b119-5f9bd58477a5", - "type": "FULL_NAME", - "name": "name", - "label": "Name", - "description": "Contactโ€™s name", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "lastName": "''", - "firstName": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a6d4fe23-a569-4cbb-a68c-96e30a0b0d5b", - "type": "EMAIL", - "name": "email", - "label": "Email", - "description": "Contactโ€™s Email", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "55a856ae-0a48-4ff6-874d-b630818c8cea", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Person record Position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d44bf743-b557-47d4-9341-04114fd05d52", - "type": "RELATION", - "name": "calendarEventParticipants", - "label": "Calendar Event Participants", - "description": "Calendar Event Participants", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "20d67b64-4e67-44a1-81c7-116c0c8c6368", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d44bf743-b557-47d4-9341-04114fd05d52", - "name": "calendarEventParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a7eb211d-4481-4269-99d7-cf2183b45598", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "9b018bba-687b-4850-9e0e-c192d3b5977d", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the contact", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9a3a145b-6d06-4892-84d4-af523f40c58d", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "9b018bba-687b-4850-9e0e-c192d3b5977d", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "940d1664-b17c-4f66-820b-abfec70adaa5", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "4c9ba269-244f-4768-a52d-9b1ffbe3339f", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the contact", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "182b32c3-9ee9-4a65-937b-d9035ab65300", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4c9ba269-244f-4768-a52d-9b1ffbe3339f", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fc1a31f8-6e1c-4ce1-b6ff-80d1cd605e58", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "e41d7b2e-3e60-4741-b410-a432b1a12b77", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ef9ff5ea-8aff-4d91-9ab6-6dc38d3eccbe", - "type": "TEXT", - "name": "avatarUrl", - "label": "Avatar", - "description": "Contactโ€™s avatar", - "icon": "IconFileUpload", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c9d4cb92-7460-4bee-8ed7-0fbdca4ba546", - "type": "TEXT", - "name": "jobTitle", - "label": "Job Title", - "description": "Contactโ€™s job title", - "icon": "IconBriefcase", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d1fb7174-82ab-4e31-8e1c-53036d0eefe2", - "type": "LINKS", - "name": "xLink", - "label": "X", - "description": "Contactโ€™s X/Twitter account", - "icon": "IconBrandX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ff809df8-9372-4345-9a73-393960c31950", - "type": "RATING", - "name": "performanceRating", - "label": "Performance Rating", - "description": "Person's Performance Rating", - "icon": "IconStars", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:39:01.997Z", - "updatedAt": "2024-08-05T16:39:01.997Z", - "defaultValue": null, - "options": [ - { - "id": "d0d6de5e-0bf5-4cdd-a599-1f8005723f6e", - "label": "1", - "value": "RATING_1", - "position": 0 - }, - { - "id": "a0c86a34-6914-43b0-a7ab-9fb4f9b9641c", - "label": "2", - "value": "RATING_2", - "position": 1 - }, - { - "id": "750dd542-0172-42da-ae00-50423f0179d3", - "label": "3", - "value": "RATING_3", - "position": 2 - }, - { - "id": "8b935586-9844-49f1-ad92-3e633d613ede", - "label": "4", - "value": "RATING_4", - "position": 3 - }, - { - "id": "eab17ee7-5aa8-40b1-a431-34438ba6b54e", - "label": "5", - "value": "RATING_5", - "position": 4 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6672a066-7a7f-44ae-bb5c-ae3a5e82d835", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Contactโ€™s company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0562d399-7053-4d7f-a415-cabfc889bd16", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6672a066-7a7f-44ae-bb5c-ae3a5e82d835", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "48dba12f-4429-4ee2-9b3a-6df97c45141d", - "name": "people" - } - } - }, - { - "__typename": "field", - "id": "c08e6ba8-b7ef-4fa7-b199-c8e93045f8ee", - "type": "RELATION", - "name": "timelineActivities", - "label": "Events", - "description": "Events linked to the person", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ba3d762d-8fbf-45e5-a958-136a269a396d", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c08e6ba8-b7ef-4fa7-b199-c8e93045f8ee", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "69e1ecef-09d7-4b53-826e-f440ae72d2b7", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "1b21ef27-ba22-46ab-967e-f2d9f780bf8b", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments linked to the contact.", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "4b90ec4b-3199-4cea-9e8b-01498967bd9f", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "1b21ef27-ba22-46ab-967e-f2d9f780bf8b", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "df6ee118-1cb0-4b2e-8668-3693d4d87ae2", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "9fbf2632-6f28-400d-86d7-3cadfbbcf7ac", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "9c2bf923-304d-47b7-beb0-286e3229f6ac", - "type": "TEXT", - "name": "phone", - "label": "Phone", - "description": "Contactโ€™s phone number", - "icon": "IconPhone", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4cd944c2-a252-42c2-93d3-c71d71b4587d", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the contact", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "66551942-e576-4eb7-96c4-f78182f44491", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4cd944c2-a252-42c2-93d3-c71d71b4587d", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b7caceaa-d49a-43c8-8b9b-10dc4298ade5", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "c817d48c-071b-47f0-917c-5e0717678c5c", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "22b21809-4eab-43a9-9ddc-0bbe04daffe3", - "type": "TEXT", - "name": "city", - "label": "City", - "description": "Contactโ€™s city", - "icon": "IconMap", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4227c9a5-6dd3-4de0-9248-e62572afc92b", - "type": "RELATION", - "name": "messageParticipants", - "label": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "f26f3e6e-35bf-474a-9679-fbfbb009d67d", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4227c9a5-6dd3-4de0-9248-e62572afc92b", - "name": "messageParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3779c76a-30a8-45bc-a56a-6bfc084a9b29", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "14c3ddf2-a50b-40c3-9f93-6f3108c0dd72", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Contactโ€™s company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "9048daf8-bd6f-4c83-8887-14c764ec0053", - "type": "TEXT", - "name": "intro", - "label": "Intro", - "description": "Contact's Intro", - "icon": "IconNote", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:39:01.936Z", - "updatedAt": "2024-08-05T16:39:01.936Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1c1c7ffc-3a45-4069-996a-bdfa8f46b037", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the contact", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9d20f0c0-e37b-48ca-bc45-da16461aa547", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "1c1c7ffc-3a45-4069-996a-bdfa8f46b037", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2f2fc7fb-51c5-4084-8d97-13b43b49c68a", - "name": "person" - } - } - }, - { - "__typename": "field", - "id": "f7002609-5760-4ae6-ba29-a8b9066b95de", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": null, - "icon": "IconUser", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:10:31.391Z", - "updatedAt": "2024-08-05T17:10:31.391Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "27f9741d-f967-4b75-affa-240f0f5f8d77", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f7002609-5760-4ae6-ba29-a8b9066b95de", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "23006c79-19fe-4148-9ee4-6db039ebc6fb", - "name": "people" - } - } - }, - { - "__typename": "field", - "id": "fdcffad9-a55c-4a74-9d3e-e0052cb3454e", - "type": "MULTI_SELECT", - "name": "workPrefereance", - "label": "Work Preference", - "description": "Person's Work Preference", - "icon": "IconHome", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:39:01.977Z", - "updatedAt": "2024-08-05T16:39:01.977Z", - "defaultValue": null, - "options": [ - { - "id": "05ae5e3e-80bb-4a89-8539-de88f875384e", - "color": "green", - "label": "On-Site", - "value": "ON_SITE", - "position": 0 - }, - { - "id": "fa7fb51a-3c52-48db-ac7c-ba23daed31cc", - "color": "turquoise", - "label": "Hybrid", - "value": "HYBRID", - "position": 1 - }, - { - "id": "c29d7f0e-f82b-4bff-973a-5081f6d60a39", - "color": "sky", - "label": "Remote Work", - "value": "REMOTE_WORK", - "position": 2 - } - ], - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "note", - "namePlural": "notes", - "labelSingular": "Note", - "labelPlural": "Notes", - "description": "A note", - "icon": "IconNotes", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "2be5c772-b9f3-4851-9d9e-8990f81c475e", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "6c2c95b9-8e58-4956-a796-926fec68c67a", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "854f4b18-7f4a-458a-b4b8-47ab09478625", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Note record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "eb26a816-6dbd-4128-9a5f-fe92dd63ba55", - "type": "RICH_TEXT", - "name": "body", - "label": "Body", - "description": "Note body", - "icon": "IconFilePencil", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "383d3f90-d691-4487-a13d-e80c50fb756e", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the note.", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bc6b24e6-9fcd-43fd-a2ba-c12f5d022132", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "383d3f90-d691-4487-a13d-e80c50fb756e", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "99599532-c0e9-4d62-b4a6-89866e0374be", - "name": "note" - } - } - }, - { - "__typename": "field", - "id": "a723f071-bc95-4a94-84d5-0f5904d88ea7", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ff8e5043-6168-4c65-984d-ef4d28eb76ce", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the note", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a39ebd92-e37e-46d7-b545-aed1945476f2", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ff8e5043-6168-4c65-984d-ef4d28eb76ce", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b4b7114a-8536-438a-8d13-917eae164506", - "name": "note" - } - } - }, - { - "__typename": "field", - "id": "143a13af-842b-4f5d-913c-648472fbfc28", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "38a2a378-bac0-4c4d-bf05-7f9ff995b860", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Note attachments", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ac29383b-d63e-4c0e-b28a-1abc03ab2b5a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "38a2a378-bac0-4c4d-bf05-7f9ff995b860", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e985e32a-532f-4259-828f-cac80c5fc3b8", - "name": "note" - } - } - }, - { - "__typename": "field", - "id": "d6d5a326-5a21-46a6-96b5-c70549f8f937", - "type": "RELATION", - "name": "noteTargets", - "label": "Targets", - "description": "Note targets", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "72daf099-f592-4521-8a4e-febd67309f47", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d6d5a326-5a21-46a6-96b5-c70549f8f937", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b8d93efe-14da-47a2-bb24-96ae2d037b59", - "name": "note" - } - } - }, - { - "__typename": "field", - "id": "2be5c772-b9f3-4851-9d9e-8990f81c475e", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Note title", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0baad27f-ac7b-48a3-b0a7-f2eb9613b2c6", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "message", - "namePlural": "messages", - "labelSingular": "Message", - "labelPlural": "Messages", - "description": "Message", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "314263a0-4be7-4b22-bac1-37c8df91bdb2", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "938b8c3d-af73-43e7-acf9-ac634186d3aa", - "type": "DATE_TIME", - "name": "receivedAt", - "label": "Received At", - "description": "The date the message was received", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2e91d365-c35a-446e-b019-bdf7e51d7d79", - "type": "RELATION", - "name": "messageThread", - "label": "Message Thread Id", - "description": "Message Thread Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "01efe0bd-eda8-494a-b7ea-b4813dcf0b5a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "nameSingular": "message", - "namePlural": "messages" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2e91d365-c35a-446e-b019-bdf7e51d7d79", - "name": "messageThread" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "c62f3148-1b8d-4fa3-ac29-8a20585bcee9", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "11a2bd26-4856-4ab2-916d-86a07beaccd3", - "name": "messages" - } - } - }, - { - "__typename": "field", - "id": "5291bbf6-1c32-47fe-8164-ebd6dca187ad", - "type": "RELATION", - "name": "messageParticipants", - "label": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "099ebe85-572a-4f77-b077-475f97c0d54c", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "nameSingular": "message", - "namePlural": "messages" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5291bbf6-1c32-47fe-8164-ebd6dca187ad", - "name": "messageParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2efee208-73da-4ca1-ba73-19763d507611", - "name": "message" - } - } - }, - { - "__typename": "field", - "id": "232561ae-1e5f-4cde-a093-53597948a567", - "type": "SELECT", - "name": "direction", - "label": "Direction", - "description": "Message Direction", - "icon": "IconDirection", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'INCOMING'", - "options": [ - { - "id": "14216544-33d1-47d0-99a9-717763d49c0f", - "color": "green", - "label": "Incoming", - "value": "INCOMING", - "position": 0 - }, - { - "id": "f1b89e48-1107-45a2-b54a-31a75e76b9b2", - "color": "blue", - "label": "Outgoing", - "value": "OUTGOING", - "position": 1 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c31a967f-0c76-4a07-9299-01ac653b3807", - "type": "TEXT", - "name": "headerMessageId", - "label": "Header message Id", - "description": "Message id from the message header", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "055e1afd-6445-4747-b489-0bb412c42e1e", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "314263a0-4be7-4b22-bac1-37c8df91bdb2", - "type": "TEXT", - "name": "subject", - "label": "Subject", - "description": "Subject", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c9419522-6ce4-48d6-bad8-8d71336ca964", - "type": "UUID", - "name": "messageThreadId", - "label": "Message Thread Id id (foreign key)", - "description": "Message Thread Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "cb6eb250-d255-4446-88f3-bc6b7dd20800", - "type": "RELATION", - "name": "messageChannelMessageAssociations", - "label": "Message Channel Association", - "description": "Messages from the channel.", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "3d3b5a91-f7b7-4c50-98d2-093be343711c", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "nameSingular": "message", - "namePlural": "messages" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "cb6eb250-d255-4446-88f3-bc6b7dd20800", - "name": "messageChannelMessageAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6e2131e3-b688-4b61-99bf-f0b50f100a5f", - "name": "message" - } - } - }, - { - "__typename": "field", - "id": "367b2c39-dca7-48cb-b04a-8dd4e5e489af", - "type": "TEXT", - "name": "text", - "label": "Text", - "description": "Text", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c2c61dcb-08ee-4761-b5d6-53b44fda1431", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "41cf0d75-84f4-4063-a428-d699a731b080", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "f3189217-0595-44ad-a51c-6145a2f7c6ba", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "apiKey", - "namePlural": "apiKeys", - "labelSingular": "Api Key", - "labelPlural": "Api Keys", - "description": "An api key", - "icon": "IconRobot", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "13406939-4334-4820-ada4-3197dedc51b0", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "6a350a28-c036-4018-8caa-97128d74c3d9", - "type": "DATE_TIME", - "name": "revokedAt", - "label": "Revocation date", - "description": "ApiKey revocation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "22031e60-824f-4458-b265-bcae66ae8555", - "type": "DATE_TIME", - "name": "expiresAt", - "label": "Expiration date", - "description": "ApiKey expiration date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "eff85403-ee56-4b40-9d6c-d57197318bd2", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "13406939-4334-4820-ada4-3197dedc51b0", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "ApiKey name", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "75eccaec-4a93-4b74-a844-89b89ca98598", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7f3deab6-000a-40e4-b513-8658642168cd", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "company", - "namePlural": "companies", - "labelSingular": "Company", - "labelPlural": "Companies", - "description": "A company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "2599d01d-02ee-4d55-9063-35c67cc81a0f", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "b229e842-ca29-4c1a-ba74-e69be1f357ac", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "65ed4460-c5b9-4634-bdc1-80f59515a2f6", - "type": "BOOLEAN", - "name": "visaSponsorship", - "label": "Visa Sponsorship", - "description": "Company's Visa Sponsorship Policy", - "icon": "IconBrandVisa", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:39:01.913Z", - "updatedAt": "2024-08-05T16:39:01.913Z", - "defaultValue": false, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a7c771af-639a-4f01-b1fa-3b245c3d4e92", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6cc3a88a-be92-47c7-9f17-8d726bb7f8b6", - "type": "LINKS", - "name": "domainName", - "label": "Domain Name", - "description": "The company website URL. We use this url to fetch the company icon", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ec77df7f-a933-4415-a515-8b4938c9f125", - "type": "LINKS", - "name": "introVideo", - "label": "Intro Video", - "description": "Company's Intro Video", - "icon": "IconVideo", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:39:01.865Z", - "updatedAt": "2024-08-05T16:39:01.865Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3ece1b4d-c052-4b32-bd2a-ba0f8c8b6f3e", - "type": "RELATION", - "name": "accountOwner", - "label": "Account Owner", - "description": "Your team member responsible for managing the company account", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0896a728-e2cf-4032-9af2-a471645e9697", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3ece1b4d-c052-4b32-bd2a-ba0f8c8b6f3e", - "name": "accountOwner" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "de44f939-76d9-4c1a-96aa-7c5a646f2045", - "name": "accountOwnerForCompanies" - } - } - }, - { - "__typename": "field", - "id": "c06142b8-52a9-4b0b-93f6-99e2b5b67ab8", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the company", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c542c9e0-b4b6-4073-aae6-66299868e9fb", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c06142b8-52a9-4b0b-93f6-99e2b5b67ab8", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "979ea933-d8a1-4db6-8c29-5c747a690326", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "b08f7cc5-f0c9-4ab1-a2bd-2733ab95d97b", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Company record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f0c8cde4-5a5d-4a6f-8273-94396a60f918", - "type": "CURRENCY", - "name": "annualRecurringRevenue", - "label": "ARR", - "description": "Annual Recurring Revenue: The actual or estimated annual revenue of the company", - "icon": "IconMoneybag", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "amountMicros": null, - "currencyCode": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5e24424a-db05-468b-bd45-7cab4059be3a", - "type": "NUMBER", - "name": "employees", - "label": "Employees", - "description": "Number of employees in the company", - "icon": "IconUsers", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "63261a95-39ac-4826-bc8b-7fc0fd21a8ad", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the company", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d420db15-3060-4760-afd0-8485c76e53b4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "63261a95-39ac-4826-bc8b-7fc0fd21a8ad", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "cb654210-43c9-4ff7-95ee-8250c1d80e8d", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "cb47633a-1b44-41b9-8bce-16e28616c2ad", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the company", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "f80bfd64-c33d-4488-bc49-1635e092ea3f", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "cb47633a-1b44-41b9-8bce-16e28616c2ad", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "1e4e3b2b-113f-4af3-aed8-94b03785a626", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "c132d454-299e-4885-89b0-de5b824e43e2", - "type": "LINKS", - "name": "linkedinLink", - "label": "Linkedin", - "description": "The company Linkedin account", - "icon": "IconBrandLinkedin", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "64f76ec9-3fae-4a61-b30f-7c646f4fe33d", - "type": "MULTI_SELECT", - "name": "workPolicy", - "label": "Work Policy", - "description": "Company's Work Policy", - "icon": "IconHome", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:39:01.888Z", - "updatedAt": "2024-08-05T16:39:01.888Z", - "defaultValue": null, - "options": [ - { - "id": "2a832e12-073e-4b58-bb71-9f68c00cabda", - "color": "green", - "label": "On-Site", - "value": "ON_SITE", - "position": 0 - }, - { - "id": "231423db-f097-4410-8efc-95bc19f9e87f", - "color": "turquoise", - "label": "Hybrid", - "value": "HYBRID", - "position": 1 - }, - { - "id": "db436d25-f1e3-45aa-9f81-7a5ef84a50ef", - "color": "sky", - "label": "Remote Work", - "value": "REMOTE_WORK", - "position": 2 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "79452277-0e6e-4dc5-b972-470c29bd6d12", - "type": "ADDRESS", - "name": "address", - "label": "Address", - "description": "Address of the company", - "icon": "IconMap", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "addressLat": null, - "addressLng": null, - "addressCity": "''", - "addressState": "''", - "addressCountry": "''", - "addressStreet1": "''", - "addressStreet2": "''", - "addressPostcode": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2599d01d-02ee-4d55-9063-35c67cc81a0f", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "The company name", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4cbb077e-ccce-4892-8fc0-9b9238d7a009", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3a2bd134-5b31-4bde-a64f-d5244a8e6271", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments linked to the company", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "be6051cd-703c-4539-89ed-e643784bad26", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3a2bd134-5b31-4bde-a64f-d5244a8e6271", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6496b8e0-2d8f-493e-8973-fcba2aa84b59", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "1ce2452b-75a2-4989-ad48-f6f696fe1a38", - "type": "LINKS", - "name": "xLink", - "label": "X", - "description": "The company Twitter/X account", - "icon": "IconBrandX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "48dba12f-4429-4ee2-9b3a-6df97c45141d", - "type": "RELATION", - "name": "people", - "label": "People", - "description": "People linked to the company.", - "icon": "IconUsers", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0562d399-7053-4d7f-a415-cabfc889bd16", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "48dba12f-4429-4ee2-9b3a-6df97c45141d", - "name": "people" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6672a066-7a7f-44ae-bb5c-ae3a5e82d835", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "a49c82ff-0483-4acc-9e67-16e121daa11f", - "type": "TEXT", - "name": "myCustomField", - "label": "myCustomField", - "description": null, - "icon": "IconUsers", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T17:09:37.502Z", - "updatedAt": "2024-08-05T17:09:37.502Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0f2127dd-9364-4a9e-a66f-a866eb629d8b", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4d74b886-b359-4c4c-a2c0-692edc8a3273", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the company", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2ed70c2c-b17a-4ed1-9f35-b570139440fa", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4d74b886-b359-4c4c-a2c0-692edc8a3273", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "10150d34-2f00-4642-8a9d-6b0b6ab72562", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "cbd94612-00a2-4efd-8869-93f945e93076", - "type": "RELATION", - "name": "opportunities", - "label": "Opportunities", - "description": "Opportunities linked to the company.", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "67d4ff08-f5e6-4382-8996-67fdc2d02125", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "cbd94612-00a2-4efd-8869-93f945e93076", - "name": "opportunities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "28b61c8a-8437-4770-9b12-f3d0e591bee8", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "ae74690d-94d5-4860-928f-4ed8ea36be1d", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the company", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "31819eaa-5847-4207-b4a3-0ecffefd9332", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ae74690d-94d5-4860-928f-4ed8ea36be1d", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "69b1d954-1ed3-4bf9-b9e1-b886a00953b4", - "name": "company" - } - } - }, - { - "__typename": "field", - "id": "abc8bbb4-cc34-428b-a182-b032c2b3c7ff", - "type": "UUID", - "name": "accountOwnerId", - "label": "Account Owner id (foreign key)", - "description": "Your team member responsible for managing the company account id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "9bbd039b-40e0-4742-879e-0be3cf9b12b7", - "type": "TEXT", - "name": "tagline", - "label": "Tagline", - "description": "Company's Tagline", - "icon": "IconAdCircle", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:39:01.834Z", - "updatedAt": "2024-08-05T16:39:01.834Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f90f1fda-258f-4b52-af93-9f157e2ed187", - "type": "BOOLEAN", - "name": "idealCustomerProfile", - "label": "ICP", - "description": "Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you", - "icon": "IconTarget", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": false, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "ee025446-440d-49ae-8d0e-ad30b6309840", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "viewField", - "namePlural": "viewFields", - "labelSingular": "View Field", - "labelPlural": "View Fields", - "description": "(System) View Fields", - "icon": "IconTag", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "440d28bb-2d5f-4624-8fdd-5476ac84baa5", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "2c466c0c-926a-4722-846c-3bd89c2751da", - "type": "BOOLEAN", - "name": "isVisible", - "label": "Visible", - "description": "View Field visibility", - "icon": "IconEye", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6725c7ad-a704-436a-be67-a4612bc48e37", - "type": "RELATION", - "name": "view", - "label": "View", - "description": "View Field related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "57d32129-b126-417e-98a8-7f1217b29dea", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ee025446-440d-49ae-8d0e-ad30b6309840", - "nameSingular": "viewField", - "namePlural": "viewFields" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6725c7ad-a704-436a-be67-a4612bc48e37", - "name": "view" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6ea01d0e-340e-40e4-a029-89a7cbc07291", - "name": "viewFields" - } - } - }, - { - "__typename": "field", - "id": "21015789-4b34-4a98-8669-1ceb7e408d0c", - "type": "UUID", - "name": "viewId", - "label": "View id (foreign key)", - "description": "View Field related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4ce74348-740e-49e4-b13f-05a7810fc021", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "be8726a0-5584-4e78-82b8-4be201cd8870", - "type": "UUID", - "name": "fieldMetadataId", - "label": "Field Metadata Id", - "description": "View Field target field", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6a582800-ad4d-4a5b-ac51-9a4a28f7b348", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "619cb257-468c-4496-8db7-3d119013b5a8", - "type": "NUMBER", - "name": "size", - "label": "Size", - "description": "View Field size", - "icon": "IconEye", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c82925e9-c98a-497d-9d70-c39158402171", - "type": "NUMBER", - "name": "position", - "label": "Position", - "description": "View Field position", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "440d28bb-2d5f-4624-8fdd-5476ac84baa5", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - } - ] - }, - { - "id": "7943376e-fa16-41e8-9e7e-0cfd8fc104ad", - "dataSourceId": "92760295-6c51-4ebc-9a59-0000e1f24f85", - "nameSingular": "viewGroup", - "namePlural": "viewGroups", - "labelSingular": "View Group", - "labelPlural": "View Groups", - "description": "(System) View Groups", - "icon": "IconTag", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "labelIdentifierFieldMetadataId": "0e8d59e3-1121-469d-8c89-240bc16ebd98", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "id": "0e8d59e3-1121-469d-8c89-240bc16ebd98", - "type": "UUID", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "id": "2fca1d8d-ec8a-4a58-807d-290fa3f1b2b3", - "type": "UUID", - "label": "Field Metadata Id", - "description": "View Group target field", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "id": "3a2f5200-45ba-46a5-aed2-fa5bb5ced269", - "type": "DATE_TIME", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "id": "41d8e614-13ec-4c47-938b-65909a1bb4f8", - "type": "BOOLEAN", - "label": "Visible", - "description": "View Group visibility", - "icon": "IconEye", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "id": "4a712eb7-3f90-4510-b46f-02709199cde5", - "type": "TEXT", - "label": "Field Value", - "description": "Group by this field value", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "id": "605598bd-d132-40a9-afe7-c76af518e502", - "type": "NUMBER", - "label": "Position", - "description": "View Field position", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null - }, - { - "id": "6c9b7054-21c2-4589-a667-63ccd8a178ad", - "type": "DATE_TIME", - "label": "Deleted at", - "description": "Date when the record was deleted", - "icon": "IconCalendarMinus", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "id": "a988bd1c-4996-4281-bdc3-3bcd8b545037", - "type": "RELATION", - "label": "View", - "description": "View Group related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "relationId": "1db712c0-9e0d-49da-ade5-f27abfb323a3", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "id": "7943376e-fa16-41e8-9e7e-0cfd8fc104ad", - "nameSingular": "viewGroup", - "namePlural": "viewGroups" - }, - "sourceFieldMetadata": { - "id": "a988bd1c-4996-4281-bdc3-3bcd8b545037", - "name": "view" - }, - "targetObjectMetadata": { - "id": "54436344-c24c-4d9e-9302-32a8c7171638", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "id": "1f1c7735-44e6-4d1e-8854-1d485bf60bf8", - "name": "viewGroups" - } - } - }, - { - "id": "ac1b8c3e-ce1e-47f5-82a2-0a20d2b36ae8", - "type": "DATE_TIME", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "id": "ea6277dc-7642-4d41-b574-72160319864f", - "type": "UUID", - "label": "View id (foreign key)", - "description": "View Group related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-09-20T09:44:11.774Z", - "updatedAt": "2024-09-20T09:44:11.774Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "e0bf07c5-4729-46ab-aa15-480f26999477", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "webhook", - "namePlural": "webhooks", - "labelSingular": "Webhook", - "labelPlural": "Webhooks", - "description": "A webhook", - "icon": "IconRobot", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "1b437ef4-0bcf-4a8d-9ebf-f25ffccb3d54", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "1b437ef4-0bcf-4a8d-9ebf-f25ffccb3d54", - "type": "TEXT", - "name": "targetUrl", - "label": "Target Url", - "description": "Webhook target url", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b3ce5dca-00ec-4ea3-ae2a-4f08cac53a3b", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e11a3ed9-2576-4bca-bc8d-4559b247f466", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "670a4724-178c-44b4-94a6-30e11f015cb0", - "type": "TEXT", - "name": "operation", - "label": "Operation", - "description": "Webhook operation", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a3d5bfcd-ed28-4dcb-a650-c474de21ec58", - "type": "TEXT", - "name": "description", - "label": "Description", - "description": null, - "icon": "IconInfo", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7ab0a589-fe02-4e80-8ae9-4ea51d3b8907", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "favorite", - "namePlural": "favorites", - "labelSingular": "Favorite", - "labelPlural": "Favorites", - "description": "A favorite", - "icon": "IconHeart", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "18c9dce6-51cb-4326-890f-156a3e19a88d", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "3d4cd287-7435-4574-aedd-09f6f5b34afb", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "Favorite task id foreign key", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3214e46b-3728-42ea-bdcc-dc8d0d15e64c", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject ID (foreign key)", - "description": "Favorite myCustomObject id foreign key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.167Z", - "updatedAt": "2024-08-05T17:09:54.167Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b5845911-ec5b-4032-a60e-bcdbd1a6b1f2", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Favorite workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "407a2cbc-6c15-41dd-942c-5322d273bec3", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Favorite workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "b0f40da3-1fda-4803-be21-14a2755bc834", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "407a2cbc-6c15-41dd-942c-5322d273bec3", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f537669a-4524-4dfc-91d3-79438e2a481e", - "name": "favorites" - } - } - }, - { - "__typename": "field", - "id": "9c99e6fc-aed4-44f6-a231-1d1ce3f218ab", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Favorite company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "87c2dd65-2c54-4184-9a19-0bdce7781a3f", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": "Favorite myCustomObject", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.168Z", - "updatedAt": "2024-08-05T17:09:54.168Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "143c2257-721f-46eb-8114-987a70979146", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "87c2dd65-2c54-4184-9a19-0bdce7781a3f", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c475ebbc-f86b-4956-9d67-d0bb62062408", - "name": "favorites" - } - } - }, - { - "__typename": "field", - "id": "0c955466-cf7a-43b5-a0c5-4703b9703193", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "18c9dce6-51cb-4326-890f-156a3e19a88d", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3464f1ce-34d3-4bf1-ac74-072bf750cc5c", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "Favorite opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c1110f68-bbc9-4dbf-aae4-c6e5e2569240", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3464f1ce-34d3-4bf1-ac74-072bf750cc5c", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a1a7bb38-6f6c-4bdb-803f-804cdd97cb77", - "name": "favorites" - } - } - }, - { - "__typename": "field", - "id": "9520f201-9dec-4e48-ba68-4fb9f2a4c662", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "Favorite note id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b4b7114a-8536-438a-8d13-917eae164506", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "Favorite note", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a39ebd92-e37e-46d7-b545-aed1945476f2", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b4b7114a-8536-438a-8d13-917eae164506", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ff8e5043-6168-4c65-984d-ef4d28eb76ce", - "name": "favorites" - } - } - }, - { - "__typename": "field", - "id": "4f2c079a-0221-47e9-ab92-68f529726424", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Favorite person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e7201f19-bfa7-42e1-9550-7c848a842ecc", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "Favorite task", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "32b10a0d-0ca4-4027-be9e-ab8d8be608d1", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e7201f19-bfa7-42e1-9550-7c848a842ecc", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "80fe7004-903e-4bdd-985d-9ef7e6acd793", - "name": "favorites" - } - } - }, - { - "__typename": "field", - "id": "ce728fc3-01f7-4e2b-b71c-05ed44b50836", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "Favorite opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "674ead9c-88e9-481c-b5b3-dec4a450d67e", - "type": "NUMBER", - "name": "position", - "label": "Position", - "description": "Favorite position", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5966af59-a371-42a3-85de-1b9a8e9768c9", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "69b1d954-1ed3-4bf9-b9e1-b886a00953b4", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Favorite company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "31819eaa-5847-4207-b4a3-0ecffefd9332", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "69b1d954-1ed3-4bf9-b9e1-b886a00953b4", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ae74690d-94d5-4860-928f-4ed8ea36be1d", - "name": "favorites" - } - } - }, - { - "__typename": "field", - "id": "b7caceaa-d49a-43c8-8b9b-10dc4298ade5", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Favorite person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "66551942-e576-4eb7-96c4-f78182f44491", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b7caceaa-d49a-43c8-8b9b-10dc4298ade5", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4cd944c2-a252-42c2-93d3-c71d71b4587d", - "name": "favorites" - } - } - } - ] - }, - { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations", - "labelSingular": "Message Channel Message Association", - "labelPlural": "Message Channel Message Associations", - "description": "Message Synced with a Message Channel", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "f3df53cf-dad8-4880-9b06-cd24188c77e5", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "53f2d078-7fd1-4ba7-bae7-e58398dd2d6e", - "type": "UUID", - "name": "messageThreadId", - "label": "Message Thread Id id (foreign key)", - "description": "Message Thread Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "70a1610d-08ea-44eb-a453-02c1749e6e0c", - "type": "UUID", - "name": "messageId", - "label": "Message Id id (foreign key)", - "description": "Message Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "930b7926-639c-43c1-85ba-4c185a9ad5d3", - "type": "RELATION", - "name": "messageThread", - "label": "Message Thread Id", - "description": "Message Thread Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "6be1cb67-30fe-41b3-8695-09cf6cbef18a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "930b7926-639c-43c1-85ba-4c185a9ad5d3", - "name": "messageThread" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "c62f3148-1b8d-4fa3-ac29-8a20585bcee9", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "bfa74ef1-b2d8-4720-a9bd-3084ceb005f3", - "name": "messageChannelMessageAssociations" - } - } - }, - { - "__typename": "field", - "id": "aa5c5146-05fc-4f48-a90a-1570bdc91ed9", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "470b0e39-1f5b-45e3-a092-c0f247539933", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6e2131e3-b688-4b61-99bf-f0b50f100a5f", - "type": "RELATION", - "name": "message", - "label": "Message Id", - "description": "Message Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "3d3b5a91-f7b7-4c50-98d2-093be343711c", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6e2131e3-b688-4b61-99bf-f0b50f100a5f", - "name": "message" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "nameSingular": "message", - "namePlural": "messages" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "cb6eb250-d255-4446-88f3-bc6b7dd20800", - "name": "messageChannelMessageAssociations" - } - } - }, - { - "__typename": "field", - "id": "621d12d5-80e5-4d0e-9c93-2433ff447dda", - "type": "UUID", - "name": "messageChannelId", - "label": "Message Channel Id id (foreign key)", - "description": "Message Channel Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ec0e8151-40a6-4695-a3bd-68794b26ea40", - "type": "TEXT", - "name": "messageExternalId", - "label": "Message External Id", - "description": "Message id from the messaging provider", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f3df53cf-dad8-4880-9b06-cd24188c77e5", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f2561dd5-c695-4635-816c-27175470b285", - "type": "RELATION", - "name": "messageChannel", - "label": "Message Channel Id", - "description": "Message Channel Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e420b731-e1e1-425a-ac7a-488d37d1958b", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f2561dd5-c695-4635-816c-27175470b285", - "name": "messageChannel" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "219d7acf-5934-44dc-8789-62ade666cb43", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2682d5c3-f05e-4c5c-87eb-bb1a6c0c37bb", - "name": "messageChannelMessageAssociations" - } - } - }, - { - "__typename": "field", - "id": "b93119d3-d244-42a4-8d7d-8c312f683f55", - "type": "TEXT", - "name": "messageThreadExternalId", - "label": "Thread External Id", - "description": "Thread id from the messaging provider", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "activity", - "namePlural": "activities", - "labelSingular": "Activity", - "labelPlural": "Activities", - "description": "An activity", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "7690dd7a-0c0d-444a-845a-2ea9ea4fa54d", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "beac3449-af10-43a2-9abb-276a798df3de", - "type": "RELATION", - "name": "author", - "label": "Author", - "description": "Activity author", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "6d4e8025-7ee9-4079-ae80-b18de7b5ff4e", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "beac3449-af10-43a2-9abb-276a798df3de", - "name": "author" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5e889b07-de1e-47f0-aeb9-301a684bd6a4", - "name": "authoredActivities" - } - } - }, - { - "__typename": "field", - "id": "9045116d-0fed-433c-80a4-f4296db72ae5", - "type": "RELATION", - "name": "comments", - "label": "Comments", - "description": "Activity comments", - "icon": "IconComment", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "00b07eda-840c-4a91-a8f7-365c008a2ea1", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "9045116d-0fed-433c-80a4-f4296db72ae5", - "name": "comments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "3af96291-b873-402f-bd90-f4731984c8dd", - "nameSingular": "comment", - "namePlural": "comments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "88c3a2b9-b59a-413a-b2d3-44b151185929", - "name": "activity" - } - } - }, - { - "__typename": "field", - "id": "bec64b6b-d141-45fe-a166-4f5d9ae578ce", - "type": "UUID", - "name": "authorId", - "label": "Author id (foreign key)", - "description": "Activity author id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6e363f5f-737c-4b94-b5ce-5fe034330f47", - "type": "TEXT", - "name": "body", - "label": "Body", - "description": "Activity body", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "605e8c91-456d-4a53-906c-df02d6425362", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d8477f91-7cb2-4455-b2c3-911dcb4c464f", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "8958f22c-ba22-42f5-8db9-11ea3df92c5b", - "type": "UUID", - "name": "assigneeId", - "label": "Assignee id (foreign key)", - "description": "Activity assignee id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "41f8fd90-2de9-402f-8b37-fb023d318de2", - "type": "RELATION", - "name": "activityTargets", - "label": "Targets", - "description": "Activity targets", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "16017cba-688e-4483-a258-9cef3999cbbf", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "41f8fd90-2de9-402f-8b37-fb023d318de2", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "aa1c7e04-31c1-4b62-8451-6b32926cab47", - "name": "activity" - } - } - }, - { - "__typename": "field", - "id": "ea9aa19c-22d8-4b72-83ff-78d9653c27c4", - "type": "RELATION", - "name": "assignee", - "label": "Assignee", - "description": "Activity assignee", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "cf9ac76a-9f22-4252-a00a-63cc45fcabc4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ea9aa19c-22d8-4b72-83ff-78d9653c27c4", - "name": "assignee" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a147a0df-eb28-4259-a304-0460f92adf30", - "name": "assignedActivities" - } - } - }, - { - "__typename": "field", - "id": "7690dd7a-0c0d-444a-845a-2ea9ea4fa54d", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Activity title", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "918033d1-237e-45fd-960f-3fa4f0e45292", - "type": "DATE_TIME", - "name": "reminderAt", - "label": "Reminder Date", - "description": "Activity reminder date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5c749bc8-9331-4744-8697-a03a4ad46a3d", - "type": "TEXT", - "name": "type", - "label": "Type", - "description": "Activity type", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'Note'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7b24a2df-8118-4dea-862b-ec21e5a12e47", - "type": "DATE_TIME", - "name": "completedAt", - "label": "Completion Date", - "description": "Activity completion date", - "icon": "IconCheck", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "29a1593e-7704-4e43-ae88-507b1ff0febb", - "type": "DATE_TIME", - "name": "dueAt", - "label": "Due Date", - "description": "Activity due date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d9f1711b-a8b1-48ee-9f81-503bbf945b87", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Activity attachments", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "23518310-2443-4907-8ac6-b77bf340d99d", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d9f1711b-a8b1-48ee-9f81-503bbf945b87", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "394c0644-d8bd-44a8-82c9-6e2a4c9aa19c", - "name": "activity" - } - } - }, - { - "__typename": "field", - "id": "2d969fc8-07b3-470e-8bb5-b6e78fbfb22a", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "c62f3148-1b8d-4fa3-ac29-8a20585bcee9", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "messageThread", - "namePlural": "messageThreads", - "labelSingular": "Message Thread", - "labelPlural": "Message Threads", - "description": "Message Thread", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "260b37ba-7451-488f-ae65-a7143aa694e8", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "f05ccb65-325b-4bef-b946-695e598092b5", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "bfa74ef1-b2d8-4720-a9bd-3084ceb005f3", - "type": "RELATION", - "name": "messageChannelMessageAssociations", - "label": "Message Channel Association", - "description": "Messages from the channel", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "6be1cb67-30fe-41b3-8695-09cf6cbef18a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "c62f3148-1b8d-4fa3-ac29-8a20585bcee9", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "bfa74ef1-b2d8-4720-a9bd-3084ceb005f3", - "name": "messageChannelMessageAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "930b7926-639c-43c1-85ba-4c185a9ad5d3", - "name": "messageThread" - } - } - }, - { - "__typename": "field", - "id": "463d59e6-add9-494a-a88f-a280329fb16e", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "260b37ba-7451-488f-ae65-a7143aa694e8", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "11a2bd26-4856-4ab2-916d-86a07beaccd3", - "type": "RELATION", - "name": "messages", - "label": "Messages", - "description": "Messages from the thread.", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "01efe0bd-eda8-494a-b7ea-b4813dcf0b5a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "c62f3148-1b8d-4fa3-ac29-8a20585bcee9", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "11a2bd26-4856-4ab2-916d-86a07beaccd3", - "name": "messages" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "nameSingular": "message", - "namePlural": "messages" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2e91d365-c35a-446e-b019-bdf7e51d7d79", - "name": "messageThread" - } - } - } - ] - }, - { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "noteTarget", - "namePlural": "noteTargets", - "labelSingular": "Note Target", - "labelPlural": "Note Targets", - "description": "A note target", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "96b428bf-6cda-44ec-a774-764075c44326", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "c60efe10-abb9-4c1f-8a27-e97884770401", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "NoteTarget person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "96b428bf-6cda-44ec-a774-764075c44326", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e1b3d8bb-787a-4f2c-a6b0-5384f7ce19fe", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f4814caa-d470-415b-83e0-015903b68bca", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject ID (foreign key)", - "description": "NoteTarget myCustomObject id foreign key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.176Z", - "updatedAt": "2024-08-05T17:09:54.176Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e884eac3-4cbd-40af-970e-a34b409c0acd", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": "NoteTarget myCustomObject", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.177Z", - "updatedAt": "2024-08-05T17:09:54.177Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "4f4d3969-913b-478c-a41d-9daffc9b2255", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e884eac3-4cbd-40af-970e-a34b409c0acd", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "87631266-4a85-4c49-82d1-90d1805c3de6", - "name": "noteTargets" - } - } - }, - { - "__typename": "field", - "id": "6267cb8e-04e2-48ce-a479-3108b2bf1801", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c4946aa0-9c73-4daf-af28-85fff088525c", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "NoteTarget opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b8d93efe-14da-47a2-bb24-96ae2d037b59", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "NoteTarget note", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "72daf099-f592-4521-8a4e-febd67309f47", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b8d93efe-14da-47a2-bb24-96ae2d037b59", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d6d5a326-5a21-46a6-96b5-c70549f8f937", - "name": "noteTargets" - } - } - }, - { - "__typename": "field", - "id": "18504863-6684-44f8-afa6-12e2eae7f428", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "NoteTarget company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "459cd941-1db8-4e61-af0e-35812736cfd1", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "NoteTarget opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a0fa9159-85ab-47c5-bdac-46f7acbb78a7", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "459cd941-1db8-4e61-af0e-35812736cfd1", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "8ee18f64-937c-489b-893c-80bc44f9c105", - "name": "noteTargets" - } - } - }, - { - "__typename": "field", - "id": "cb654210-43c9-4ff7-95ee-8250c1d80e8d", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "NoteTarget company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d420db15-3060-4760-afd0-8485c76e53b4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "cb654210-43c9-4ff7-95ee-8250c1d80e8d", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "63261a95-39ac-4826-bc8b-7fc0fd21a8ad", - "name": "noteTargets" - } - } - }, - { - "__typename": "field", - "id": "2d7a63c8-3a4f-4e20-aa73-f417ee714632", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "NoteTarget note id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2f2fc7fb-51c5-4084-8d97-13b43b49c68a", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "NoteTarget person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9d20f0c0-e37b-48ca-bc45-da16461aa547", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2f2fc7fb-51c5-4084-8d97-13b43b49c68a", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "1c1c7ffc-3a45-4069-996a-bdfa8f46b037", - "name": "noteTargets" - } - } - } - ] - }, - { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "opportunity", - "namePlural": "opportunities", - "labelSingular": "Opportunity", - "labelPlural": "Opportunities", - "description": "An opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "945ed2b8-974b-47cd-a40e-63dd1130919e", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "40cdd413-5239-4887-b5e6-eb32eb1d95e3", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the opportunity", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e594dda4-55fc-46ba-8108-5f672a5b1301", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "40cdd413-5239-4887-b5e6-eb32eb1d95e3", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "705e4379-9ba2-4853-b267-c86dad461dd7", - "name": "opportunity" - } - } - }, - { - "__typename": "field", - "id": "8ee18f64-937c-489b-893c-80bc44f9c105", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the opportunity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a0fa9159-85ab-47c5-bdac-46f7acbb78a7", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "8ee18f64-937c-489b-893c-80bc44f9c105", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "459cd941-1db8-4e61-af0e-35812736cfd1", - "name": "opportunity" - } - } - }, - { - "__typename": "field", - "id": "b32abba8-6801-4bf2-8c62-748fb0f2c224", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Opportunity company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a1a7bb38-6f6c-4bdb-803f-804cdd97cb77", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the opportunity", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c1110f68-bbc9-4dbf-aae4-c6e5e2569240", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a1a7bb38-6f6c-4bdb-803f-804cdd97cb77", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3464f1ce-34d3-4bf1-ac74-072bf750cc5c", - "name": "opportunity" - } - } - }, - { - "__typename": "field", - "id": "b32af3b6-330f-405b-b9ce-6156797f836a", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments linked to the opportunity", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0ee1046a-3f9f-477e-8811-5d29021eca38", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b32af3b6-330f-405b-b9ce-6156797f836a", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "31bbc876-619d-4444-b954-9b6c66343314", - "name": "opportunity" - } - } - }, - { - "__typename": "field", - "id": "59d9b42a-aedf-4bd4-89bb-79416d1c45ba", - "type": "DATE_TIME", - "name": "closeDate", - "label": "Close date", - "description": "Opportunity close date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "23598371-f3eb-4dc3-9254-c8150639fa2d", - "type": "SELECT", - "name": "stage", - "label": "Stage", - "description": "Opportunity stage", - "icon": "IconProgressCheck", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'NEW'", - "options": [ - { - "id": "20e58665-2b7c-464b-b25e-6ed2f27cc94a", - "color": "red", - "label": "New", - "value": "NEW", - "position": 0 - }, - { - "id": "9c2a17fd-65ad-4b55-82e1-eaf62da947ee", - "color": "purple", - "label": "Screening", - "value": "SCREENING", - "position": 1 - }, - { - "id": "a0e93b94-c12f-433d-9971-2e7a6d55881b", - "color": "sky", - "label": "Meeting", - "value": "MEETING", - "position": 2 - }, - { - "id": "7c510ced-cbea-4e39-98a0-35b449a1ac28", - "color": "turquoise", - "label": "Proposal", - "value": "PROPOSAL", - "position": 3 - }, - { - "id": "ff6973c2-a8eb-4e37-b742-259ea74893dd", - "color": "yellow", - "label": "Customer", - "value": "CUSTOMER", - "position": 4 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f1e9a8ea-6c0a-4bf4-acef-86d0855ac9ae", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f28ff3e4-c792-4f94-84d2-4df0572d29cb", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "64c6f5b9-63c4-4079-bd72-b54bf2b0c1ae", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "71341870-4e4d-4399-ab74-8f277047664e", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the opportunity", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d2a53784-3664-49ab-983e-5ad5bf15dbd0", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "71341870-4e4d-4399-ab74-8f277047664e", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "997b0618-d3f7-4e5c-8c8e-2ba1bad10549", - "name": "opportunity" - } - } - }, - { - "__typename": "field", - "id": "713c39d7-63e2-4b0e-bd5c-d240b840255e", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the opportunity.", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "61e4edee-13d4-4edd-9101-b9dc5ea5506a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "713c39d7-63e2-4b0e-bd5c-d240b840255e", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2c3baa6b-7ffe-470e-bd9d-0532e2bce3ac", - "name": "opportunity" - } - } - }, - { - "__typename": "field", - "id": "945ed2b8-974b-47cd-a40e-63dd1130919e", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "The opportunity name", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "eeeecdec-299d-4ac1-8475-180b9de43351", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "28b61c8a-8437-4770-9b12-f3d0e591bee8", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Opportunity company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "67d4ff08-f5e6-4382-8996-67fdc2d02125", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "28b61c8a-8437-4770-9b12-f3d0e591bee8", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "cbd94612-00a2-4efd-8869-93f945e93076", - "name": "opportunities" - } - } - }, - { - "__typename": "field", - "id": "dc7898b0-d2b7-4910-bedc-a6fe8eb4c41e", - "type": "RELATION", - "name": "pointOfContact", - "label": "Point of Contact", - "description": "Opportunity point of contact", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "3e039f55-e535-406a-8a80-185123910b7a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "dc7898b0-d2b7-4910-bedc-a6fe8eb4c41e", - "name": "pointOfContact" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e1ecbeb4-76cb-4f9a-8829-ac0665854c69", - "name": "pointOfContactForOpportunities" - } - } - }, - { - "__typename": "field", - "id": "35f32ceb-5f9c-4106-84e1-a480e015fb6f", - "type": "UUID", - "name": "pointOfContactId", - "label": "Point of Contact id (foreign key)", - "description": "Opportunity point of contact id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d4fdbcc2-9de3-4ec8-a3d2-6126f01cd930", - "type": "CURRENCY", - "name": "amount", - "label": "Amount", - "description": "Opportunity amount", - "icon": "IconCurrencyDollar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "amountMicros": null, - "currencyCode": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6fe0ff62-8159-4a5f-b0cd-2ca1dfcf5fb7", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Opportunity record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants", - "labelSingular": "Message Participant", - "labelPlural": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "3ad8eb5c-c148-43f4-89b9-3152b77854e5", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "a7036ff9-a86d-4290-9f2a-cc360c86fe1e", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "94c22c13-b00a-4f60-b2d2-f34b9efe6aa2", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a7036ff9-a86d-4290-9f2a-cc360c86fe1e", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "dd7ee456-52bf-4335-bee9-7ba18a1e9a09", - "name": "messageParticipants" - } - } - }, - { - "__typename": "field", - "id": "3779c76a-30a8-45bc-a56a-6bfc084a9b29", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "f26f3e6e-35bf-474a-9679-fbfbb009d67d", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3779c76a-30a8-45bc-a56a-6bfc084a9b29", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4227c9a5-6dd3-4de0-9248-e62572afc92b", - "name": "messageParticipants" - } - } - }, - { - "__typename": "field", - "id": "2efee208-73da-4ca1-ba73-19763d507611", - "type": "RELATION", - "name": "message", - "label": "Message", - "description": "Message", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "099ebe85-572a-4f77-b077-475f97c0d54c", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2efee208-73da-4ca1-ba73-19763d507611", - "name": "message" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f5a97cba-781d-4665-9dea-0eda6d687a99", - "nameSingular": "message", - "namePlural": "messages" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5291bbf6-1c32-47fe-8164-ebd6dca187ad", - "name": "messageParticipants" - } - } - }, - { - "__typename": "field", - "id": "199bc901-b741-42c5-85dd-4869ce61c879", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e218128f-4d7f-4aa8-9dd2-28849b6c538b", - "type": "TEXT", - "name": "displayName", - "label": "Display Name", - "description": "Display Name", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "71f33a61-2df2-4e23-bdd4-175e572e87ed", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "25cbd116-0230-46f6-88a7-4943ac810ec7", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3ad8eb5c-c148-43f4-89b9-3152b77854e5", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0e8a37ac-fd49-4d07-9695-7a156b5469b7", - "type": "SELECT", - "name": "role", - "label": "Role", - "description": "Role", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'from'", - "options": [ - { - "id": "333b63f9-8a5a-4532-ac53-c03b300fc23e", - "color": "green", - "label": "From", - "value": "from", - "position": 0 - }, - { - "id": "0e980644-befb-4f91-91db-b566c8ff5533", - "color": "blue", - "label": "To", - "value": "to", - "position": 1 - }, - { - "id": "662e29a9-43bd-40ad-a4b7-e728cc2489f0", - "color": "orange", - "label": "Cc", - "value": "cc", - "position": 2 - }, - { - "id": "c3405807-634e-4195-bc58-5d4f0eae8b29", - "color": "red", - "label": "Bcc", - "value": "bcc", - "position": 3 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "dd20415d-fb86-40b6-99bb-25b128d81776", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6c0f1e3e-8aea-4e5d-9298-089a0a3dd0de", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4bef8eac-ecb2-4de2-af0e-7c9e1fca1f06", - "type": "UUID", - "name": "messageId", - "label": "Message id (foreign key)", - "description": "Message id foreign key", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "attachment", - "namePlural": "attachments", - "labelSingular": "Attachment", - "labelPlural": "Attachments", - "description": "An attachment", - "icon": "IconFileImport", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "13649d8b-94c8-40ce-9dbb-9c7ab32a2ed8", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "e985e32a-532f-4259-828f-cac80c5fc3b8", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "Attachment note", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ac29383b-d63e-4c0e-b28a-1abc03ab2b5a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e985e32a-532f-4259-828f-cac80c5fc3b8", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "38a2a378-bac0-4c4d-bf05-7f9ff995b860", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "2352488a-4c22-42ac-9645-2ef1cedc80b5", - "type": "UUID", - "name": "activityId", - "label": "Activity id (foreign key)", - "description": "Attachment activity id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c2ace3e1-07b9-4073-b27d-023586dfc0d2", - "type": "TEXT", - "name": "type", - "label": "Type", - "description": "Attachment type", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "df6ee118-1cb0-4b2e-8668-3693d4d87ae2", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Attachment person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "4b90ec4b-3199-4cea-9e8b-01498967bd9f", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "df6ee118-1cb0-4b2e-8668-3693d4d87ae2", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "1b21ef27-ba22-46ab-967e-f2d9f780bf8b", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "53d23be1-1b1f-4782-900c-3501e5ff1e96", - "type": "TEXT", - "name": "fullPath", - "label": "Full path", - "description": "Attachment full path", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "41623677-7310-44a0-80a2-4a19880bccd1", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "Attachment opportunity id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0f978508-2e4a-4bc6-bbaf-87ad6acbe08a", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "Attachment task", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "aeca7339-1ff5-45cb-b9f4-d8bd9ec572d9", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0f978508-2e4a-4bc6-bbaf-87ad6acbe08a", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "7e719850-091f-4876-87b0-4eb5fd9847af", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "263a372a-3df9-4c57-9e5b-80db73c7b56e", - "type": "UUID", - "name": "authorId", - "label": "Author id (foreign key)", - "description": "Attachment author id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b86a10c8-fc7f-436a-8b05-7b6c618357a7", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d9a522cf-9b09-496d-b5be-a57589c42bc5", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3d2bbb4e-e908-4bc5-97d7-a152dd7652bf", - "type": "RELATION", - "name": "author", - "label": "Author", - "description": "Attachment author", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c86299f2-7210-4c89-a2ab-29e17f21edc8", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3d2bbb4e-e908-4bc5-97d7-a152dd7652bf", - "name": "author" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "1b2d1e2c-290d-4a0e-adf9-192e5fac103c", - "name": "authoredAttachments" - } - } - }, - { - "__typename": "field", - "id": "31bbc876-619d-4444-b954-9b6c66343314", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "Attachment opportunity", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0ee1046a-3f9f-477e-8811-5d29021eca38", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "31bbc876-619d-4444-b954-9b6c66343314", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b32af3b6-330f-405b-b9ce-6156797f836a", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "3ec97541-3618-453f-b498-e2a0b2d7aee4", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "Attachment note id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2e1b103b-a75b-4ebc-8219-4b59027bd3fd", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": "Attachment myCustomObject", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.173Z", - "updatedAt": "2024-08-05T17:09:54.173Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bd914a9e-b1f8-43c6-af60-3afe46518988", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2e1b103b-a75b-4ebc-8219-4b59027bd3fd", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a4580aee-6fb4-4b5d-87c0-daf24745ca13", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "c538c173-a98b-4019-b956-ab997255a429", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "13649d8b-94c8-40ce-9dbb-9c7ab32a2ed8", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "Attachment name", - "icon": "IconFileUpload", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "fffa8f0c-6e71-42fa-8127-50c27909efe2", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "Attachment task id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6496b8e0-2d8f-493e-8973-fcba2aa84b59", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Attachment company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "be6051cd-703c-4539-89ed-e643784bad26", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6496b8e0-2d8f-493e-8973-fcba2aa84b59", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3a2bd134-5b31-4bde-a64f-d5244a8e6271", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "827ed149-e80e-4d73-a687-23e005e20670", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Attachment person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "352954a9-daf9-4d56-9360-51587debc379", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject ID (foreign key)", - "description": "Attachment myCustomObject id foreign key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.172Z", - "updatedAt": "2024-08-05T17:09:54.172Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "394c0644-d8bd-44a8-82c9-6e2a4c9aa19c", - "type": "RELATION", - "name": "activity", - "label": "Activity", - "description": "Attachment activity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "23518310-2443-4907-8ac6-b77bf340d99d", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "394c0644-d8bd-44a8-82c9-6e2a4c9aa19c", - "name": "activity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d9f1711b-a8b1-48ee-9f81-503bbf945b87", - "name": "attachments" - } - } - }, - { - "__typename": "field", - "id": "86007c49-92a3-4b62-aa0a-5d6418675b0a", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Attachment company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "task", - "namePlural": "tasks", - "labelSingular": "Task", - "labelPlural": "Tasks", - "description": "A task", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "3a9c10bc-f466-42e3-aa9d-203c8a946955", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "4e474cea-2549-45bc-9c60-4eecff56a186", - "type": "SELECT", - "name": "status", - "label": "Status", - "description": "Task status", - "icon": "IconCheck", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'TODO'", - "options": [ - { - "id": "d6af8120-3e3d-49ba-b6ac-3089e3446b6c", - "color": "sky", - "label": "To do", - "value": "TODO", - "position": 0 - }, - { - "id": "0d66033b-96f3-4128-8182-a25751a50ce2", - "color": "purple", - "label": "In progress", - "value": "IN_PROGESS", - "position": 1 - }, - { - "id": "8e897afd-d233-4943-92c3-89e8979faa9c", - "color": "green", - "label": "Done", - "value": "DONE", - "position": 1 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "53347f0d-658a-45b0-91b5-2088adbeaaf0", - "type": "RELATION", - "name": "assignee", - "label": "Assignee", - "description": "Task assignee", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5527b9f6-55ec-4efd-b244-03e91b01e91b", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "53347f0d-658a-45b0-91b5-2088adbeaaf0", - "name": "assignee" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fefca31b-ba53-4860-b04e-5b9944587693", - "name": "assignedTasks" - } - } - }, - { - "__typename": "field", - "id": "9233de38-fac9-4df0-bb13-d68c14389a59", - "type": "UUID", - "name": "assigneeId", - "label": "Assignee id (foreign key)", - "description": "Task assignee id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "886d752e-cc9d-43c5-8e73-ee99943ae04c", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7f33f6b1-1c59-4860-ba51-ed0bc3356f06", - "type": "RICH_TEXT", - "name": "body", - "label": "Body", - "description": "Task body", - "icon": "IconFilePencil", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "90f8da4c-865b-4ebe-ae5c-d4377081d8d5", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c7759c16-3319-40b2-873d-84e3d4a1d3ad", - "type": "DATE_TIME", - "name": "dueAt", - "label": "Due Date", - "description": "Task due date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a6c74223-e44c-4677-bde7-e3e06c28fe01", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Task record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7e719850-091f-4876-87b0-4eb5fd9847af", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Task attachments", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "aeca7339-1ff5-45cb-b9f4-d8bd9ec572d9", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "7e719850-091f-4876-87b0-4eb5fd9847af", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0f978508-2e4a-4bc6-bbaf-87ad6acbe08a", - "name": "task" - } - } - }, - { - "__typename": "field", - "id": "f1ed6bba-53bc-4f9c-ac40-504a9ff5bade", - "type": "RELATION", - "name": "taskTargets", - "label": "Targets", - "description": "Task targets", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "85f92b6a-bc71-4da4-ba4e-7b0685a05fde", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f1ed6bba-53bc-4f9c-ac40-504a9ff5bade", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "31d8d648-5118-4b59-8c1e-876e83bf85f3", - "name": "task" - } - } - }, - { - "__typename": "field", - "id": "e10938b5-0a72-48b3-8fb1-4984d1bd651b", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "80fe7004-903e-4bdd-985d-9ef7e6acd793", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the task", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "32b10a0d-0ca4-4027-be9e-ab8d8be608d1", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "80fe7004-903e-4bdd-985d-9ef7e6acd793", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e7201f19-bfa7-42e1-9550-7c848a842ecc", - "name": "task" - } - } - }, - { - "__typename": "field", - "id": "3a9c10bc-f466-42e3-aa9d-203c8a946955", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Task title", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "49cf9eb7-53bc-4347-93f5-daec9c219cfa", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the task.", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9f7f6a7c-072e-4818-8077-d0d0c9601cb0", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "49cf9eb7-53bc-4347-93f5-daec9c219cfa", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6ea41d58-7eae-4434-bb2f-9a1b2c30dba3", - "name": "task" - } - } - }, - { - "__typename": "field", - "id": "504f6d88-cdaa-4e7f-beda-14d7e8adf203", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities", - "labelSingular": "Timeline Activity", - "labelPlural": "Timeline Activities", - "description": "Aggregated / filtered event to be displayed on the timeline", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "13cff5d9-b6d4-4eaa-82d3-31c98395deca", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "13cff5d9-b6d4-4eaa-82d3-31c98395deca", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "99599532-c0e9-4d62-b4a6-89866e0374be", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "Event note", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bc6b24e6-9fcd-43fd-a2ba-c12f5d022132", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "99599532-c0e9-4d62-b4a6-89866e0374be", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f62992f2-80ef-477c-ae60-fc7a862b0f4a", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "383d3f90-d691-4487-a13d-e80c50fb756e", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "b148030f-e465-4961-9c94-d096dc332bf7", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e86bb958-5af5-4e89-8104-925159a41d79", - "type": "TEXT", - "name": "name", - "label": "Event name", - "description": "Event name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a866ea59-2ff6-4a55-816f-f19927ff742e", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "Event task id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "136cece0-793d-4274-9cc4-f03d5aea0ce0", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject ID (foreign key)", - "description": "Timeline Activity myCustomObject id foreign key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.152Z", - "updatedAt": "2024-08-05T17:09:54.152Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6c46473c-5baa-4795-8eed-3b0649cc9ef0", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "Event opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "dba195e2-63d6-42ca-94aa-42c87b4306ea", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Event workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "170c6f88-63b3-41ff-9e5d-044968a062a4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "dba195e2-63d6-42ca-94aa-42c87b4306ea", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4ae17923-dd16-45c7-9df3-8cee92584a52", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "a4101adc-223b-4fa3-a375-b5e49e5a05eb", - "type": "UUID", - "name": "linkedRecordId", - "label": "Linked Record id", - "description": "Linked Record id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "da41fbb2-1e52-416b-be9c-32242ae63e33", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "69e1ecef-09d7-4b53-826e-f440ae72d2b7", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Event person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ba3d762d-8fbf-45e5-a958-136a269a396d", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "69e1ecef-09d7-4b53-826e-f440ae72d2b7", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c08e6ba8-b7ef-4fa7-b199-c8e93045f8ee", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "6ea41d58-7eae-4434-bb2f-9a1b2c30dba3", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "Event task", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9f7f6a7c-072e-4818-8077-d0d0c9601cb0", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6ea41d58-7eae-4434-bb2f-9a1b2c30dba3", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "49cf9eb7-53bc-4347-93f5-daec9c219cfa", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "979ea933-d8a1-4db6-8c29-5c747a690326", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Event company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c542c9e0-b4b6-4073-aae6-66299868e9fb", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "979ea933-d8a1-4db6-8c29-5c747a690326", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c06142b8-52a9-4b0b-93f6-99e2b5b67ab8", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "55701d3c-1710-4f70-ae03-25690249dddf", - "type": "RAW_JSON", - "name": "properties", - "label": "Event details", - "description": "Json value for event details", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ff438247-19b7-4ea6-a00e-1f66b4caa523", - "type": "UUID", - "name": "linkedObjectMetadataId", - "label": "Linked Object Metadata Id", - "description": "inked Object Metadata Id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "faacecdd-08e4-4d9a-b64d-abbac4d5320e", - "type": "TEXT", - "name": "linkedRecordCachedName", - "label": "Linked Record cached name", - "description": "Cached record name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2a612f5a-c6c8-4e82-9368-2ba6f458b4f2", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Event company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "8d4a2a2f-a04f-4cd4-add6-8a417220306e", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "Event note id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e7e04af7-a0a2-4999-9265-e9bafcd0197c", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": "Timeline Activity myCustomObject", - "icon": "IconTimeline", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.153Z", - "updatedAt": "2024-08-05T17:09:54.153Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e143276e-7f4a-46e8-bf0f-61111f36d4fd", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e7e04af7-a0a2-4999-9265-e9bafcd0197c", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e7cd7adb-152e-4d19-b2ba-d1b66bc40e79", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "a5a11b28-dc40-42b1-b160-f95e36f25682", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Event person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5494a528-2d5c-402b-b5b0-30b0d30852a1", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Event workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2c3baa6b-7ffe-470e-bd9d-0532e2bce3ac", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "Event opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "61e4edee-13d4-4edd-9101-b9dc5ea5506a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2c3baa6b-7ffe-470e-bd9d-0532e2bce3ac", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "713c39d7-63e2-4b0e-bd5c-d240b840255e", - "name": "timelineActivities" - } - } - }, - { - "__typename": "field", - "id": "2371abba-e2a8-42c1-96cf-127f2fd2fddc", - "type": "DATE_TIME", - "name": "happensAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts", - "labelSingular": "Connected Account", - "labelPlural": "Connected Accounts", - "description": "A connected account", - "icon": "IconAt", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "6a63f624-b6fe-4b46-ab94-6d9e327dedf8", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "6b464b10-e402-4862-887c-eccc38af9145", - "type": "UUID", - "name": "accountOwnerId", - "label": "Account Owner id (foreign key)", - "description": "Account Owner id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b392b533-9e94-4c00-8dfd-5183c6a544df", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b6b3881a-852b-42dd-bfe0-65eab4dbcb0a", - "type": "TEXT", - "name": "provider", - "label": "provider", - "description": "The account provider", - "icon": "IconSettings", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7e0a02a3-6d2f-4064-a2fa-e13da62c0e6f", - "type": "TEXT", - "name": "refreshToken", - "label": "Refresh Token", - "description": "Messaging provider refresh token", - "icon": "IconKey", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "55a55985-3e1d-4db6-b0ac-3585d72b69ed", - "type": "RELATION", - "name": "messageChannels", - "label": "Message Channels", - "description": "Message Channels", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1a74fdd8-63d4-407f-9d25-7e2e6c4d271c", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "55a55985-3e1d-4db6-b0ac-3585d72b69ed", - "name": "messageChannels" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "219d7acf-5934-44dc-8789-62ade666cb43", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "252a6670-31ea-4b7a-a75f-09c44f4822be", - "name": "connectedAccount" - } - } - }, - { - "__typename": "field", - "id": "ff264a3b-dcfa-4a94-aa6b-3416456ca567", - "type": "TEXT", - "name": "handleAliases", - "label": "Handle Aliases", - "description": "Handle Aliases", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "793ca9eb-06ac-433a-b0c3-d62139cbd71c", - "type": "RELATION", - "name": "calendarChannels", - "label": "Calendar Channels", - "description": "Calendar Channels", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "69dde225-0e12-4df6-ab55-d870d6dec717", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "793ca9eb-06ac-433a-b0c3-d62139cbd71c", - "name": "calendarChannels" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "3f89df71-38d5-46f4-818f-076a5ee77e48", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f7f4925d-e186-4fe6-80f5-1ddd1ae5bf22", - "name": "connectedAccount" - } - } - }, - { - "__typename": "field", - "id": "044bb29e-1cea-455e-8cd4-76c3aecfe9c5", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6a63f624-b6fe-4b46-ab94-6d9e327dedf8", - "type": "TEXT", - "name": "handle", - "label": "handle", - "description": "The account handle (email, username, phone number, etc.)", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "02ff8c81-8978-4dae-b719-2efed251c95d", - "type": "TEXT", - "name": "accessToken", - "label": "Access Token", - "description": "Messaging provider access token", - "icon": "IconKey", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "8c36e0eb-108e-4797-97a1-b9b5ea096180", - "type": "RELATION", - "name": "accountOwner", - "label": "Account Owner", - "description": "Account Owner", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9a65de46-ef09-429f-b7ba-31cb8a8c7038", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "8c36e0eb-108e-4797-97a1-b9b5ea096180", - "name": "accountOwner" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4d977e6a-4d0e-4bca-b743-9bc3df1744d6", - "name": "connectedAccounts" - } - } - }, - { - "__typename": "field", - "id": "7aa3c042-1267-4ff7-9dcf-51bf6dafb96d", - "type": "DATE_TIME", - "name": "authFailedAt", - "label": "Auth failed at", - "description": "Auth failed at", - "icon": "IconX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "608ca34b-405b-4b6e-8eaa-7183c45f9b13", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "46ffb6c5-a7fc-4035-bf98-e02f06d67059", - "type": "TEXT", - "name": "lastSyncHistoryId", - "label": "Last sync history ID", - "description": "Last sync history ID", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "view", - "namePlural": "views", - "labelSingular": "View", - "labelPlural": "Views", - "description": "(System) Views", - "icon": "IconLayoutCollage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "1558d329-6983-48af-a136-e5b10f9edd3a", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "fa6d3625-a637-421d-97f6-354c92915ff1", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4a7f5e32-8c16-43fe-a20b-0847e8002a8a", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0d2e0bfe-fe67-4df4-af4f-49722ba4bf96", - "type": "RELATION", - "name": "viewFilters", - "label": "View Filters", - "description": "View Filters", - "icon": "IconFilterBolt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bd7a6047-3eb5-413e-9315-bb28533c4aed", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "nameSingular": "view", - "namePlural": "views" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0d2e0bfe-fe67-4df4-af4f-49722ba4bf96", - "name": "viewFilters" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "666caea5-3c1e-4847-9fd9-2d8c1d08eabb", - "nameSingular": "viewFilter", - "namePlural": "viewFilters" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c56cc7db-9e19-470e-9d6d-2b7180fb0fb7", - "name": "view" - } - } - }, - { - "__typename": "field", - "id": "4c25b9f8-40e3-4889-ab95-551cd9fdfbb6", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0e220989-7171-4776-9045-4cea28effd86", - "type": "TEXT", - "name": "type", - "label": "Type", - "description": "View type", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'table'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1a7b73fc-a1b3-41e1-8971-e11bc73421cf", - "type": "BOOLEAN", - "name": "isCompact", - "label": "Compact View", - "description": "Describes if the view is in compact mode", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": false, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "d8c8b4c5-d58c-4990-9c43-65d8c070d629", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "View position", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6ea01d0e-340e-40e4-a029-89a7cbc07291", - "type": "RELATION", - "name": "viewFields", - "label": "View Fields", - "description": "View Fields", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "57d32129-b126-417e-98a8-7f1217b29dea", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "nameSingular": "view", - "namePlural": "views" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6ea01d0e-340e-40e4-a029-89a7cbc07291", - "name": "viewFields" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ee025446-440d-49ae-8d0e-ad30b6309840", - "nameSingular": "viewField", - "namePlural": "viewFields" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6725c7ad-a704-436a-be67-a4612bc48e37", - "name": "view" - } - } - }, - { - "__typename": "field", - "id": "737edb22-8d06-4d16-aac0-4eda04062485", - "type": "TEXT", - "name": "kanbanFieldMetadataId", - "label": "kanbanfieldMetadataId", - "description": "View Kanban column field", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1558d329-6983-48af-a136-e5b10f9edd3a", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "View name", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "8b65e9c3-764d-4569-9d47-0e9146b27802", - "type": "TEXT", - "name": "icon", - "label": "Icon", - "description": "View icon", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b6cb0290-080b-4bab-867a-6d32122d24ad", - "type": "SELECT", - "name": "key", - "label": "Key", - "description": "View key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'INDEX'", - "options": [ - { - "id": "54770bee-85b8-48db-87e9-9ba47fde5b27", - "color": "red", - "label": "Index", - "value": "INDEX", - "position": 0 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "df99390b-b681-4c2b-ba91-8279f0bf0707", - "type": "UUID", - "name": "objectMetadataId", - "label": "Object Metadata Id", - "description": "View target object", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4abadd14-56cd-48e4-8013-7b46de4ffe22", - "type": "RELATION", - "name": "viewSorts", - "label": "View Sorts", - "description": "View Sorts", - "icon": "IconArrowsSort", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "53abf7c2-810d-478b-bb2d-689f31322d67", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "nameSingular": "view", - "namePlural": "views" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4abadd14-56cd-48e4-8013-7b46de4ffe22", - "name": "viewSorts" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1b5e63b9-9fc3-485d-86ff-de70ff17a665", - "nameSingular": "viewSort", - "namePlural": "viewSorts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "673fb6fb-5123-4336-9b4b-e4b268c1cffe", - "name": "view" - } - } - } - ] - }, - { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "taskTarget", - "namePlural": "taskTargets", - "labelSingular": "Task Target", - "labelPlural": "Task Targets", - "description": "An task target", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "4d792e46-cd2f-4db4-a348-50f1adbf0ebf", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "85958c6a-882f-4304-947c-45a44fb73585", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "TaskTarget task id foreign key", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "90e006e0-f7b9-49da-acd5-7c36a85e5d53", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "TaskTarget opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "cd786729-40e1-4829-a6ee-9e4bf2e0a04f", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4d792e46-cd2f-4db4-a348-50f1adbf0ebf", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e0094c52-055f-4f11-9334-927f01ac586f", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "TaskTarget person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ea500914-d6c5-45d6-90ef-40c9912c98c2", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "TaskTarget company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "fc1a31f8-6e1c-4ce1-b6ff-80d1cd605e58", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "TaskTarget person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "182b32c3-9ee9-4a65-937b-d9035ab65300", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fc1a31f8-6e1c-4ce1-b6ff-80d1cd605e58", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4c9ba269-244f-4768-a52d-9b1ffbe3339f", - "name": "taskTargets" - } - } - }, - { - "__typename": "field", - "id": "0cfef76e-8309-4946-bfad-0a400d2327b9", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7c6ab6b0-9978-456b-bb7c-c7c1dc454e3d", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": "TaskTarget myCustomObject", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.181Z", - "updatedAt": "2024-08-05T17:09:54.181Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "3f2b2bab-8411-41b7-a87b-06dd0007eab4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "7c6ab6b0-9978-456b-bb7c-c7c1dc454e3d", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "90dd4b06-8b21-4411-9f38-b95968a1d4e1", - "name": "taskTargets" - } - } - }, - { - "__typename": "field", - "id": "705e4379-9ba2-4853-b267-c86dad461dd7", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "TaskTarget opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e594dda4-55fc-46ba-8108-5f672a5b1301", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "705e4379-9ba2-4853-b267-c86dad461dd7", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "40cdd413-5239-4887-b5e6-eb32eb1d95e3", - "name": "taskTargets" - } - } - }, - { - "__typename": "field", - "id": "31d8d648-5118-4b59-8c1e-876e83bf85f3", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "TaskTarget task", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "85f92b6a-bc71-4da4-ba4e-7b0685a05fde", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "31d8d648-5118-4b59-8c1e-876e83bf85f3", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f1ed6bba-53bc-4f9c-ac40-504a9ff5bade", - "name": "taskTargets" - } - } - }, - { - "__typename": "field", - "id": "f6566665-bc44-4d92-9e6a-7405e8f505cb", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject ID (foreign key)", - "description": "TaskTarget myCustomObject id foreign key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.180Z", - "updatedAt": "2024-08-05T17:09:54.180Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1e4e3b2b-113f-4af3-aed8-94b03785a626", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "TaskTarget company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "f80bfd64-c33d-4488-bc49-1635e092ea3f", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "1e4e3b2b-113f-4af3-aed8-94b03785a626", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "cb47633a-1b44-41b9-8bce-16e28616c2ad", - "name": "taskTargets" - } - } - } - ] - }, - { - "__typename": "object", - "id": "666caea5-3c1e-4847-9fd9-2d8c1d08eabb", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "viewFilter", - "namePlural": "viewFilters", - "labelSingular": "View Filter", - "labelPlural": "View Filters", - "description": "(System) View Filters", - "icon": "IconFilterBolt", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "5a3d73f8-918d-49f5-a890-aac2b3163ed2", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "2d97f9a5-4f5b-45d0-aabd-843c7f2ed19b", - "type": "TEXT", - "name": "operand", - "label": "Operand", - "description": "View Filter operand", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'Contains'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "40f77e70-c43b-4f16-8a20-beb0aafd77a0", - "type": "TEXT", - "name": "displayValue", - "label": "Display Value", - "description": "View Filter Display Value", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c56cc7db-9e19-470e-9d6d-2b7180fb0fb7", - "type": "RELATION", - "name": "view", - "label": "View", - "description": "View Filter related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bd7a6047-3eb5-413e-9315-bb28533c4aed", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "666caea5-3c1e-4847-9fd9-2d8c1d08eabb", - "nameSingular": "viewFilter", - "namePlural": "viewFilters" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c56cc7db-9e19-470e-9d6d-2b7180fb0fb7", - "name": "view" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0d2e0bfe-fe67-4df4-af4f-49722ba4bf96", - "name": "viewFilters" - } - } - }, - { - "__typename": "field", - "id": "5a3d73f8-918d-49f5-a890-aac2b3163ed2", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f2f27130-bc36-4a22-96eb-a7a3a0aee6c5", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "97ecc896-e994-4822-8758-6f06de23b1d5", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1927b1c6-5531-43f4-9c3d-078aeeedbafc", - "type": "TEXT", - "name": "value", - "label": "Value", - "description": "View Filter value", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6e257f21-2577-46eb-81e1-30b6280411ce", - "type": "UUID", - "name": "viewId", - "label": "View id (foreign key)", - "description": "View Filter related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "97920c5b-939b-489f-9132-15522c8b2f25", - "type": "UUID", - "name": "fieldMetadataId", - "label": "Field Metadata Id", - "description": "View Filter target field", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "activityTarget", - "namePlural": "activityTargets", - "labelSingular": "Activity Target", - "labelPlural": "Activity Targets", - "description": "An activity target", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "ba72ff28-333c-4a4e-a9a7-97625ae1a899", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "aa1c7e04-31c1-4b62-8451-6b32926cab47", - "type": "RELATION", - "name": "activity", - "label": "Activity", - "description": "ActivityTarget activity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "16017cba-688e-4483-a258-9cef3999cbbf", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "aa1c7e04-31c1-4b62-8451-6b32926cab47", - "name": "activity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "41f8fd90-2de9-402f-8b37-fb023d318de2", - "name": "activityTargets" - } - } - }, - { - "__typename": "field", - "id": "702bc8dc-83d5-4367-82e9-a221dcf05687", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "ActivityTarget company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a2e154ce-eb15-4d30-adcf-f8b6f969b007", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "ActivityTarget opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "997b0618-d3f7-4e5c-8c8e-2ba1bad10549", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "ActivityTarget opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d2a53784-3664-49ab-983e-5ad5bf15dbd0", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "997b0618-d3f7-4e5c-8c8e-2ba1bad10549", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b95b3f38-9fc2-4d7e-a823-7791cf13d089", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "71341870-4e4d-4399-ab74-8f277047664e", - "name": "activityTargets" - } - } - }, - { - "__typename": "field", - "id": "0dbe65bc-b07e-4359-a7ab-c70cdf95dcf7", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "29b26108-6f7e-4424-b430-de1b54a9629a", - "type": "UUID", - "name": "myCustomObjectId", - "label": "myCustomObject ID (foreign key)", - "description": "ActivityTarget myCustomObject id foreign key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.160Z", - "updatedAt": "2024-08-05T17:09:54.160Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f522e7f8-7e9c-4fde-9e8a-1ddd5fa40063", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "ActivityTarget person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5032f1f4-d2ae-46de-bff6-b596a73c0d8c", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ba72ff28-333c-4a4e-a9a7-97625ae1a899", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "10150d34-2f00-4642-8a9d-6b0b6ab72562", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "ActivityTarget company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2ed70c2c-b17a-4ed1-9f35-b570139440fa", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "10150d34-2f00-4642-8a9d-6b0b6ab72562", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4d74b886-b359-4c4c-a2c0-692edc8a3273", - "name": "activityTargets" - } - } - }, - { - "__typename": "field", - "id": "940d1664-b17c-4f66-820b-abfec70adaa5", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "ActivityTarget person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9a3a145b-6d06-4892-84d4-af523f40c58d", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "940d1664-b17c-4f66-820b-abfec70adaa5", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "9b018bba-687b-4850-9e0e-c192d3b5977d", - "name": "activityTargets" - } - } - }, - { - "__typename": "field", - "id": "d20eb128-2dd6-49e2-ac71-9d8991bc22fb", - "type": "RELATION", - "name": "myCustomObject", - "label": "myCustomObject", - "description": "ActivityTarget myCustomObject", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.161Z", - "updatedAt": "2024-08-05T17:09:54.161Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1271db29-f60e-4cf2-83cb-b31f62211850", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d20eb128-2dd6-49e2-ac71-9d8991bc22fb", - "name": "myCustomObject" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "7e079b54-1abf-486d-850e-5a5d32fed77b", - "name": "activityTargets" - } - } - }, - { - "__typename": "field", - "id": "23a39b63-86cb-46da-b4a1-a49317ad06f5", - "type": "UUID", - "name": "activityId", - "label": "Activity id (foreign key)", - "description": "ActivityTarget activity id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "5b55de69-76d8-4170-94d3-ff85ee7640ca", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations", - "labelSingular": "Calendar Channel Event Association", - "labelPlural": "Calendar Channel Event Associations", - "description": "Calendar Channel Event Associations", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "5966ae0a-06c2-4f9e-92e7-e908b4afbcbc", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "94180d42-50ed-48a0-a62b-e00f1a6f4753", - "type": "RELATION", - "name": "calendarChannel", - "label": "Channel ID", - "description": "Channel ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "876e63a4-ce57-4852-b1b7-5659021ea34c", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "5b55de69-76d8-4170-94d3-ff85ee7640ca", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "94180d42-50ed-48a0-a62b-e00f1a6f4753", - "name": "calendarChannel" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "3f89df71-38d5-46f4-818f-076a5ee77e48", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0751ede9-2493-4079-b976-14b98a4eb971", - "name": "calendarChannelEventAssociations" - } - } - }, - { - "__typename": "field", - "id": "c9dfb626-58e2-4aab-986c-4839dc7f5e0b", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2baeedd7-34a0-4d07-ab4c-5e6995213ec5", - "type": "RELATION", - "name": "calendarEvent", - "label": "Event ID", - "description": "Event ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "09476064-1403-4948-84c2-d87a02a022ca", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "5b55de69-76d8-4170-94d3-ff85ee7640ca", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2baeedd7-34a0-4d07-ab4c-5e6995213ec5", - "name": "calendarEvent" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "37d40c3f-e106-4348-af22-201659bbd8a6", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "46421d40-86ab-4f44-aee4-862320bf7534", - "name": "calendarChannelEventAssociations" - } - } - }, - { - "__typename": "field", - "id": "a00a384b-ab25-4dc5-a778-25d9b9cb8ffd", - "type": "UUID", - "name": "calendarEventId", - "label": "Event ID id (foreign key)", - "description": "Event ID id foreign key", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "fea76c39-2d44-4d78-b346-eec8a9d55102", - "type": "TEXT", - "name": "eventExternalId", - "label": "Event external ID", - "description": "Event external ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5966ae0a-06c2-4f9e-92e7-e908b4afbcbc", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6bec724b-ce56-4ebb-82fb-f2c79c4ef360", - "type": "UUID", - "name": "calendarChannelId", - "label": "Channel ID id (foreign key)", - "description": "Channel ID id foreign key", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "00171466-dba6-4b35-81a5-e37a9d4ea659", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects", - "labelSingular": "myCustomObject", - "labelPlural": "myCustomObjects", - "description": null, - "icon": "IconListNumbers", - "isCustom": true, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "labelIdentifierFieldMetadataId": null, - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "c475ebbc-f86b-4956-9d67-d0bb62062408", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites tied to the myCustomObject", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.168Z", - "updatedAt": "2024-08-05T17:09:54.168Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "143c2257-721f-46eb-8114-987a70979146", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c475ebbc-f86b-4956-9d67-d0bb62062408", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "87c2dd65-2c54-4184-9a19-0bdce7781a3f", - "name": "myCustomObject" - } - } - }, - { - "__typename": "field", - "id": "7e079b54-1abf-486d-850e-5a5d32fed77b", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the myCustomObject", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.161Z", - "updatedAt": "2024-08-05T17:09:54.161Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1271db29-f60e-4cf2-83cb-b31f62211850", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "7e079b54-1abf-486d-850e-5a5d32fed77b", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "648268ca-94bf-418e-853c-56d0f51472b3", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d20eb128-2dd6-49e2-ac71-9d8991bc22fb", - "name": "myCustomObject" - } - } - }, - { - "__typename": "field", - "id": "90dd4b06-8b21-4411-9f38-b95968a1d4e1", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the myCustomObject", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.181Z", - "updatedAt": "2024-08-05T17:09:54.181Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "3f2b2bab-8411-41b7-a87b-06dd0007eab4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "90dd4b06-8b21-4411-9f38-b95968a1d4e1", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "77d124cc-049a-44f9-ab59-56e3dd55bb69", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "7c6ab6b0-9978-456b-bb7c-c7c1dc454e3d", - "name": "myCustomObject" - } - } - }, - { - "__typename": "field", - "id": "13b6535d-d523-4e2b-9ba4-90630173388a", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "9d9aa060-cdfd-4e01-80fc-92549f025a9d", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5842b56f-0029-4205-9ee2-d90bc51f25e5", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "87631266-4a85-4c49-82d1-90d1805c3de6", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the myCustomObject", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.177Z", - "updatedAt": "2024-08-05T17:09:54.177Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "4f4d3969-913b-478c-a41d-9daffc9b2255", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "87631266-4a85-4c49-82d1-90d1805c3de6", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "bd4e44a0-4b0d-4392-b0c9-d6c8684e3d44", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e884eac3-4cbd-40af-970e-a34b409c0acd", - "name": "myCustomObject" - } - } - }, - { - "__typename": "field", - "id": "7dd8ba1c-17d8-491b-bc6d-1bf024b38eee", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "Name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "defaultValue": "'Untitled'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c2a859ac-2667-4f12-8cbd-6cc55e45664f", - "type": "TEXT", - "name": "myCustomField", - "label": "myCustomField", - "description": null, - "icon": "IconUsers", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T17:10:03.185Z", - "updatedAt": "2024-08-05T17:10:03.185Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a4580aee-6fb4-4b5d-87c0-daf24745ca13", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments tied to the myCustomObject", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.173Z", - "updatedAt": "2024-08-05T17:09:54.173Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bd914a9e-b1f8-43c6-af60-3afe46518988", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a4580aee-6fb4-4b5d-87c0-daf24745ca13", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2e1b103b-a75b-4ebc-8219-4b59027bd3fd", - "name": "myCustomObject" - } - } - }, - { - "__typename": "field", - "id": "6e5b1581-2e88-44e4-ac0c-2143cc92ad19", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e7cd7adb-152e-4d19-b2ba-d1b66bc40e79", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities tied to the myCustomObject", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T17:09:54.153Z", - "updatedAt": "2024-08-05T17:09:54.153Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e143276e-7f4a-46e8-bf0f-61111f36d4fd", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e7cd7adb-152e-4d19-b2ba-d1b66bc40e79", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e7e04af7-a0a2-4999-9265-e9bafcd0197c", - "name": "myCustomObject" - } - } - }, - { - "__typename": "field", - "id": "dbf144b7-885d-40ae-af3e-3efdb424fe26", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T17:09:54.141Z", - "updatedAt": "2024-08-05T17:09:54.141Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "23006c79-19fe-4148-9ee4-6db039ebc6fb", - "type": "RELATION", - "name": "people", - "label": "people", - "description": null, - "icon": "IconUsers", - "isCustom": true, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T17:10:31.391Z", - "updatedAt": "2024-08-05T17:10:31.391Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "27f9741d-f967-4b75-affa-240f0f5f8d77", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "56dffccc-daf8-4c49-8919-f19787f07846", - "nameSingular": "myCustomObject", - "namePlural": "myCustomObjects" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "23006c79-19fe-4148-9ee4-6db039ebc6fb", - "name": "people" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f7002609-5760-4ae6-ba29-a8b9066b95de", - "name": "myCustomObject" - } - } - } - ] - }, - { - "__typename": "object", - "id": "3f89df71-38d5-46f4-818f-076a5ee77e48", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels", - "labelSingular": "Calendar Channel", - "labelPlural": "Calendar Channels", - "description": "Calendar Channels", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "fc64465a-ab08-4df8-93e2-a19e758e54d8", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "26d457d6-8f93-4813-88fa-78704b780644", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "895be714-df44-4d95-8860-a40ab37bf61d", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "11816da4-27dd-475c-ae34-c578b21d1072", - "type": "DATE_TIME", - "name": "syncStageStartedAt", - "label": "Sync stage started at", - "description": "Sync stage started at", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "17cc8c28-aa8d-43a0-9c11-ccfc43300d57", - "type": "BOOLEAN", - "name": "isContactAutoCreationEnabled", - "label": "Is Contact Auto Creation Enabled", - "description": "Is Contact Auto Creation Enabled", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "bb8528d4-eb52-469b-a87a-895e41fce448", - "type": "NUMBER", - "name": "throttleFailureCount", - "label": "Throttle Failure Count", - "description": "Throttle Failure Count", - "icon": "IconX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "cc4fbb8e-3d27-461c-be5e-3b55c3d059d0", - "type": "BOOLEAN", - "name": "isSyncEnabled", - "label": "Is Sync Enabled", - "description": "Is Sync Enabled", - "icon": "IconRefresh", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "fc64465a-ab08-4df8-93e2-a19e758e54d8", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0751ede9-2493-4079-b976-14b98a4eb971", - "type": "RELATION", - "name": "calendarChannelEventAssociations", - "label": "Calendar Channel Event Associations", - "description": "Calendar Channel Event Associations", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "876e63a4-ce57-4852-b1b7-5659021ea34c", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "3f89df71-38d5-46f4-818f-076a5ee77e48", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0751ede9-2493-4079-b976-14b98a4eb971", - "name": "calendarChannelEventAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "5b55de69-76d8-4170-94d3-ff85ee7640ca", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "94180d42-50ed-48a0-a62b-e00f1a6f4753", - "name": "calendarChannel" - } - } - }, - { - "__typename": "field", - "id": "ce4b8d97-0dbc-4e35-b07d-9b76b78b0805", - "type": "SELECT", - "name": "visibility", - "label": "Visibility", - "description": "Visibility", - "icon": "IconEyeglass", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'SHARE_EVERYTHING'", - "options": [ - { - "id": "b6afdf8e-219c-4fa4-b493-2ef201cf08ae", - "color": "green", - "label": "Metadata", - "value": "METADATA", - "position": 0 - }, - { - "id": "1005e275-acda-4842-9b5f-202a82d7eef9", - "color": "orange", - "label": "Share Everything", - "value": "SHARE_EVERYTHING", - "position": 1 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f7f4925d-e186-4fe6-80f5-1ddd1ae5bf22", - "type": "RELATION", - "name": "connectedAccount", - "label": "Connected Account", - "description": "Connected Account", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "69dde225-0e12-4df6-ab55-d870d6dec717", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "3f89df71-38d5-46f4-818f-076a5ee77e48", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f7f4925d-e186-4fe6-80f5-1ddd1ae5bf22", - "name": "connectedAccount" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "793ca9eb-06ac-433a-b0c3-d62139cbd71c", - "name": "calendarChannels" - } - } - }, - { - "__typename": "field", - "id": "fb9fa3ad-8a76-4b35-9e3d-2d70e0bd9b43", - "type": "TEXT", - "name": "syncCursor", - "label": "Sync Cursor", - "description": "Sync Cursor. Used for syncing events from the calendar provider", - "icon": "IconReload", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "cc894f79-0ce0-4534-8874-f6f7b38bcfe8", - "type": "SELECT", - "name": "contactAutoCreationPolicy", - "label": "Contact auto creation policy", - "description": "Automatically create records for people you participated with in an event.", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'AS_PARTICIPANT_AND_ORGANIZER'", - "options": [ - { - "id": "479dcab1-99b1-49db-a398-c838657d694c", - "color": "green", - "label": "As Participant and Organizer", - "value": "AS_PARTICIPANT_AND_ORGANIZER", - "position": 0 - }, - { - "id": "8736e812-170f-46ff-ace4-e16a58a6426d", - "color": "orange", - "label": "As Participant", - "value": "AS_PARTICIPANT", - "position": 1 - }, - { - "id": "afffd87a-0025-4c0e-bd88-6808da2a6d4c", - "color": "blue", - "label": "As Organizer", - "value": "AS_ORGANIZER", - "position": 2 - }, - { - "id": "45145a84-a014-436a-bf88-a174e6da5352", - "color": "red", - "label": "None", - "value": "NONE", - "position": 3 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "87dda3f9-e513-44c1-87f3-1c2b322368e6", - "type": "UUID", - "name": "connectedAccountId", - "label": "Connected Account id (foreign key)", - "description": "Connected Account id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "465d2623-1abb-4587-878c-4752cacf5fc9", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2a2b8f04-c432-4a96-9eb2-7b824b68dfc6", - "type": "SELECT", - "name": "syncStage", - "label": "Sync stage", - "description": "Sync stage", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'FULL_CALENDAR_EVENT_LIST_FETCH_PENDING'", - "options": [ - { - "id": "37376ca4-6926-4128-9395-9cd599d1dc56", - "color": "blue", - "label": "Full calendar event list fetch pending", - "value": "FULL_CALENDAR_EVENT_LIST_FETCH_PENDING", - "position": 0 - }, - { - "id": "0ee03d31-3ffc-4b2d-9e8d-0eea0179bf04", - "color": "blue", - "label": "Partial calendar event list fetch pending", - "value": "PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING", - "position": 1 - }, - { - "id": "aaa2d5b7-c930-4672-b381-8759b53c3c12", - "color": "orange", - "label": "Calendar event list fetch ongoing", - "value": "CALENDAR_EVENT_LIST_FETCH_ONGOING", - "position": 2 - }, - { - "id": "940eeb35-e2f6-44ca-9b81-cd682b9f4d32", - "color": "blue", - "label": "Calendar events import pending", - "value": "CALENDAR_EVENTS_IMPORT_PENDING", - "position": 3 - }, - { - "id": "ff49a7d8-b7ee-4804-85a3-b0b1641681f2", - "color": "orange", - "label": "Calendar events import ongoing", - "value": "CALENDAR_EVENTS_IMPORT_ONGOING", - "position": 4 - }, - { - "id": "10e8fafb-a086-491e-a6b5-b33bb11e757b", - "color": "red", - "label": "Failed", - "value": "FAILED", - "position": 5 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "335172ff-f683-4cd4-96d0-3120d505ee7e", - "type": "SELECT", - "name": "syncStatus", - "label": "Sync status", - "description": "Sync status", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": [ - { - "id": "427be75a-3309-4ac7-ad9f-6315829ec1de", - "color": "yellow", - "label": "Ongoing", - "value": "ONGOING", - "position": 1 - }, - { - "id": "5caefabb-b384-4d23-84cf-569ae417e0e9", - "color": "blue", - "label": "Not Synced", - "value": "NOT_SYNCED", - "position": 2 - }, - { - "id": "34d49c7c-ea37-4293-ab2c-1d1e68bb0e09", - "color": "green", - "label": "Active", - "value": "ACTIVE", - "position": 3 - }, - { - "id": "b7ed2a50-b048-4f30-b7f8-b586a7e8deef", - "color": "red", - "label": "Failed Insufficient Permissions", - "value": "FAILED_INSUFFICIENT_PERMISSIONS", - "position": 4 - }, - { - "id": "73e4ce2f-f208-482f-b054-85ecb9cbfb9c", - "color": "red", - "label": "Failed Unknown", - "value": "FAILED_UNKNOWN", - "position": 5 - } - ], - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "3af96291-b873-402f-bd90-f4731984c8dd", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "comment", - "namePlural": "comments", - "labelSingular": "Comment", - "labelPlural": "Comments", - "description": "A comment", - "icon": "IconMessageCircle", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "95c741f7-cf0f-403e-9664-83a052f3934c", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "209c9e5d-c82d-4d30-8fb1-1f7bc70d74ec", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "95c741f7-cf0f-403e-9664-83a052f3934c", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e2400fef-dfb4-4ec8-b83f-705b923b7b00", - "type": "UUID", - "name": "authorId", - "label": "Author id (foreign key)", - "description": "Comment author id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7e3e4ebe-fc63-47c6-b21d-0934b2842806", - "type": "TEXT", - "name": "body", - "label": "Body", - "description": "Comment body", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "239d39c7-de84-462e-b99c-c7e9c1b99d8a", - "type": "UUID", - "name": "activityId", - "label": "Activity id (foreign key)", - "description": "Comment activity id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5a74f15a-2abd-4631-b324-575dd0a81b14", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1a7fec45-dd33-43a0-9302-5b0b9052d2f0", - "type": "RELATION", - "name": "author", - "label": "Author", - "description": "Comment author", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "4917f689-5cc2-4716-b4ab-6906aef009b3", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "3af96291-b873-402f-bd90-f4731984c8dd", - "nameSingular": "comment", - "namePlural": "comments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "1a7fec45-dd33-43a0-9302-5b0b9052d2f0", - "name": "author" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "12d19915-891e-4a4e-8c42-49a12639264f", - "name": "authoredComments" - } - } - }, - { - "__typename": "field", - "id": "88c3a2b9-b59a-413a-b2d3-44b151185929", - "type": "RELATION", - "name": "activity", - "label": "Activity", - "description": "Comment activity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "00b07eda-840c-4a91-a8f7-365c008a2ea1", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "3af96291-b873-402f-bd90-f4731984c8dd", - "nameSingular": "comment", - "namePlural": "comments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "88c3a2b9-b59a-413a-b2d3-44b151185929", - "name": "activity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "9045116d-0fed-433c-80a4-f4296db72ae5", - "name": "comments" - } - } - } - ] - }, - { - "__typename": "object", - "id": "37d40c3f-e106-4348-af22-201659bbd8a6", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents", - "labelSingular": "Calendar event", - "labelPlural": "Calendar events", - "description": "Calendar events", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "c84c6690-4785-4487-a164-75b35fa6b5ed", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "2563cdaf-19b2-4ca7-bea7-d233228ac87b", - "type": "TEXT", - "name": "description", - "label": "Description", - "description": "Description", - "icon": "IconFileDescription", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f67e0a42-d909-4029-b740-ed4ba6fef8d2", - "type": "BOOLEAN", - "name": "isCanceled", - "label": "Is canceled", - "description": "Is canceled", - "icon": "IconCalendarCancel", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": false, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "21f11229-c9a8-4d90-8f78-8803296de422", - "type": "DATE_TIME", - "name": "endsAt", - "label": "End Date", - "description": "End Date", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "3f88228c-6666-4c18-b4c3-2355e9fb74fd", - "type": "TEXT", - "name": "iCalUID", - "label": "iCal UID", - "description": "iCal UID", - "icon": "IconKey", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b9ff2603-b060-47f7-8b74-367411c97043", - "type": "DATE_TIME", - "name": "externalCreatedAt", - "label": "Creation DateTime", - "description": "Creation DateTime", - "icon": "IconCalendarPlus", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "65f9e0fd-c875-43e0-9988-94dcd1105951", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e4b6323e-2faa-472a-b369-394b7682f7f1", - "type": "BOOLEAN", - "name": "isFullDay", - "label": "Is Full Day", - "description": "Is Full Day", - "icon": "Icon24Hours", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": false, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "eec86c3b-f7ac-4066-ae3e-f7add17f1f97", - "type": "TEXT", - "name": "location", - "label": "Location", - "description": "Location", - "icon": "IconMapPin", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "46421d40-86ab-4f44-aee4-862320bf7534", - "type": "RELATION", - "name": "calendarChannelEventAssociations", - "label": "Calendar Channel Event Associations", - "description": "Calendar Channel Event Associations", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "09476064-1403-4948-84c2-d87a02a022ca", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "37d40c3f-e106-4348-af22-201659bbd8a6", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "46421d40-86ab-4f44-aee4-862320bf7534", - "name": "calendarChannelEventAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "5b55de69-76d8-4170-94d3-ff85ee7640ca", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2baeedd7-34a0-4d07-ab4c-5e6995213ec5", - "name": "calendarEvent" - } - } - }, - { - "__typename": "field", - "id": "c84c6690-4785-4487-a164-75b35fa6b5ed", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Title", - "icon": "IconH1", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "26107044-238f-49dc-89ce-179b46450864", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "57f8f510-2486-47cf-aa05-b10c14f3cc43", - "type": "LINKS", - "name": "conferenceLink", - "label": "Meet Link", - "description": "Meet Link", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "75f5a937-b2c8-4225-bbd6-c8b575e2dd25", - "type": "DATE_TIME", - "name": "startsAt", - "label": "Start Date", - "description": "Start Date", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e286138c-24c0-49d9-8f51-41d47f7958cd", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "849a0245-051d-4dac-8ecf-f35462912b3f", - "type": "DATE_TIME", - "name": "externalUpdatedAt", - "label": "Update DateTime", - "description": "Update DateTime", - "icon": "IconCalendarCog", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c9a2189f-abb1-4166-9d1d-f870f0e32b3e", - "type": "RELATION", - "name": "calendarEventParticipants", - "label": "Event Participants", - "description": "Event Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "28688620-c3df-463d-a655-ad6435f6215b", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "37d40c3f-e106-4348-af22-201659bbd8a6", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c9a2189f-abb1-4166-9d1d-f870f0e32b3e", - "name": "calendarEventParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "9ae2ccf9-1390-4861-a771-324ab4310f33", - "name": "calendarEvent" - } - } - }, - { - "__typename": "field", - "id": "cb31cbcb-b866-46d9-aeda-4b4aacd32e6e", - "type": "TEXT", - "name": "conferenceSolution", - "label": "Conference Solution", - "description": "Conference Solution", - "icon": "IconScreenShare", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c1a46da5-904c-4883-8cf1-730ecd860a18", - "type": "TEXT", - "name": "recurringEventExternalId", - "label": "Recurring Event ID", - "description": "Recurring Event ID", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "219d7acf-5934-44dc-8789-62ade666cb43", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "messageChannel", - "namePlural": "messageChannels", - "labelSingular": "Message Channel", - "labelPlural": "Message Channels", - "description": "Message Channels", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "ee88f9a9-318c-44ce-ba9e-3d64c740e090", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "47b231aa-01e2-49e0-bafb-d2b63a153265", - "type": "NUMBER", - "name": "throttleFailureCount", - "label": "Throttle Failure Count", - "description": "Throttle Failure Count", - "icon": "IconX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "2821a085-4008-4b66-a99d-f3c2e1557967", - "type": "SELECT", - "name": "type", - "label": "Type", - "description": "Channel Type", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'email'", - "options": [ - { - "id": "08b1d086-9a6f-4343-8f13-2201c1310f5e", - "color": "green", - "label": "Email", - "value": "email", - "position": 0 - }, - { - "id": "2cef0d80-3db0-4793-b849-650cca4d5021", - "color": "blue", - "label": "SMS", - "value": "sms", - "position": 1 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b4d39140-3163-4a87-98fe-bb9e8cc137d8", - "type": "DATE_TIME", - "name": "syncStageStartedAt", - "label": "Sync stage started at", - "description": "Sync stage started at", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6485d1ba-0c12-4843-bef8-2ecd53074e7d", - "type": "UUID", - "name": "connectedAccountId", - "label": "Connected Account id (foreign key)", - "description": "Connected Account id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "608b0c0a-a828-44e8-9491-e58507090911", - "type": "SELECT", - "name": "syncStatus", - "label": "Sync status", - "description": "Sync status", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": [ - { - "id": "3b0b0655-df75-40cc-b2a3-5b0a44d4e89e", - "color": "yellow", - "label": "Ongoing", - "value": "ONGOING", - "position": 1 - }, - { - "id": "668d19e4-a5f7-471b-aa2d-5b9ee0bd38a4", - "color": "blue", - "label": "Not Synced", - "value": "NOT_SYNCED", - "position": 2 - }, - { - "id": "5d0b907a-0f0d-48ef-971e-6da5c8c6cb08", - "color": "green", - "label": "Active", - "value": "ACTIVE", - "position": 3 - }, - { - "id": "919a86ec-634a-4d12-8a70-0da59eed2ed7", - "color": "red", - "label": "Failed Insufficient Permissions", - "value": "FAILED_INSUFFICIENT_PERMISSIONS", - "position": 4 - }, - { - "id": "360e055a-314f-4e1f-b1a3-bde1cb3a3743", - "color": "red", - "label": "Failed Unknown", - "value": "FAILED_UNKNOWN", - "position": 5 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "898058fb-b487-4300-a3d7-a9f5d7efc928", - "type": "TEXT", - "name": "syncCursor", - "label": "Last sync cursor", - "description": "Last sync cursor", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ee851ccd-e643-40aa-9553-bb0487d4b4b9", - "type": "BOOLEAN", - "name": "isContactAutoCreationEnabled", - "label": "Is Contact Auto Creation Enabled", - "description": "Is Contact Auto Creation Enabled", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "60c85385-7576-4f68-a3a3-4539f9a79f14", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7dc2ea04-202c-4f02-a302-6438d5100423", - "type": "SELECT", - "name": "visibility", - "label": "Visibility", - "description": "Visibility", - "icon": "IconEyeglass", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'SHARE_EVERYTHING'", - "options": [ - { - "id": "30bf3541-7afe-4a39-a070-79b85f6de6ce", - "color": "green", - "label": "Metadata", - "value": "METADATA", - "position": 0 - }, - { - "id": "edb11f69-2cda-4959-9fd2-0629f8d9d4c5", - "color": "blue", - "label": "Subject", - "value": "SUBJECT", - "position": 1 - }, - { - "id": "5728b6b0-59fc-4def-ae0a-6aa261a18ad9", - "color": "orange", - "label": "Share Everything", - "value": "SHARE_EVERYTHING", - "position": 2 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1987d97a-4dd3-4d12-a991-8e514f6999ca", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ee88f9a9-318c-44ce-ba9e-3d64c740e090", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6a84ba01-5f11-40bf-99e4-a5a7e9a8dadb", - "type": "DATE_TIME", - "name": "syncedAt", - "label": "Last sync date", - "description": "Last sync date", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "252a6670-31ea-4b7a-a75f-09c44f4822be", - "type": "RELATION", - "name": "connectedAccount", - "label": "Connected Account", - "description": "Connected Account", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1a74fdd8-63d4-407f-9d25-7e2e6c4d271c", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "219d7acf-5934-44dc-8789-62ade666cb43", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "252a6670-31ea-4b7a-a75f-09c44f4822be", - "name": "connectedAccount" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "55a55985-3e1d-4db6-b0ac-3585d72b69ed", - "name": "messageChannels" - } - } - }, - { - "__typename": "field", - "id": "2682d5c3-f05e-4c5c-87eb-bb1a6c0c37bb", - "type": "RELATION", - "name": "messageChannelMessageAssociations", - "label": "Message Channel Association", - "description": "Messages from the channel.", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e420b731-e1e1-425a-ac7a-488d37d1958b", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "219d7acf-5934-44dc-8789-62ade666cb43", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2682d5c3-f05e-4c5c-87eb-bb1a6c0c37bb", - "name": "messageChannelMessageAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d0f0efa4-9f44-4812-96f9-d91ee933a5e8", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f2561dd5-c695-4635-816c-27175470b285", - "name": "messageChannel" - } - } - }, - { - "__typename": "field", - "id": "6484b400-3a26-4e67-94d4-f286e758d32d", - "type": "SELECT", - "name": "syncStage", - "label": "Sync stage", - "description": "Sync stage", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'FULL_MESSAGE_LIST_FETCH_PENDING'", - "options": [ - { - "id": "542073b7-5d8d-456a-9f97-c4318c965ee1", - "color": "blue", - "label": "Full messages list fetch pending", - "value": "FULL_MESSAGE_LIST_FETCH_PENDING", - "position": 0 - }, - { - "id": "9b6e03b6-7f79-4af9-adc6-be179c7e12ba", - "color": "blue", - "label": "Partial messages list fetch pending", - "value": "PARTIAL_MESSAGE_LIST_FETCH_PENDING", - "position": 1 - }, - { - "id": "1e551bc2-cc65-4b02-9538-3964763ed964", - "color": "orange", - "label": "Messages list fetch ongoing", - "value": "MESSAGE_LIST_FETCH_ONGOING", - "position": 2 - }, - { - "id": "d9fb2786-ccbb-4c26-a8e8-efa09cec9779", - "color": "blue", - "label": "Messages import pending", - "value": "MESSAGES_IMPORT_PENDING", - "position": 3 - }, - { - "id": "a0c3a8c3-be71-4b7e-9fbd-79e9d564e033", - "color": "orange", - "label": "Messages import ongoing", - "value": "MESSAGES_IMPORT_ONGOING", - "position": 4 - }, - { - "id": "0bea2ea8-99cf-4a04-80a7-197022bab014", - "color": "red", - "label": "Failed", - "value": "FAILED", - "position": 5 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5d8b9142-82f7-44cc-931b-457a7895c864", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "53050215-3982-4ed7-857f-6089bb66e63e", - "type": "BOOLEAN", - "name": "excludeGroupEmails", - "label": "Exclude group emails", - "description": "Exclude group emails", - "icon": "IconUsersGroup", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "95465535-60f1-4a23-8c0d-0e3c98b986da", - "type": "BOOLEAN", - "name": "excludeNonProfessionalEmails", - "label": "Exclude non professional emails", - "description": "Exclude non professional emails", - "icon": "IconBriefcase", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "71179534-e47a-4f19-916e-adad2e0360c8", - "type": "SELECT", - "name": "contactAutoCreationPolicy", - "label": "Contact auto creation policy", - "description": "Automatically create People records when receiving or sending emails", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'SENT'", - "options": [ - { - "id": "61e4a068-f941-4951-940b-4534b55036e5", - "color": "green", - "label": "Sent and Received", - "value": "SENT_AND_RECEIVED", - "position": 0 - }, - { - "id": "8922afcf-f166-48cd-bdbe-1fc29cbc9d83", - "color": "blue", - "label": "Sent", - "value": "SENT", - "position": 1 - }, - { - "id": "e992588f-fb3c-483c-9bd8-3798c944246b", - "color": "red", - "label": "None", - "value": "NONE", - "position": 2 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "697a053e-b751-469e-a46a-c1d9ca189ed0", - "type": "BOOLEAN", - "name": "isSyncEnabled", - "label": "Is Sync Enabled", - "description": "Is Sync Enabled", - "icon": "IconRefresh", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": true, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants", - "labelSingular": "Calendar event participant", - "labelPlural": "Calendar event participants", - "description": "Calendar event participants", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "40ddb08c-2b24-429b-8c38-538aa6793d9e", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "57c875cf-6453-4e41-9bd5-3c804464de3b", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Workspace Member id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "cc3faae7-be01-435f-aafb-835e0f7cd8e4", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "00b58d77-0409-4fb0-a6b4-7a4b4f4ec71b", - "type": "UUID", - "name": "calendarEventId", - "label": "Event ID id (foreign key)", - "description": "Event ID id foreign key", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "40ddb08c-2b24-429b-8c38-538aa6793d9e", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b2982cd8-c796-4718-b74b-298b46d19841", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Workspace Member", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "67aaa999-8332-43ba-8830-76bf48d53cf2", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b2982cd8-c796-4718-b74b-298b46d19841", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6623a9d7-e137-4709-a592-02ae76e5bfd7", - "name": "calendarEventParticipants" - } - } - }, - { - "__typename": "field", - "id": "00578ebe-8d35-4f34-8f67-02485d704025", - "type": "SELECT", - "name": "responseStatus", - "label": "Response Status", - "description": "Response Status", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'NEEDS_ACTION'", - "options": [ - { - "id": "fd0a498b-4600-41c1-9a96-cebe90c41a55", - "color": "orange", - "label": "Needs Action", - "value": "NEEDS_ACTION", - "position": 0 - }, - { - "id": "81a5956c-b7d4-45a2-afde-0f956ce68aee", - "color": "red", - "label": "Declined", - "value": "DECLINED", - "position": 1 - }, - { - "id": "4adf3c41-f7a4-4614-8226-0ce0918c0503", - "color": "yellow", - "label": "Tentative", - "value": "TENTATIVE", - "position": 2 - }, - { - "id": "23c5a1c2-1d16-40ce-a747-660be35bd518", - "color": "green", - "label": "Accepted", - "value": "ACCEPTED", - "position": 3 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "da6e6808-841d-4079-b050-a2d59e7b1a37", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "bd330d82-e102-43ed-88fe-caf5abe486f4", - "type": "TEXT", - "name": "displayName", - "label": "Display Name", - "description": "Display Name", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6cf6873a-af1e-46ab-aacd-80b2254b0239", - "type": "BOOLEAN", - "name": "isOrganizer", - "label": "Is Organizer", - "description": "Is Organizer", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": false, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "61840181-5ec1-4e0f-a133-b705d19a38ff", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "9ae2ccf9-1390-4861-a771-324ab4310f33", - "type": "RELATION", - "name": "calendarEvent", - "label": "Event ID", - "description": "Event ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "28688620-c3df-463d-a655-ad6435f6215b", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "9ae2ccf9-1390-4861-a771-324ab4310f33", - "name": "calendarEvent" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "37d40c3f-e106-4348-af22-201659bbd8a6", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c9a2189f-abb1-4166-9d1d-f870f0e32b3e", - "name": "calendarEventParticipants" - } - } - }, - { - "__typename": "field", - "id": "a7eb211d-4481-4269-99d7-cf2183b45598", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "20d67b64-4e67-44a1-81c7-116c0c8c6368", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a7eb211d-4481-4269-99d7-cf2183b45598", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ff2881da-89f6-4f15-8f0a-e3f355ea3b94", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d44bf743-b557-47d4-9341-04114fd05d52", - "name": "calendarEventParticipants" - } - } - }, - { - "__typename": "field", - "id": "eb0946e3-084e-477f-8aba-3c88ed29cf3b", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers", - "labelSingular": "Workspace Member", - "labelPlural": "Workspace Members", - "description": "A workspace member", - "icon": "IconUserCircle", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "db603745-ca4c-4a32-bb0d-d475216111d9", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "b97b813e-dc34-4c16-b7d3-7dee169c3f11", - "type": "UUID", - "name": "userId", - "label": "User Id", - "description": "Associated User Id", - "icon": "IconCircleUsers", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "12d19915-891e-4a4e-8c42-49a12639264f", - "type": "RELATION", - "name": "authoredComments", - "label": "Authored comments", - "description": "Authored comments", - "icon": "IconComment", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "4917f689-5cc2-4716-b4ab-6906aef009b3", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "12d19915-891e-4a4e-8c42-49a12639264f", - "name": "authoredComments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "3af96291-b873-402f-bd90-f4731984c8dd", - "nameSingular": "comment", - "namePlural": "comments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "1a7fec45-dd33-43a0-9302-5b0b9052d2f0", - "name": "author" - } - } - }, - { - "__typename": "field", - "id": "5e889b07-de1e-47f0-aeb9-301a684bd6a4", - "type": "RELATION", - "name": "authoredActivities", - "label": "Authored activities", - "description": "Activities created by the workspace member", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "6d4e8025-7ee9-4079-ae80-b18de7b5ff4e", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5e889b07-de1e-47f0-aeb9-301a684bd6a4", - "name": "authoredActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "beac3449-af10-43a2-9abb-276a798df3de", - "name": "author" - } - } - }, - { - "__typename": "field", - "id": "dd7ee456-52bf-4335-bee9-7ba18a1e9a09", - "type": "RELATION", - "name": "messageParticipants", - "label": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "94c22c13-b00a-4f60-b2d2-f34b9efe6aa2", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "dd7ee456-52bf-4335-bee9-7ba18a1e9a09", - "name": "messageParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b889efa2-e58a-471c-b258-3c5ef2fa09e9", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a7036ff9-a86d-4290-9f2a-cc360c86fe1e", - "name": "workspaceMember" - } - } - }, - { - "__typename": "field", - "id": "de44f939-76d9-4c1a-96aa-7c5a646f2045", - "type": "RELATION", - "name": "accountOwnerForCompanies", - "label": "Account Owner For Companies", - "description": "Account owner for companies", - "icon": "IconBriefcase", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0896a728-e2cf-4032-9af2-a471645e9697", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "de44f939-76d9-4c1a-96aa-7c5a646f2045", - "name": "accountOwnerForCompanies" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "f1231579-8e7d-4b84-9a60-41844902f2c4", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3ece1b4d-c052-4b32-bd2a-ba0f8c8b6f3e", - "name": "accountOwner" - } - } - }, - { - "__typename": "field", - "id": "45b45740-d6cd-476c-af07-f6ed323953b6", - "type": "RELATION", - "name": "auditLogs", - "label": "Audit Logs", - "description": "Audit Logs linked to the workspace member", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "025cb05b-3bdb-4418-8a07-8b7a8c22dbc1", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "45b45740-d6cd-476c-af07-f6ed323953b6", - "name": "auditLogs" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "189129d8-8037-4edf-9c91-63001ab52370", - "nameSingular": "auditLog", - "namePlural": "auditLogs" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0e09a491-d3bd-4a23-9c50-cda32acbc7ef", - "name": "workspaceMember" - } - } - }, - { - "__typename": "field", - "id": "956a552e-b573-4d31-afd4-b65cb8f2b4b8", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "1b2d1e2c-290d-4a0e-adf9-192e5fac103c", - "type": "RELATION", - "name": "authoredAttachments", - "label": "Authored attachments", - "description": "Attachments created by the workspace member", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c86299f2-7210-4c89-a2ab-29e17f21edc8", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "1b2d1e2c-290d-4a0e-adf9-192e5fac103c", - "name": "authoredAttachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "9a53b4e1-bce2-4160-8ce3-028e14b2abb7", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3d2bbb4e-e908-4bc5-97d7-a152dd7652bf", - "name": "author" - } - } - }, - { - "__typename": "field", - "id": "89b50259-5ed6-4504-9c89-3f4457dc43a6", - "type": "RELATION", - "name": "blocklist", - "label": "Blocklist", - "description": "Blocklisted handles", - "icon": "IconForbid2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d9eb657a-f97b-4cec-af79-0e113d47279b", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "89b50259-5ed6-4504-9c89-3f4457dc43a6", - "name": "blocklist" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "033ae6fd-c59e-475e-ba93-bbc1b2b185a5", - "nameSingular": "blocklist", - "namePlural": "blocklists" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "597169c3-ad77-4a48-8a2a-94b7cc155e25", - "name": "workspaceMember" - } - } - }, - { - "__typename": "field", - "id": "aa2d23d6-1303-4347-b252-48dd9f9bd52b", - "type": "SELECT", - "name": "dateFormat", - "label": "Date format", - "description": "User's preferred date format", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'SYSTEM'", - "options": [ - { - "id": "6a0b0a55-082b-4d1e-ac29-2d423a05744c", - "color": "turquoise", - "label": "System", - "value": "SYSTEM", - "position": 0 - }, - { - "id": "eee2180b-4f0e-41ef-9a6a-2e2938322270", - "color": "red", - "label": "Month First", - "value": "MONTH_FIRST", - "position": 1 - }, - { - "id": "4cbf15d8-a63c-4615-ad5d-74e798199ffd", - "color": "purple", - "label": "Day First", - "value": "DAY_FIRST", - "position": 2 - }, - { - "id": "c391f77e-7728-4369-ad35-60a1d84ee49e", - "color": "sky", - "label": "Year First", - "value": "YEAR_FIRST", - "position": 3 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "95db483c-ff8c-430f-a995-de4f96fff94b", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "ddadaf73-086d-4453-b631-5e16afce87b5", - "type": "TEXT", - "name": "locale", - "label": "Language", - "description": "Preferred language", - "icon": "IconLanguage", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'en'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6623a9d7-e137-4709-a592-02ae76e5bfd7", - "type": "RELATION", - "name": "calendarEventParticipants", - "label": "Calendar Event Participants", - "description": "Calendar Event Participants", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "67aaa999-8332-43ba-8830-76bf48d53cf2", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6623a9d7-e137-4709-a592-02ae76e5bfd7", - "name": "calendarEventParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2128a43e-af47-44bf-b7e9-5d00ddd27a99", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b2982cd8-c796-4718-b74b-298b46d19841", - "name": "workspaceMember" - } - } - }, - { - "__typename": "field", - "id": "7b895d3d-e6df-4b0e-8497-28d91431de59", - "type": "TEXT", - "name": "avatarUrl", - "label": "Avatar Url", - "description": "Workspace member avatar", - "icon": "IconFileUpload", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4d977e6a-4d0e-4bca-b743-9bc3df1744d6", - "type": "RELATION", - "name": "connectedAccounts", - "label": "Connected accounts", - "description": "Connected accounts", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "9a65de46-ef09-429f-b7ba-31cb8a8c7038", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4d977e6a-4d0e-4bca-b743-9bc3df1744d6", - "name": "connectedAccounts" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "90e63030-f26d-46c8-b27a-13686b717538", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "8c36e0eb-108e-4797-97a1-b9b5ea096180", - "name": "accountOwner" - } - } - }, - { - "__typename": "field", - "id": "156aa42f-667a-46aa-9aea-ff8472f28509", - "type": "TEXT", - "name": "timeZone", - "label": "Time zone", - "description": "User time zone", - "icon": "IconTimezone", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'system'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "fefca31b-ba53-4860-b04e-5b9944587693", - "type": "RELATION", - "name": "assignedTasks", - "label": "Assigned tasks", - "description": "Tasks assigned to the workspace member", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5527b9f6-55ec-4efd-b244-03e91b01e91b", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fefca31b-ba53-4860-b04e-5b9944587693", - "name": "assignedTasks" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "99f8caa6-263c-4690-8dc0-eb7645304cf5", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "53347f0d-658a-45b0-91b5-2088adbeaaf0", - "name": "assignee" - } - } - }, - { - "__typename": "field", - "id": "db603745-ca4c-4a32-bb0d-d475216111d9", - "type": "FULL_NAME", - "name": "name", - "label": "Name", - "description": "Workspace member name", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": { - "lastName": "''", - "firstName": "''" - }, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "83def86f-9dbd-4606-a7a1-844c1d0f3080", - "type": "TEXT", - "name": "userEmail", - "label": "User Email", - "description": "Related user email address", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a147a0df-eb28-4259-a304-0460f92adf30", - "type": "RELATION", - "name": "assignedActivities", - "label": "Assigned activities", - "description": "Activities assigned to the workspace member", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "cf9ac76a-9f22-4252-a00a-63cc45fcabc4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a147a0df-eb28-4259-a304-0460f92adf30", - "name": "assignedActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "cf6f8138-3445-4a36-b137-41ebb8f2e3dc", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ea9aa19c-22d8-4b72-83ff-78d9653c27c4", - "name": "assignee" - } - } - }, - { - "__typename": "field", - "id": "56975f55-db1f-464f-b03e-2ecadb8b59cd", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f537669a-4524-4dfc-91d3-79438e2a481e", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the workspace member", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "b0f40da3-1fda-4803-be21-14a2755bc834", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f537669a-4524-4dfc-91d3-79438e2a481e", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d19be8c8-2cf4-4c29-80ae-0d1841dc11c1", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "407a2cbc-6c15-41dd-942c-5322d273bec3", - "name": "workspaceMember" - } - } - }, - { - "__typename": "field", - "id": "f1374fe7-1b98-4868-96b0-63d64996e397", - "type": "SELECT", - "name": "timeFormat", - "label": "Time format", - "description": "User's preferred time format", - "icon": "IconClock2", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'SYSTEM'", - "options": [ - { - "id": "91233e08-341a-4dfd-bb89-450a4f40c579", - "color": "sky", - "label": "System", - "value": "SYSTEM", - "position": 0 - }, - { - "id": "9e680299-7bee-432a-9e65-dfa9ad270769", - "color": "red", - "label": "24HRS", - "value": "HOUR_24", - "position": 1 - }, - { - "id": "c1d9ba3d-291b-4636-9909-14e41a5812db", - "color": "purple", - "label": "12HRS", - "value": "HOUR_12", - "position": 2 - } - ], - "relationDefinition": null - }, - { - "__typename": "field", - "id": "6c5481e3-b9a4-4298-b011-14dfc7ed3be4", - "type": "TEXT", - "name": "colorScheme", - "label": "Color Scheme", - "description": "Preferred color scheme", - "icon": "IconColorSwatch", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'Light'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "4ae17923-dd16-45c7-9df3-8cee92584a52", - "type": "RELATION", - "name": "timelineActivities", - "label": "Events", - "description": "Events linked to the workspace member", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "170c6f88-63b3-41ff-9e5d-044968a062a4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4ae17923-dd16-45c7-9df3-8cee92584a52", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "94ef21ab-5eca-4c80-b378-2a207dcca2e4", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "dba195e2-63d6-42ca-94aa-42c87b4306ea", - "name": "workspaceMember" - } - } - } - ] - }, - { - "__typename": "object", - "id": "1b5e63b9-9fc3-485d-86ff-de70ff17a665", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "viewSort", - "namePlural": "viewSorts", - "labelSingular": "View Sort", - "labelPlural": "View Sorts", - "description": "(System) View Sorts", - "icon": "IconArrowsSort", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "35329333-5b6c-4160-8a2a-48ff1f40c500", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "14b9ac38-97c5-44f9-b4f5-a6bbc18dd87c", - "type": "TEXT", - "name": "direction", - "label": "Direction", - "description": "View Sort direction", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "'asc'", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "673fb6fb-5123-4336-9b4b-e4b268c1cffe", - "type": "RELATION", - "name": "view", - "label": "View", - "description": "View Sort related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "53abf7c2-810d-478b-bb2d-689f31322d67", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1b5e63b9-9fc3-485d-86ff-de70ff17a665", - "nameSingular": "viewSort", - "namePlural": "viewSorts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "673fb6fb-5123-4336-9b4b-e4b268c1cffe", - "name": "view" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "90df20e5-c655-474f-bb98-b423652e36df", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4abadd14-56cd-48e4-8013-7b46de4ffe22", - "name": "viewSorts" - } - } - }, - { - "__typename": "field", - "id": "75ec0934-d5bf-4a0a-9b18-cadb0a56e489", - "type": "UUID", - "name": "fieldMetadataId", - "label": "Field Metadata Id", - "description": "View Sort target field", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "35329333-5b6c-4160-8a2a-48ff1f40c500", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "a5baa6c5-3cf2-4f2d-a7ff-b5d7176a1498", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "bdfdc845-c48e-4671-b07b-579ad800408f", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "e9572e7f-c327-4da6-93c0-64bf5a465de4", - "type": "UUID", - "name": "viewId", - "label": "View id (foreign key)", - "description": "View Sort related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "189129d8-8037-4edf-9c91-63001ab52370", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "auditLog", - "namePlural": "auditLogs", - "labelSingular": "Audit Log", - "labelPlural": "Audit Logs", - "description": "An audit log of actions performed in the system", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "8407e296-b80d-4bcb-9e33-8bf9bf942625", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "3cc5ffd0-17da-42f6-87bb-2021b3ad41e3", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "78632f28-cdfa-4f91-a019-de3d7b711d4d", - "type": "RAW_JSON", - "name": "context", - "label": "Event context", - "description": "Json object to provide context (user, device, workspace, etc.)", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "b706c04a-686d-45a4-b2a0-f28fb8e743fa", - "type": "TEXT", - "name": "objectMetadataId", - "label": "Object metadata id", - "description": "Object metadata id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "fadfc339-188a-4563-a502-478e72ce09c3", - "type": "TEXT", - "name": "objectName", - "label": "Object name", - "description": "Object name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "f90ab65b-1171-4c1e-9603-7b744058c317", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "5e4525b9-5a43-414e-ad35-f1eb19858a27", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "522226c1-3446-423c-b6a6-44776f59b076", - "type": "RAW_JSON", - "name": "properties", - "label": "Event details", - "description": "Json value for event details", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "0e09a491-d3bd-4a23-9c50-cda32acbc7ef", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Event workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "025cb05b-3bdb-4418-8a07-8b7a8c22dbc1", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "189129d8-8037-4edf-9c91-63001ab52370", - "nameSingular": "auditLog", - "namePlural": "auditLogs" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0e09a491-d3bd-4a23-9c50-cda32acbc7ef", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "45b45740-d6cd-476c-af07-f6ed323953b6", - "name": "auditLogs" - } - } - }, - { - "__typename": "field", - "id": "8407e296-b80d-4bcb-9e33-8bf9bf942625", - "type": "TEXT", - "name": "name", - "label": "Event name", - "description": "Event name/type", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "9d2e7f7f-d1a9-4f42-bfe9-437fe0e72088", - "type": "UUID", - "name": "recordId", - "label": "Record id", - "description": "Record id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "7d8d0023-c979-4c93-850e-7fce90b500ae", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Event workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - } - ] - }, - { - "__typename": "object", - "id": "033ae6fd-c59e-475e-ba93-bbc1b2b185a5", - "dataSourceId": "9af88cea-baa2-4c00-bc22-c55cfbcd7e3c", - "nameSingular": "blocklist", - "namePlural": "blocklists", - "labelSingular": "Blocklist", - "labelPlural": "Blocklists", - "description": "Blocklist", - "icon": "IconForbid2", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "labelIdentifierFieldMetadataId": "231e4111-5613-4925-97e4-6c84bcee60b7", - "imageIdentifierFieldMetadataId": null, - "fields": [ - { - "__typename": "field", - "id": "2b4e97a1-b598-47f4-83ab-4ec184dac6ed", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "54603149-fda9-4d0b-87eb-0c093c297599", - "type": "UUID", - "name": "workspaceMemberId", - "label": "WorkspaceMember id (foreign key)", - "description": "WorkspaceMember id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "597169c3-ad77-4a48-8a2a-94b7cc155e25", - "type": "RELATION", - "name": "workspaceMember", - "label": "WorkspaceMember", - "description": "WorkspaceMember", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d9eb657a-f97b-4cec-af79-0e113d47279b", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "033ae6fd-c59e-475e-ba93-bbc1b2b185a5", - "nameSingular": "blocklist", - "namePlural": "blocklists" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "597169c3-ad77-4a48-8a2a-94b7cc155e25", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1e9ad365-ccb9-4dec-b42f-13b6e86477e3", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "89b50259-5ed6-4504-9c89-3f4457dc43a6", - "name": "blocklist" - } - } - }, - { - "__typename": "field", - "id": "231e4111-5613-4925-97e4-6c84bcee60b7", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "88c47801-89ed-43ee-8b0d-172eccef5445", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Last update", - "description": "Last time the record was changed", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null - }, - { - "__typename": "field", - "id": "c1f89a93-80b6-4601-a330-8d9f7d5ef894", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-05T16:38:57.285Z", - "updatedAt": "2024-08-05T16:38:57.285Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null - } - ] - } - ]; - - // Todo fix typing here (the backend is not in sync with the frontend) - return mockArray as ObjectMetadataItem[]; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isObjectMetadataReadOnly.ts b/packages/twenty-front/src/modules/object-metadata/utils/isObjectMetadataReadOnly.ts new file mode 100644 index 000000000000..c6455e009c9d --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/isObjectMetadataReadOnly.ts @@ -0,0 +1,8 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; + +export const isObjectMetadataReadOnly = ( + objectMetadataItem: Pick<ObjectMetadataItem, 'isRemote' | 'nameSingular'>, +) => + objectMetadataItem.isRemote || + isWorkflowSubObjectMetadata(objectMetadataItem.nameSingular); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObjectMetadata.ts b/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObjectMetadata.ts new file mode 100644 index 000000000000..1ad6c0cbb290 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObjectMetadata.ts @@ -0,0 +1,7 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; + +export const isWorkflowSubObjectMetadata = ( + objectMetadataNameSingular?: string, +) => + objectMetadataNameSingular === CoreObjectNameSingular.WorkflowVersion || + objectMetadataNameSingular === CoreObjectNameSingular.WorkflowRun; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts index d379313a0496..bf29d99ee1f9 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts @@ -26,10 +26,8 @@ export const mapFieldMetadataToGraphQLQuery = ({ const fieldIsSimpleValue = [ FieldMetadataType.Uuid, FieldMetadataType.Text, - FieldMetadataType.Phone, FieldMetadataType.DateTime, FieldMetadataType.Date, - FieldMetadataType.Email, FieldMetadataType.Number, FieldMetadataType.Boolean, FieldMetadataType.Rating, @@ -97,14 +95,6 @@ ${mapObjectMetadataToGraphQLQuery({ }`; } - if (fieldType === FieldMetadataType.Link) { - return `${field.name} -{ - label - url -}`; - } - if (fieldType === FieldMetadataType.Links) { return `${field.name} { 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/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts index 9c714cb95a92..db3506c2d12c 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts @@ -11,6 +11,12 @@ export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({ pagedObjectMetadataItems?.objects.edges.map((object) => ({ ...object.node, fields: object.node.fields.edges.map((field) => field.node), + indexMetadatas: object.node.indexMetadatas.edges.map((index) => ({ + ...index.node, + indexFieldMetadatas: index.node.indexFieldMetadatas?.edges.map( + (indexField) => indexField.node, + ), + })), })) ?? []; return formattedObjects; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapSoftDeleteFieldsToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapSoftDeleteFieldsToGraphQLQuery.ts new file mode 100644 index 000000000000..701e524b56ab --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapSoftDeleteFieldsToGraphQLQuery.ts @@ -0,0 +1,16 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const mapSoftDeleteFieldsToGraphQLQuery = ( + objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>, +): string => { + const softDeleteFields = ['id', 'deletedAt']; + + const fieldsThatShouldBeQueried = objectMetadataItem.fields.filter( + (field) => field.isActive && softDeleteFields.includes(field.name), + ); + + return `{ + __typename + ${fieldsThatShouldBeQueried.map((field) => field.name).join('\n')} + }`; +}; 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-metadata/validation-schemas/fieldMetadataItemSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/fieldMetadataItemSchema.ts index 42a976b5d2e1..5d4ddc67cbb2 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/fieldMetadataItemSchema.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/fieldMetadataItemSchema.ts @@ -20,6 +20,7 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => { isActive: z.boolean(), isCustom: z.boolean(), isNullable: z.boolean(), + isUnique: z.boolean(), isSystem: z.boolean(), label: metadataLabelSchema(existingLabels), name: camelCaseStringSchema, @@ -35,6 +36,7 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => { ) .nullable() .optional(), + settings: z.any().optional(), relationDefinition: z .object({ __typename: z.literal('RelationDefinition').optional(), diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/indexFieldMetadataItemSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/indexFieldMetadataItemSchema.ts new file mode 100644 index 000000000000..a88fe26ecde5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/indexFieldMetadataItemSchema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +import { IndexFieldMetadataItem } from '@/object-metadata/types/IndexFieldMetadataItem'; + +export const indexFieldMetadataItemSchema = z.object({ + __typename: z.literal('indexField'), + fieldMetadataId: z.string().uuid(), + id: z.string(), + createdAt: z.string(), + updatedAt: z.string(), + order: z.number(), +}) satisfies z.ZodType<IndexFieldMetadataItem>; diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/indexMetadataItemSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/indexMetadataItemSchema.ts new file mode 100644 index 000000000000..50d80da705ec --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/indexMetadataItemSchema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +import { IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem'; +import { indexFieldMetadataItemSchema } from '@/object-metadata/validation-schemas/indexFieldMetadataItemSchema'; +import { IndexType } from '~/generated-metadata/graphql'; + +export const indexMetadataItemSchema = z.object({ + __typename: z.literal('index'), + id: z.string().uuid(), + name: z.string(), + indexFieldMetadatas: z.array(indexFieldMetadataItemSchema), + createdAt: z.string(), + updatedAt: z.string(), + indexType: z.nativeEnum(IndexType), + indexWhereClause: z.string().nullable(), + isUnique: z.boolean(), + objectMetadata: z.any(), +}) satisfies z.ZodType<IndexMetadataItem>; diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/objectMetadataItemSchema.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/objectMetadataItemSchema.ts index 145d9f68091e..a12b072ebc35 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/objectMetadataItemSchema.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/objectMetadataItemSchema.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema'; +import { indexMetadataItemSchema } from '@/object-metadata/validation-schemas/indexMetadataItemSchema'; import { metadataLabelSchema } from '@/object-metadata/validation-schemas/metadataLabelSchema'; import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema'; @@ -11,6 +12,7 @@ export const objectMetadataItemSchema = z.object({ dataSourceId: z.string().uuid(), description: z.string().trim().nullable().optional(), fields: z.array(fieldMetadataItemSchema()), + indexMetadatas: z.array(indexMetadataItemSchema), icon: z.string().startsWith('Icon').trim(), id: z.string().uuid(), imageIdentifierFieldMetadataId: z.string().uuid().nullable(), diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts index 693236975e51..90105c700526 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts @@ -18,11 +18,11 @@ export const useDeleteRecordFromCache = ({ const { objectMetadataItems } = useObjectMetadataItems(); - return (recordToDelete: ObjectRecord) => { + return (recordToDestroy: ObjectRecord) => { deleteRecordFromCache({ objectMetadataItem, objectMetadataItems, - recordToDelete, + recordToDestroy, cache: apolloClient.cache, }); }; 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<ObjectMetadataItem, 'fields' | 'namePlural' | 'nameSingular'> + | 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<ObjectMetadataItem, 'fields' | 'namePlural' | 'nameSingular'> + | 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/deleteRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts index ec9ec8b3a4a3..10bc657ca3d7 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts @@ -1,6 +1,6 @@ import { ApolloCache } from '@apollo/client'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; @@ -8,21 +8,21 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; export const deleteRecordFromCache = ({ objectMetadataItem, objectMetadataItems, - recordToDelete, + recordToDestroy, cache, }: { objectMetadataItem: ObjectMetadataItem; objectMetadataItems: ObjectMetadataItem[]; - recordToDelete: ObjectRecord; + recordToDestroy: ObjectRecord; cache: ApolloCache<object>; }) => { - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, objectMetadataItems, - recordsToDelete: [ + recordsToDestroy: [ { - ...recordToDelete, + ...recordToDestroy, __typename: getObjectTypename(objectMetadataItem.nameSingular), }, ], 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..21db7bcf49f4 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 = <T extends ObjectRecord>({ RelationDefinitionType.OneToMany ) { const oneToManyObjectMetadataItem = objectMetadataItems.find( - (item) => item.namePlural === fieldName, + (item) => + item.namePlural === + field.relationDefinition?.targetObjectMetadata.namePlural, ); if (!oneToManyObjectMetadataItem) { @@ -129,7 +131,6 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({ }, ]; } - case FieldMetadataType.Link: case FieldMetadataType.Links: case FieldMetadataType.Address: case FieldMetadataType.FullName: diff --git a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts index 72573ea2133e..6c1615b17afa 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts @@ -92,6 +92,7 @@ export type LinksFilter = { export type ActorFilter = { name?: StringFilter; + source?: IsFilter; }; export type EmailsFilter = { diff --git a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationSearchResult.ts b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationSearchResult.ts new file mode 100644 index 000000000000..7cd2f5e314b5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationSearchResult.ts @@ -0,0 +1,5 @@ +import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; + +export type RecordGqlOperationSearchResult = { + [objectNamePlural: string]: RecordGqlConnection; +}; 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 2f0fdfcee60b..000000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts +++ /dev/null @@ -1,37 +0,0 @@ -export const PERSON_FRAGMENT = ` - __typename - updatedAt - myCustomObjectId - whatsapp - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - name { - firstName - lastName - } - email - position - createdBy { - source - workspaceMemberId - name - } - avatarUrl - jobTitle - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - performanceRating - createdAt - phone - id - city - companyId - intro - workPrefereance -` 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 967f1ac6a5d9..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} } } `; @@ -36,7 +36,10 @@ export const responseData = { firstName: '', lastName: '', }, - phone: '', + phones: { + primaryPhoneCountryCode: '', + primaryPhoneNumber: '', + }, linkedinLink: { primaryLinkUrl: '', primaryLinkLabel: '', 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 cb22a045d519..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} } } `; @@ -41,7 +41,10 @@ export const responseData = { firstName: '', lastName: '', }, - phone: '', + phones: { + primaryPhoneCountryCode: '', + primaryPhoneNumber: '', + }, linkedinLink: { primaryLinkUrl: '', primaryLinkLabel: '', 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 4cc34f4932eb..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} } } `; @@ -18,13 +18,11 @@ const basePerson = { }, createdAt: '', city: '', - email: '', jobTitle: '', name: { firstName: '', lastName: '', }, - phone: '', linkedinLink: { primaryLinkUrl: '', primaryLinkLabel: '', 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 }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +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 }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +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..89d5d120592e 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 { renderHook } from '@testing-library/react'; import { query, @@ -9,8 +6,10 @@ import { variables, } from '@/object-record/hooks/__mocks__/useDeleteManyRecords'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; +import { act } from 'react'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -const people = [ +const personIds = [ 'a7286b9a-c039-4a89-9567-2dfa7953cda9', '37faabcd-cb39-4a0a-8618-7e3fda9afca0', ]; @@ -29,13 +28,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useDeleteManyRecords', () => { it('works as expected', async () => { @@ -47,7 +42,7 @@ describe('useDeleteManyRecords', () => { ); await act(async () => { - const res = await result.current.deleteManyRecords(people); + const res = await result.current.deleteManyRecords(personIds); expect(res).toBeDefined(); expect(res[0]).toHaveProperty('id'); }); 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 }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +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 fc32df77ebb0..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,10 +1,8 @@ -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 { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { mockPageSize, peopleMockWithIdsOnly, @@ -17,7 +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 { 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 }) => ( - <RecoilRoot> - <SnackBarManagerScopeInternalContext.Provider - value={{ - scopeId: 'snack-bar-manager', - }} - > - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </SnackBarManagerScopeInternalContext.Provider> - </RecoilRoot> - ); - const { result } = renderHook( () => { const [, setObjectMetadataItems] = useRecoilState( @@ -75,7 +64,7 @@ describe('useFetchAllRecordIds', () => { ); useEffect(() => { - setObjectMetadataItems(getObjectMetadataItemsMock()); + setObjectMetadataItems(generatedMockObjectMetadataItems); }, [setObjectMetadataItems]); return useFetchAllRecordIds({ 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 }) => ( - <RecoilRoot> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </SnackBarProviderScope> - </RecoilRoot> -); +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 6b7d71c18b5d..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,18 +1,16 @@ -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'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { query, responseData, 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 { 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 }) => ( - <RecoilRoot> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </SnackBarProviderScope> - </RecoilRoot> -); - +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(); @@ -65,11 +44,9 @@ describe('useFindManyRecords', () => { locale: 'en', }); - const mockObjectMetadataItems = getObjectMetadataItemsMock(); - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFindManyRecords({ objectNameSingular: 'person', 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 }) => ( - <RecoilRoot> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </SnackBarProviderScope> - </RecoilRoot> -); +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 6d29107a3204..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 @@ -1,11 +1,11 @@ -import { ReactNode } from 'react'; import { expect } from '@storybook/test'; import { renderHook } from '@testing-library/react'; +import { ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( <RecoilRoot> @@ -18,7 +18,7 @@ describe('useGenerateFindManyRecordsForMultipleMetadataItemsQuery', () => { const { result } = renderHook( () => { return useGenerateCombinedFindManyRecordsQuery({ - operationSignatures: getObjectMetadataItemsMock() + operationSignatures: generatedMockObjectMetadataItems .slice(0, 2) .map((item) => ({ objectNameSingular: item.nameSingular, 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 }) => ( - <RecoilRoot> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </SnackBarProviderScope> - </RecoilRoot> -); +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 ( - <RecoilRoot> + <HookMockWrapper> <ObjectNamePluralSetter> <RecordTableScope recordTableScopeId={getScopeIdFromComponentId(recordTableId)} onColumnsChange={onColumnsChange} > - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <MockedProvider addTypename={false}>{children}</MockedProvider> - </SnackBarProviderScope> + {children} </RecordTableScope> </ObjectNamePluralSetter> - </RecoilRoot> + </HookMockWrapper> ); }; 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 0ccd92897a89..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 { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,10 +6,20 @@ 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 = { name: { firstName: 'John', lastName: 'Doe' } }; -const updatePerson = { ...person, ...responseData, ...update }; +const update = { + name: { + firstName: 'John', + lastName: 'Doe', + }, +}; +const updatePerson = { + ...person, + ...responseData, + ...update, +}; const mocks = [ { @@ -28,13 +35,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <RecoilRoot> - <MockedProvider mocks={mocks} addTypename={false}> - {children} - </MockedProvider> - </RecoilRoot> -); +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/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts index b9313fae3ecc..c236baede835 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -2,9 +2,11 @@ import { useApolloClient } from '@apollo/client'; import { v4 } from 'uuid'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache'; +import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; @@ -67,7 +69,7 @@ export const useCreateManyRecords = < }, ); - const recordsCreatedInCache = []; + const recordsCreatedInCache: ObjectRecord[] = []; for (const recordToCreate of sanitizedCreateManyRecordsInput) { if (recordToCreate.id === null) { @@ -98,26 +100,46 @@ export const useCreateManyRecords = < objectMetadataItem.namePlural, ); - const createdObjects = await apolloClient.mutate({ - mutation: createManyRecordsMutation, - variables: { - data: sanitizedCreateManyRecordsInput, - upsert: upsert, - }, - update: (cache, { data }) => { - const records = data?.[mutationResponseField]; + const createdObjects = await apolloClient + .mutate({ + mutation: createManyRecordsMutation, + variables: { + data: sanitizedCreateManyRecordsInput, + upsert: upsert, + }, + update: (cache, { data }) => { + const records = data?.[mutationResponseField]; + + if (!records?.length || skipPostOptmisticEffect) return; - if (!records?.length || skipPostOptmisticEffect) return; + triggerCreateRecordsOptimisticEffect({ + cache, + objectMetadataItem, + recordsToCreate: records, + objectMetadataItems, + shouldMatchRootQueryFilter, + }); + }, + }) + .catch((error: Error) => { + recordsCreatedInCache.forEach((recordToDestroy) => { + deleteRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + recordToDestroy, + }); + }); - triggerCreateRecordsOptimisticEffect({ - cache, + triggerDestroyRecordsOptimisticEffect({ + cache: apolloClient.cache, objectMetadataItem, - recordsToCreate: records, + recordsToDestroy: recordsCreatedInCache, objectMetadataItems, - shouldMatchRootQueryFilter, }); - }, - }); + + throw error; + }); return createdObjects.data?.[mutationResponseField] ?? []; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts index f34ce0692f7a..73c9cd9897d6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -3,9 +3,11 @@ import { useState } from 'react'; import { v4 } from 'uuid'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache'; +import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; @@ -85,27 +87,49 @@ export const useCreateOneRecord = < const mutationResponseField = getCreateOneRecordMutationResponseField(objectNameSingular); - const createdObject = await apolloClient.mutate({ - mutation: createOneRecordMutation, - variables: { - input: sanitizedInput, - }, - update: (cache, { data }) => { - const record = data?.[mutationResponseField]; - - if (!record || skipPostOptmisticEffect) return; + const createdObject = await apolloClient + .mutate({ + mutation: createOneRecordMutation, + variables: { + input: sanitizedInput, + }, + update: (cache, { data }) => { + const record = data?.[mutationResponseField]; + + if (!record || skipPostOptmisticEffect) return; + + triggerCreateRecordsOptimisticEffect({ + cache, + objectMetadataItem, + recordsToCreate: [record], + objectMetadataItems, + shouldMatchRootQueryFilter, + }); + + setLoading(false); + }, + }) + .catch((error: Error) => { + if (!recordCreatedInCache) { + throw error; + } + + deleteRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + recordToDestroy: recordCreatedInCache, + }); - triggerCreateRecordsOptimisticEffect({ - cache, + triggerDestroyRecordsOptimisticEffect({ + cache: apolloClient.cache, objectMetadataItem, - recordsToCreate: [record], + recordsToDestroy: [recordCreatedInCache], objectMetadataItems, - shouldMatchRootQueryFilter, }); - setLoading(false); - }, - }); + throw error; + }); return createdObject.data?.[mutationResponseField] ?? null; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts index 35a65a507765..192b642bf79d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -1,15 +1,19 @@ import { useApolloClient } from '@apollo/client'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { apiConfigState } from '@/client-config/states/apiConfigState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize'; import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { isDefined } from '~/utils/isDefined'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { sleep } from '~/utils/sleep'; import { capitalize } from '~/utils/string/capitalize'; @@ -60,43 +64,126 @@ export const useDeleteManyRecords = ({ const deletedRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchIds = idsToDelete.slice( + const batchedIdsToDelete = idsToDelete.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); - const deletedRecordsResponse = await apolloClient.mutate({ - mutation: deleteManyRecordsMutation, - variables: { - filter: { id: { in: batchIds } }, - }, - optimisticResponse: options?.skipOptimisticEffect - ? undefined - : { - [mutationResponseField]: batchIds.map((idToDelete) => ({ - __typename: capitalize(objectNameSingular), - id: idToDelete, - })), - }, - update: options?.skipOptimisticEffect - ? undefined - : (cache, { data }) => { - const records = data?.[mutationResponseField]; - - if (!records?.length) return; - - const cachedRecords = records - .map((record) => getRecordFromCache(record.id, cache)) - .filter(isDefined); - - triggerDeleteRecordsOptimisticEffect({ - cache, + const currentTimestamp = new Date().toISOString(); + + const cachedRecords = batchedIdsToDelete + .map((idToDelete) => getRecordFromCache(idToDelete, apolloClient.cache)) + .filter(isDefined); + + if (!options?.skipOptimisticEffect) { + cachedRecords.forEach((cachedRecord) => { + if (!cachedRecord || !cachedRecord.id) { + return; + } + + const cachedRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: currentTimestamp }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + return null; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: computedOptimisticRecord, + }); + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: cachedRecordWithConnection, + updatedRecord: optimisticRecordWithConnection, + objectMetadataItems, + }); + }); + } + + const deletedRecordsResponse = await apolloClient + .mutate({ + mutation: deleteManyRecordsMutation, + variables: { + filter: { id: { in: batchedIdsToDelete } }, + }, + }) + .catch((error: Error) => { + cachedRecords.forEach((cachedRecord) => { + if (isUndefinedOrNull(cachedRecord?.id)) { + return; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: cachedRecord, + }); + + const cachedRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: currentTimestamp }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: computedOptimisticRecord, objectMetadataItem, - recordsToDelete: cachedRecords, objectMetadataItems, + computeReferences: true, }); - }, - }); + + if ( + !optimisticRecordWithConnection || + !cachedRecordWithConnection + ) { + return null; + } + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, + objectMetadataItems, + }); + }); + + throw error; + }); const deletedRecordsForThisBatch = deletedRecordsResponse.data?.[mutationResponseField] ?? []; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts index bf7dcd778fd0..969b830114ce 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -1,17 +1,19 @@ -import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; +import { useCallback } from 'react'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { capitalize } from '~/utils/string/capitalize'; type useDeleteOneRecordProps = { objectNameSingular: string; - refetchFindManyQuery?: boolean; }; export const useDeleteOneRecord = ({ @@ -38,32 +40,91 @@ export const useDeleteOneRecord = ({ const deleteOneRecord = useCallback( async (idToDelete: string) => { - const deletedRecord = await apolloClient.mutate({ - mutation: deleteOneRecordMutation, - variables: { idToDelete }, - optimisticResponse: { - [mutationResponseField]: { - __typename: capitalize(objectNameSingular), - id: idToDelete, - }, - }, - update: (cache, { data }) => { - const record = data?.[mutationResponseField]; + const currentTimestamp = new Date().toISOString(); + + const cachedRecord = getRecordFromCache(idToDelete, apolloClient.cache); + + const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: idToDelete, deletedAt: currentTimestamp }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + return null; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: computedOptimisticRecord, + }); - if (!record) return; + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: cachedRecordWithConnection, + updatedRecord: optimisticRecordWithConnection, + objectMetadataItems, + }); - const cachedRecord = getRecordFromCache(record.id, cache); + const deletedRecord = await apolloClient + .mutate({ + mutation: deleteOneRecordMutation, + variables: { + idToDelete: idToDelete, + }, + update: (cache, { data }) => { + const record = data?.[mutationResponseField]; - if (!cachedRecord) return; + if (!record || !cachedRecord) return; + + triggerUpdateRecordOptimisticEffect({ + cache, + objectMetadataItem, + currentRecord: cachedRecord, + updatedRecord: record, + objectMetadataItems, + }); + }, + }) + .catch((error: Error) => { + if (!cachedRecord) { + throw error; + } + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: cachedRecord, + }); - triggerDeleteRecordsOptimisticEffect({ - cache, + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, objectMetadataItems, }); - }, - }); + + throw error; + }); return deletedRecord.data?.[mutationResponseField] ?? null; }, @@ -73,7 +134,6 @@ export const useDeleteOneRecord = ({ getRecordFromCache, mutationResponseField, objectMetadataItem, - objectNameSingular, objectMetadataItems, ], ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts index ea8a3d458c77..ae7557bed7d5 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts @@ -1,6 +1,7 @@ import gql from 'graphql-tag'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { mapSoftDeleteFieldsToGraphQLQuery } from '@/object-metadata/utils/mapSoftDeleteFieldsToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -26,12 +27,11 @@ export const useDeleteOneRecordMutation = ({ ); const deleteOneRecordMutation = gql` - mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) { - ${mutationResponseField}(id: $idToDelete) { - id - } - } - `; + mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) { + ${mutationResponseField}(id: $idToDelete) + ${mapSoftDeleteFieldsToGraphQLQuery(objectMetadataItem)} + } +`; return { deleteOneRecordMutation, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts index 08f4d092a135..78895084487e 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -1,6 +1,7 @@ import { useApolloClient } from '@apollo/client'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { apiConfigState } from '@/client-config/states/apiConfigState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -60,43 +61,61 @@ export const useDestroyManyRecords = ({ const destroyedRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchIds = idsToDestroy.slice( + const batchedIdToDestroy = idsToDestroy.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); - const destroyedRecordsResponse = await apolloClient.mutate({ - mutation: destroyManyRecordsMutation, - variables: { - filter: { id: { in: batchIds } }, - }, - optimisticResponse: options?.skipOptimisticEffect - ? undefined - : { - [mutationResponseField]: batchIds.map((idToDestroy) => ({ - __typename: capitalize(objectNameSingular), - id: idToDestroy, - })), - }, - update: options?.skipOptimisticEffect - ? undefined - : (cache, { data }) => { - const records = data?.[mutationResponseField]; - - if (!records?.length) return; - - const cachedRecords = records - .map((record) => getRecordFromCache(record.id, cache)) - .filter(isDefined); - - triggerDeleteRecordsOptimisticEffect({ - cache, - objectMetadataItem, - recordsToDelete: cachedRecords, - objectMetadataItems, - }); - }, - }); + const originalRecords = batchedIdToDestroy + .map((recordId) => getRecordFromCache(recordId, apolloClient.cache)) + .filter(isDefined); + + const destroyedRecordsResponse = await apolloClient + .mutate({ + mutation: destroyManyRecordsMutation, + variables: { + filter: { id: { in: batchedIdToDestroy } }, + }, + optimisticResponse: options?.skipOptimisticEffect + ? undefined + : { + [mutationResponseField]: batchedIdToDestroy.map( + (idToDestroy) => ({ + __typename: capitalize(objectNameSingular), + id: idToDestroy, + }), + ), + }, + update: options?.skipOptimisticEffect + ? undefined + : (cache, { data }) => { + const records = data?.[mutationResponseField]; + + if (!records?.length) return; + + const cachedRecords = records + .map((record) => getRecordFromCache(record.id, cache)) + .filter(isDefined); + + triggerDestroyRecordsOptimisticEffect({ + cache, + objectMetadataItem, + recordsToDestroy: cachedRecords, + objectMetadataItems, + }); + }, + }) + .catch((error: Error) => { + if (originalRecords.length > 0) { + triggerCreateRecordsOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + recordsToCreate: originalRecords, + objectMetadataItems, + }); + } + throw error; + }); const destroyedRecordsForThisBatch = destroyedRecordsResponse.data?.[mutationResponseField] ?? []; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts index fc5d75d0a42f..1ea6aacea173 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts @@ -1,12 +1,15 @@ import { useApolloClient } from '@apollo/client'; import { useCallback } from 'react'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { capitalize } from '~/utils/string/capitalize'; type useDestroyOneRecordProps = { @@ -38,32 +41,49 @@ export const useDestroyOneRecord = ({ const destroyOneRecord = useCallback( async (idToDestroy: string) => { - const deletedRecord = await apolloClient.mutate({ - mutation: destroyOneRecordMutation, - variables: { idToDestroy }, - optimisticResponse: { - [mutationResponseField]: { - __typename: capitalize(objectNameSingular), - id: idToDestroy, + const originalRecord: ObjectRecord | null = getRecordFromCache( + idToDestroy, + apolloClient.cache, + ); + + const deletedRecord = await apolloClient + .mutate({ + mutation: destroyOneRecordMutation, + variables: { idToDestroy }, + optimisticResponse: { + [mutationResponseField]: { + __typename: capitalize(objectNameSingular), + id: idToDestroy, + }, }, - }, - update: (cache, { data }) => { - const record = data?.[mutationResponseField]; + update: (cache, { data }) => { + const record = data?.[mutationResponseField]; - if (!record) return; + if (!record) return; - const cachedRecord = getRecordFromCache(record.id, cache); + const cachedRecord = getRecordFromCache(record.id, cache); - if (!cachedRecord) return; + if (!cachedRecord) return; - triggerDeleteRecordsOptimisticEffect({ - cache, - objectMetadataItem, - recordsToDelete: [cachedRecord], - objectMetadataItems, - }); - }, - }); + triggerDestroyRecordsOptimisticEffect({ + cache, + objectMetadataItem, + recordsToDestroy: [cachedRecord], + objectMetadataItems, + }); + }, + }) + .catch((error: Error) => { + if (!isUndefinedOrNull(originalRecord)) { + triggerCreateRecordsOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + recordsToCreate: [originalRecord], + objectMetadataItems, + }); + } + throw error; + }); return deletedRecord.data?.[mutationResponseField] ?? null; }, 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..aac408d014e3 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'; @@ -53,12 +53,9 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({ `useFindDuplicateRecords for "${objectMetadataItem.nameSingular}" error : ` + error, ); - enqueueSnackBar( - `Error during useFindDuplicateRecords for "${objectMetadataItem.nameSingular}", ${error.message}`, - { - variant: SnackBarVariant.Error, - }, - ); + enqueueSnackBar(`Error finding duplicates:", ${error.message}`, { + variant: SnackBarVariant.Error, + }); }, }, ); 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 = <T extends ObjectRecord = ObjectRecord>({ onCompleted, cursorFilter, }: UseFindManyRecordsParams<T>) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); @@ -66,7 +63,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({ const { data, loading, error, fetchMore } = useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, { - skip: skip || !objectMetadataItem || !currentWorkspaceMember, + skip: skip || !objectMetadataItem, variables: { filter, orderBy, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsError.ts b/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsError.ts index 1b5a8cae64ef..1517c92e7c45 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsError.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsError.ts @@ -19,12 +19,9 @@ export const useHandleFindManyRecordsError = ({ `useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` + error, ); - enqueueSnackBar( - `Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`, - { - variant: SnackBarVariant.Error, - }, - ); + enqueueSnackBar(`${error.message}`, { + variant: SnackBarVariant.Error, + }); handleError?.(error); }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts index 55bd5cc5e865..a223f260f73e 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts @@ -1,9 +1,15 @@ import { useApolloClient } from '@apollo/client'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { apiConfigState } from '@/client-config/states/apiConfigState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize'; import { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { isDefined } from '~/utils/isDefined'; @@ -34,10 +40,16 @@ export const useRestoreManyRecords = ({ objectNameSingular, }); + const getRecordFromCache = useGetRecordFromCache({ + objectNameSingular, + }); + const { restoreManyRecordsMutation } = useRestoreManyRecordsMutation({ objectNameSingular, }); + const { objectMetadataItems } = useObjectMetadataItems(); + const mutationResponseField = getRestoreManyRecordsMutationResponseField( objectMetadataItem.namePlural, ); @@ -51,33 +63,126 @@ export const useRestoreManyRecords = ({ const restoredRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchIds = idsToRestore.slice( + const batchedIdsToRestore = idsToRestore.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); - // TODO: fix optimistic effect - const findOneQueryName = `FindOne${capitalize(objectNameSingular)}`; - const findManyQueryName = `FindMany${capitalize( - objectMetadataItem.namePlural, - )}`; - - const restoredRecordsResponse = await apolloClient.mutate({ - mutation: restoreManyRecordsMutation, - refetchQueries: [findOneQueryName, findManyQueryName], - variables: { - filter: { id: { in: batchIds } }, - }, - optimisticResponse: options?.skipOptimisticEffect - ? undefined - : { - [mutationResponseField]: batchIds.map((idToRestore) => ({ - __typename: capitalize(objectNameSingular), - id: idToRestore, - deletedAt: null, - })), - }, - }); + const cachedRecords = batchedIdsToRestore + .map((idToRestore) => + getRecordFromCache(idToRestore, apolloClient.cache), + ) + .filter(isDefined); + + if (!options?.skipOptimisticEffect) { + cachedRecords.forEach((cachedRecord) => { + if (!cachedRecord || !cachedRecord.id) { + return; + } + + const cachedRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: null }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + return null; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: computedOptimisticRecord, + }); + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: cachedRecordWithConnection, + updatedRecord: optimisticRecordWithConnection, + objectMetadataItems, + }); + }); + } + + const restoredRecordsResponse = await apolloClient + .mutate({ + mutation: restoreManyRecordsMutation, + variables: { + filter: { id: { in: batchedIdsToRestore } }, + }, + }) + .catch((error: Error) => { + cachedRecords.forEach((cachedRecord) => { + if (!cachedRecord) { + return; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: cachedRecord, + }); + + const cachedRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: null }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord<ObjectRecord>({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if ( + !optimisticRecordWithConnection || + !cachedRecordWithConnection + ) { + return null; + } + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, + objectMetadataItems, + }); + }); + + throw error; + }); const restoredRecordsForThisBatch = restoredRecordsResponse.data?.[mutationResponseField] ?? []; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts new file mode 100644 index 000000000000..175f84554f19 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts @@ -0,0 +1,92 @@ +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; +import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; +import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; +import { RecordGqlOperationSearchResult } from '@/object-record/graphql/types/RecordGqlOperationSearchResult'; +import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables'; +import { useSearchRecordsQuery } from '@/object-record/hooks/useSearchRecordsQuery'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField'; +import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useQuery, WatchQueryFetchPolicy } from '@apollo/client'; +import { useMemo } from 'react'; +import { useRecoilValue } from 'recoil'; +import { logError } from '~/utils/logError'; + +export type UseSearchRecordsParams = ObjectMetadataItemIdentifier & + RecordGqlOperationVariables & { + onError?: (error?: Error) => void; + skip?: boolean; + recordGqlFields?: RecordGqlOperationGqlRecordFields; + fetchPolicy?: WatchQueryFetchPolicy; + searchInput?: string; + }; + +export const useSearchRecords = <T extends ObjectRecord = ObjectRecord>({ + objectNameSingular, + searchInput, + limit, + skip, + recordGqlFields, + fetchPolicy, +}: UseSearchRecordsParams) => { + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular, + }); + const { searchRecordsQuery } = useSearchRecordsQuery({ + objectNameSingular, + recordGqlFields, + }); + + const { enqueueSnackBar } = useSnackBar(); + const { data, loading, error, previousData } = + useQuery<RecordGqlOperationSearchResult>(searchRecordsQuery, { + skip: + skip || !objectMetadataItem || !currentWorkspaceMember || !searchInput, + variables: { + search: searchInput, + limit: limit, + }, + fetchPolicy: fetchPolicy, + onError: (error) => { + logError( + `useSearchRecords for "${objectMetadataItem.namePlural}" error : ` + + error, + ); + enqueueSnackBar( + `Error during useSearchRecords for "${objectMetadataItem.namePlural}", ${error.message}`, + { + variant: SnackBarVariant.Error, + }, + ); + }, + }); + + const effectiveData = loading ? previousData : data; + + const queryResponseField = getSearchRecordsQueryResponseField( + objectMetadataItem.namePlural, + ); + + const result = effectiveData?.[queryResponseField]; + + const records = useMemo( + () => + result + ? (getRecordsFromRecordConnection({ + recordConnection: result, + }) as T[]) + : [], + [result], + ); + + return { + objectMetadataItem, + records: records, + loading, + error, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useSearchRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useSearchRecordsQuery.ts new file mode 100644 index 000000000000..6cc3972caa9c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/useSearchRecordsQuery.ts @@ -0,0 +1,33 @@ +import { useRecoilValue } from 'recoil'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; +import { generateSearchRecordsQuery } from '@/object-record/utils/generateSearchRecordsQuery'; + +export const useSearchRecordsQuery = ({ + objectNameSingular, + recordGqlFields, + computeReferences, +}: { + objectNameSingular: string; + recordGqlFields?: RecordGqlOperationGqlRecordFields; + computeReferences?: boolean; +}) => { + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular, + }); + + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const searchRecordsQuery = generateSearchRecordsQuery({ + objectMetadataItem, + objectMetadataItems, + recordGqlFields, + computeReferences, + }); + + return { + searchRecordsQuery, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts index 427c48547172..8f77eaaee238 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -11,6 +11,7 @@ import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRe import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { capitalize } from '~/utils/string/capitalize'; type useUpdateOneRecordProps = { @@ -108,26 +109,48 @@ export const useUpdateOneRecord = < const mutationResponseField = getUpdateOneRecordMutationResponseField(objectNameSingular); - const updatedRecord = await apolloClient.mutate({ - mutation: updateOneRecordMutation, - variables: { - idToUpdate, - input: sanitizedInput, - }, - update: (cache, { data }) => { - const record = data?.[mutationResponseField]; - - if (!record || !cachedRecord) return; + const updatedRecord = await apolloClient + .mutate({ + mutation: updateOneRecordMutation, + variables: { + idToUpdate, + input: sanitizedInput, + }, + update: (cache, { data }) => { + const record = data?.[mutationResponseField]; + + if (!record || !cachedRecord) return; + + triggerUpdateRecordOptimisticEffect({ + cache, + objectMetadataItem, + currentRecord: cachedRecord, + updatedRecord: record, + objectMetadataItems, + }); + }, + }) + .catch((error: Error) => { + if (isUndefinedOrNull(cachedRecord?.id)) { + throw error; + } + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: cachedRecord, + }); triggerUpdateRecordOptimisticEffect({ - cache, + cache: apolloClient.cache, objectMetadataItem, - currentRecord: cachedRecord, - updatedRecord: record, + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, objectMetadataItems, }); - }, - }); + + throw error; + }); return updatedRecord?.data?.[mutationResponseField] ?? null; }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton.tsx index 885a6c8eb840..b88f421c9857 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton.tsx @@ -2,6 +2,7 @@ import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdow import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { useCallback } from 'react'; import { MultipleFiltersButton } from './MultipleFiltersButton'; import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent'; @@ -13,12 +14,18 @@ type MultipleFiltersDropdownButtonProps = { export const MultipleFiltersDropdownButton = ({ hotkeyScope, }: MultipleFiltersDropdownButtonProps) => { - const { resetFilter } = useFilterDropdown(); + const { resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded } = + useFilterDropdown(); + + const handleDropdownClose = useCallback(() => { + resetFilter(); + setIsObjectFilterDropdownOperandSelectUnfolded(false); + }, [resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded]); return ( <Dropdown dropdownId={OBJECT_FILTER_DROPDOWN_ID} - onClose={resetFilter} + onClose={handleDropdownClose} clickableComponent={<MultipleFiltersButton />} dropdownComponents={<MultipleFiltersDropdownContent />} dropdownHotkeyScope={hotkeyScope} diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx index ec3df12f0d1d..1e019b6c77e5 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx @@ -1,20 +1,18 @@ -import { useRecoilValue } from 'recoil'; - -import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; -import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; +import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; +import { ObjectFilterDropdownFilterSelectCompositeFieldSubMenu } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu'; +import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; +import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect'; -import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput'; import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect'; -import { ObjectFilterDropdownNumberInput } from './ObjectFilterDropdownNumberInput'; -import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton'; -import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect'; -import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSelect'; -import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect'; -import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput'; + +const StyledContainer = styled.div` + position: relative; +`; type MultipleFiltersDropdownContentProps = { filterDropdownId?: string; @@ -23,86 +21,44 @@ type MultipleFiltersDropdownContentProps = { export const MultipleFiltersDropdownContent = ({ filterDropdownId, }: MultipleFiltersDropdownContentProps) => { - const { - isObjectFilterDropdownOperandSelectUnfoldedState, - filterDefinitionUsedInDropdownState, - selectedOperandInDropdownState, - } = useFilterDropdown({ filterDropdownId }); + const { filterDefinitionUsedInDropdownState } = useFilterDropdown({ + filterDropdownId, + }); + + const [objectFilterDropdownIsSelectingCompositeField] = + useRecoilComponentStateV2( + objectFilterDropdownIsSelectingCompositeFieldComponentState, + filterDropdownId, + ); - const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( - isObjectFilterDropdownOperandSelectUnfoldedState, + const [objectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( + objectFilterDropdownFilterIsSelectedComponentState, + filterDropdownId, ); + const filterDefinitionUsedInDropdown = useRecoilValue( filterDefinitionUsedInDropdownState, ); - const selectedOperandInDropdown = useRecoilValue( - selectedOperandInDropdownState, - ); - const isEmptyOperand = - selectedOperandInDropdown && - [ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes( - selectedOperandInDropdown, - ); + + const shouldShowCompositeSelectionSubMenu = + objectFilterDropdownIsSelectingCompositeField; + + const shoudShowFilterInput = objectFilterDropdownFilterIsSelected; return ( - <> - {!filterDefinitionUsedInDropdown ? ( - <ObjectFilterDropdownFilterSelect /> - ) : isObjectFilterDropdownOperandSelectUnfolded ? ( - <ObjectFilterDropdownOperandSelect /> - ) : isEmptyOperand ? ( - <ObjectFilterDropdownOperandButton /> + <StyledContainer> + {shoudShowFilterInput ? ( + <ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} /> + ) : shouldShowCompositeSelectionSubMenu ? ( + <ObjectFilterDropdownFilterSelectCompositeFieldSubMenu /> ) : ( - selectedOperandInDropdown && ( - <> - <ObjectFilterDropdownOperandButton /> - <DropdownMenuSeparator /> - {[ - 'TEXT', - 'EMAIL', - 'EMAILS', - 'PHONE', - 'FULL_NAME', - 'LINK', - 'LINKS', - 'ADDRESS', - 'ACTOR', - 'ARRAY', - 'PHONES', - ].includes(filterDefinitionUsedInDropdown.type) && ( - <ObjectFilterDropdownTextSearchInput /> - )} - {['NUMBER', 'CURRENCY'].includes( - filterDefinitionUsedInDropdown.type, - ) && <ObjectFilterDropdownNumberInput />} - {filterDefinitionUsedInDropdown.type === 'RATING' && ( - <ObjectFilterDropdownRatingInput /> - )} - {['DATE_TIME', 'DATE'].includes( - filterDefinitionUsedInDropdown.type, - ) && <ObjectFilterDropdownDateInput />} - {filterDefinitionUsedInDropdown.type === 'RELATION' && ( - <> - <ObjectFilterDropdownSearchInput /> - <DropdownMenuSeparator /> - <ObjectFilterDropdownRecordSelect /> - </> - )} - {filterDefinitionUsedInDropdown.type === 'SELECT' && ( - <> - <ObjectFilterDropdownSearchInput /> - <DropdownMenuSeparator /> - <ObjectFilterDropdownOptionSelect /> - </> - )} - </> - ) + <ObjectFilterDropdownFilterSelect /> )} <MultipleFiltersDropdownFilterOnFilterChangedEffect filterDefinitionUsedInDropdownType={ filterDefinitionUsedInDropdown?.type } /> - </> + </StyledContainer> ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx index 9abc6b7842a1..895adf219502 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton.tsx @@ -1,6 +1,7 @@ import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton'; @@ -29,12 +30,16 @@ export const ObjectFilterDropdownButton = ({ } return ( - <ObjectFilterDropdownScope filterScopeId={filterDropdownId}> - {hasOnlyOneEntityFilter ? ( - <SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} /> - ) : ( - <MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} /> - )} - </ObjectFilterDropdownScope> + <ObjectFilterDropdownComponentInstanceContext.Provider + value={{ instanceId: filterDropdownId }} + > + <ObjectFilterDropdownScope filterScopeId={filterDropdownId}> + {hasOnlyOneEntityFilter ? ( + <SingleEntityObjectFilterDropdownButton hotkeyScope={hotkeyScope} /> + ) : ( + <MultipleFiltersDropdownButton hotkeyScope={hotkeyScope} /> + )} + </ObjectFilterDropdownScope> + </ObjectFilterDropdownComponentInstanceContext.Provider> ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 20d1dee8389f..3961f28c836b 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -2,10 +2,19 @@ import { useRecoilValue } from 'recoil'; import { v4 } from 'uuid'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue'; import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { computeVariableDateViewFilterValue } from '@/views/utils/view-filter-value/computeVariableDateViewFilterValue'; +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; +import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; import { useState } from 'react'; +import { isDefined } from 'twenty-ui'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { isDefined } from '~/utils/isDefined'; export const ObjectFilterDropdownDateInput = () => { const { @@ -23,28 +32,35 @@ export const ObjectFilterDropdownDateInput = () => { selectedOperandInDropdownState, ); - const selectedFilter = useRecoilValue(selectedFilterState); + const selectedFilter = useRecoilValue(selectedFilterState) as + | (Filter & { definition: { type: 'DATE' | 'DATE_TIME' } }) + | null + | undefined; + + const initialFilterValue = selectedFilter + ? resolveFilterValue(selectedFilter) + : null; const [internalDate, setInternalDate] = useState<Date | null>( - selectedFilter?.value ? new Date(selectedFilter.value) : new Date(), + initialFilterValue instanceof Date ? initialFilterValue : null, ); const isDateTimeInput = filterDefinitionUsedInDropdown?.type === FieldMetadataType.DateTime; - const handleChange = (date: Date | null) => { - setInternalDate(date); + const handleAbsoluteDateChange = (newDate: Date | null) => { + setInternalDate(newDate); if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; selectFilter?.({ id: selectedFilter?.id ? selectedFilter.id : v4(), fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, - value: isDefined(date) ? date.toISOString() : '', + value: newDate?.toISOString() ?? '', operand: selectedOperandInDropdown, - displayValue: isDefined(date) + displayValue: isDefined(newDate) ? isDateTimeInput - ? date.toLocaleString() - : date.toLocaleDateString() + ? newDate.toLocaleString() + : newDate.toLocaleDateString() : '', definition: filterDefinitionUsedInDropdown, }); @@ -52,11 +68,56 @@ export const ObjectFilterDropdownDateInput = () => { setIsObjectFilterDropdownUnfolded(false); }; + const handleRelativeDateChange = ( + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + } | null, + ) => { + if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return; + + const value = relativeDate + ? computeVariableDateViewFilterValue( + relativeDate.direction, + relativeDate.amount, + relativeDate.unit, + ) + : ''; + + selectFilter?.({ + id: selectedFilter?.id ? selectedFilter.id : v4(), + fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + value, + operand: selectedOperandInDropdown, + displayValue: getRelativeDateDisplayValue(relativeDate), + definition: filterDefinitionUsedInDropdown, + }); + + setIsObjectFilterDropdownUnfolded(false); + }; + + const isRelativeOperand = + selectedOperandInDropdown === ViewFilterOperand.IsRelative; + + const resolvedValue = selectedFilter + ? resolveFilterValue(selectedFilter) + : null; + + const relativeDate = + resolvedValue && !(resolvedValue instanceof Date) + ? resolvedValue + : undefined; + return ( <InternalDatePicker + relativeDate={relativeDate} + highlightedDateRange={relativeDate} + isRelative={isRelativeOperand} date={internalDate} - onChange={handleChange} - onMouseSelect={handleChange} + onChange={handleAbsoluteDateChange} + onRelativeDateChange={handleRelativeDateChange} + onMouseSelect={handleAbsoluteDateChange} isDateTimeInput={isDateTimeInput} /> ); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx new file mode 100644 index 000000000000..a630286ffadb --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx @@ -0,0 +1,132 @@ +import { useRecoilValue } from 'recoil'; + +import { ObjectFilterDropdownDateInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput'; +import { ObjectFilterDropdownNumberInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownNumberInput'; +import { ObjectFilterDropdownOperandButton } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton'; +import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect'; +import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect'; +import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; +import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect'; +import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput'; +import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect'; +import { ObjectFilterDropdownTextSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextSearchInput'; +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import styled from '@emotion/styled'; +import { isDefined } from 'twenty-ui'; + +const StyledOperandSelectContainer = styled.div` + background: ${({ theme }) => theme.background.secondary}; + box-shadow: ${({ theme }) => theme.boxShadow.light}; + border-radius: ${({ theme }) => theme.border.radius.md}; + left: 10px; + position: absolute; + top: 10px; + width: 100%; + z-index: 1000; +`; + +type ObjectFilterDropdownFilterInputProps = { + filterDropdownId?: string; +}; + +export const ObjectFilterDropdownFilterInput = ({ + filterDropdownId, +}: ObjectFilterDropdownFilterInputProps) => { + const { + filterDefinitionUsedInDropdownState, + selectedOperandInDropdownState, + isObjectFilterDropdownOperandSelectUnfoldedState, + } = useFilterDropdown({ filterDropdownId }); + + const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( + isObjectFilterDropdownOperandSelectUnfoldedState, + ); + + const filterDefinitionUsedInDropdown = useRecoilValue( + filterDefinitionUsedInDropdownState, + ); + + const selectedOperandInDropdown = useRecoilValue( + selectedOperandInDropdownState, + ); + + const isConfigurable = + selectedOperandInDropdown && + [ + ViewFilterOperand.Is, + ViewFilterOperand.IsNotNull, + ViewFilterOperand.IsNot, + ViewFilterOperand.LessThan, + ViewFilterOperand.GreaterThan, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ViewFilterOperand.IsRelative, + ].includes(selectedOperandInDropdown); + + if (!isDefined(filterDefinitionUsedInDropdown)) { + return null; + } + + return ( + <> + <ObjectFilterDropdownOperandButton /> + {isObjectFilterDropdownOperandSelectUnfolded && ( + <StyledOperandSelectContainer> + <ObjectFilterDropdownOperandSelect /> + </StyledOperandSelectContainer> + )} + {isConfigurable && selectedOperandInDropdown && ( + <> + {[ + 'TEXT', + 'EMAIL', + 'EMAILS', + 'PHONE', + 'FULL_NAME', + 'LINK', + 'LINKS', + 'ADDRESS', + 'ACTOR', + 'ARRAY', + 'PHONES', + ].includes(filterDefinitionUsedInDropdown.type) && + !isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && ( + <ObjectFilterDropdownTextSearchInput /> + )} + {['NUMBER', 'CURRENCY'].includes( + filterDefinitionUsedInDropdown.type, + ) && <ObjectFilterDropdownNumberInput />} + {filterDefinitionUsedInDropdown.type === 'RATING' && ( + <ObjectFilterDropdownRatingInput /> + )} + {['DATE_TIME', 'DATE'].includes( + filterDefinitionUsedInDropdown.type, + ) && <ObjectFilterDropdownDateInput />} + {filterDefinitionUsedInDropdown.type === 'RELATION' && ( + <> + <ObjectFilterDropdownSearchInput /> + <ObjectFilterDropdownRecordSelect /> + </> + )} + {isActorSourceCompositeFilter(filterDefinitionUsedInDropdown) && ( + <> + <DropdownMenuSeparator /> + <ObjectFilterDropdownSourceSelect /> + </> + )} + {filterDefinitionUsedInDropdown.type === 'SELECT' && ( + <> + <ObjectFilterDropdownSearchInput /> + <ObjectFilterDropdownOptionSelect /> + </> + )} + </> + )} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx index 59ee04d92518..9c815948dc28 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx @@ -1,17 +1,22 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; +import { useContext } from 'react'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem'; import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; +import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; +import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; export const StyledInput = styled.input` @@ -41,19 +46,53 @@ export const StyledInput = styled.input` `; export const ObjectFilterDropdownFilterSelect = () => { - const [searchText, setSearchText] = useState(''); + const { + setObjectFilterDropdownSearchInput, + objectFilterDropdownSearchInputState, + } = useFilterDropdown(); + + const objectFilterDropdownSearchInput = useRecoilValue( + objectFilterDropdownSearchInputState, + ); const availableFilterDefinitions = useRecoilComponentValueV2( availableFilterDefinitionsComponentState, ); + const { recordIndexId } = useContext(RecordIndexRootPropsContext); + const { hiddenTableColumnsSelector, visibleTableColumnsSelector } = + useRecordTableStates(recordIndexId); - const sortedAvailableFilterDefinitions = [...availableFilterDefinitions] - .sort((a, b) => a.label.localeCompare(b.label)) - .filter((item) => - item.label.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()), + const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector()); + const visibleColumnsIds = visibleTableColumns.map( + (column) => column.fieldMetadataId, + ); + const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector()); + const hiddenColumnIds = hiddenTableColumns.map( + (column) => column.fieldMetadataId, + ); + + const filteredSearchInputFilterDefinitions = + availableFilterDefinitions.filter((item) => + item.label + .toLocaleLowerCase() + .includes(objectFilterDropdownSearchInput.toLocaleLowerCase()), ); - const selectableListItemIds = sortedAvailableFilterDefinitions.map( + const visibleColumnsFilterDefinitions = filteredSearchInputFilterDefinitions + + .sort((a, b) => { + return ( + visibleColumnsIds.indexOf(a.fieldMetadataId) - + visibleColumnsIds.indexOf(b.fieldMetadataId) + ); + }) + .filter((item) => visibleColumnsIds.includes(item.fieldMetadataId)); + + const hiddenColumnsFilterDefinitions = filteredSearchInputFilterDefinitions + .sort((a, b) => a.label.localeCompare(b.label)) + .filter((item) => hiddenColumnIds.includes(item.fieldMetadataId)); + + const selectableListItemIds = availableFilterDefinitions.map( (item) => item.fieldMetadataId, ); @@ -62,7 +101,7 @@ export const ObjectFilterDropdownFilterSelect = () => { const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID); const handleEnter = (itemId: string) => { - const selectedFilterDefinition = sortedAvailableFilterDefinitions.find( + const selectedFilterDefinition = availableFilterDefinitions.find( (item) => item.fieldMetadataId === itemId, ); @@ -75,14 +114,18 @@ export const ObjectFilterDropdownFilterSelect = () => { selectFilter({ filterDefinition: selectedFilterDefinition }); }; + const shoudShowSeparator = + visibleColumnsFilterDefinitions.length > 0 && + hiddenColumnsFilterDefinitions.length > 0; + return ( <> <StyledInput - value={searchText} + value={objectFilterDropdownSearchInput} autoFocus placeholder="Search fields" onChange={(event: React.ChangeEvent<HTMLInputElement>) => - setSearchText(event.target.value) + setObjectFilterDropdownSearchInput(event.target.value) } /> <SelectableList @@ -92,14 +135,29 @@ export const ObjectFilterDropdownFilterSelect = () => { onEnter={handleEnter} > <DropdownMenuItemsContainer> - {sortedAvailableFilterDefinitions.map( - (availableFilterDefinition, index) => ( + {visibleColumnsFilterDefinitions.map( + (visibleFilterDefinition, index) => ( + <SelectableItem + itemId={visibleFilterDefinition.fieldMetadataId} + key={`visible-select-filter-${index}`} + > + <ObjectFilterDropdownFilterSelectMenuItem + filterDefinition={visibleFilterDefinition} + /> + </SelectableItem> + ), + )} + </DropdownMenuItemsContainer> + {shoudShowSeparator && <DropdownMenuSeparator />} + <DropdownMenuItemsContainer> + {hiddenColumnsFilterDefinitions.map( + (hiddenFilterDefinition, index) => ( <SelectableItem - itemId={availableFilterDefinition.fieldMetadataId} + itemId={hiddenFilterDefinition.fieldMetadataId} + key={`hidden-select-filter-${index}`} > <ObjectFilterDropdownFilterSelectMenuItem - key={`select-filter-${index}`} - filterDefinition={availableFilterDefinition} + filterDefinition={hiddenFilterDefinition} /> </SelectableItem> ), diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx new file mode 100644 index 000000000000..6fd8b778dc14 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectCompositeFieldSubMenu.tsx @@ -0,0 +1,145 @@ +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; +import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState'; +import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; +import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; +import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel'; +import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; +import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; +import { useState } from 'react'; +import { IconApps, IconChevronLeft, isDefined, useIcons } from 'twenty-ui'; + +export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => { + const [searchText] = useState(''); + + const { getIcon } = useIcons(); + + const [ + objectFilterDropdownFirstLevelFilterDefinition, + setObjectFilterDropdownFirstLevelFilterDefinition, + ] = useRecoilComponentStateV2( + objectFilterDropdownFirstLevelFilterDefinitionComponentState, + ); + + const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( + objectFilterDropdownFilterIsSelectedComponentState, + ); + + const [, setObjectFilterDropdownIsSelectingCompositeField] = + useRecoilComponentStateV2( + objectFilterDropdownIsSelectingCompositeFieldComponentState, + ); + + const [ + objectFilterDropdownSubMenuFieldType, + setObjectFilterDropdownSubMenuFieldType, + ] = useRecoilComponentStateV2( + objectFilterDropdownSubMenuFieldTypeComponentState, + ); + + const { + setFilterDefinitionUsedInDropdown, + setSelectedOperandInDropdown, + setObjectFilterDropdownSearchInput, + } = useFilterDropdown(); + + const handleSelectFilter = (definition: FilterDefinition | null) => { + if (definition !== null) { + setFilterDefinitionUsedInDropdown(definition); + + setSelectedOperandInDropdown( + getOperandsForFilterDefinition(definition)[0], + ); + + setObjectFilterDropdownSearchInput(''); + + setObjectFilterDropdownFilterIsSelected(true); + } + }; + + const handleSubMenuBack = () => { + setFilterDefinitionUsedInDropdown(null); + setObjectFilterDropdownSubMenuFieldType(null); + setObjectFilterDropdownFirstLevelFilterDefinition(null); + setObjectFilterDropdownIsSelectingCompositeField(false); + setObjectFilterDropdownFilterIsSelected(false); + }; + + if ( + !isDefined(objectFilterDropdownSubMenuFieldType) || + !isDefined(objectFilterDropdownFirstLevelFilterDefinition) + ) { + return null; + } + + const options = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[ + objectFilterDropdownSubMenuFieldType + ].filterableSubFields + .sort((a, b) => a.localeCompare(b)) + .filter((item) => + item.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()), + ); + + return ( + <> + <DropdownMenuHeader + StartIcon={IconChevronLeft} + onClick={handleSubMenuBack} + > + {getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} + </DropdownMenuHeader> + {/* <StyledInput + value={searchText} + autoFocus + placeholder="Search fields" + onChange={(event: React.ChangeEvent<HTMLInputElement>) => + setSearchText(event.target.value) + } + /> */} + <DropdownMenuItemsContainer> + <MenuItem + key={`select-filter-${-1}`} + testId={`select-filter-${-1}`} + onClick={() => { + handleSelectFilter(objectFilterDropdownFirstLevelFilterDefinition); + }} + LeftIcon={IconApps} + text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`} + /> + {/* TODO: fix this with a backend field on ViewFilter for composite field filter */} + {objectFilterDropdownFirstLevelFilterDefinition.type === 'ACTOR' && + options.map((subFieldName, index) => ( + <MenuItem + key={`select-filter-${index}`} + testId={`select-filter-${index}`} + onClick={() => { + if (isDefined(objectFilterDropdownFirstLevelFilterDefinition)) { + handleSelectFilter({ + ...objectFilterDropdownFirstLevelFilterDefinition, + label: getCompositeSubFieldLabel( + objectFilterDropdownSubMenuFieldType, + subFieldName, + ), + compositeFieldName: subFieldName, + }); + } + }} + text={getCompositeSubFieldLabel( + objectFilterDropdownSubMenuFieldType, + subFieldName, + )} + LeftIcon={getIcon( + objectFilterDropdownFirstLevelFilterDefinition?.iconName, + )} + /> + ))} + </DropdownMenuItemsContainer> + </> + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx index eb04750c9b8c..7d94eaaf0e35 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx @@ -1,8 +1,20 @@ import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; +import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; +import { objectFilterDropdownFirstLevelFilterDefinitionComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState'; +import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; +import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; +import { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType'; + import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; +import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; +import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui'; @@ -15,6 +27,24 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ }: ObjectFilterDropdownFilterSelectMenuItemProps) => { const { selectFilter } = useSelectFilter(); + const [, setObjectFilterDropdownFirstLevelFilterDefinition] = + useRecoilComponentStateV2( + objectFilterDropdownFirstLevelFilterDefinitionComponentState, + ); + + const [, setObjectFilterDropdownSubMenuFieldType] = useRecoilComponentStateV2( + objectFilterDropdownSubMenuFieldTypeComponentState, + ); + + const [, setObjectFilterDropdownIsSelectingCompositeField] = + useRecoilComponentStateV2( + objectFilterDropdownIsSelectingCompositeFieldComponentState, + ); + + const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( + objectFilterDropdownFilterIsSelectedComponentState, + ); + const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( OBJECT_FILTER_DROPDOWN_ID, ); @@ -23,12 +53,52 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ isSelectedItemIdSelector(filterDefinition.fieldMetadataId), ); + const isACompositeField = isCompositeField(filterDefinition.type); + + const { + setFilterDefinitionUsedInDropdown, + setSelectedOperandInDropdown, + setObjectFilterDropdownSearchInput, + } = useFilterDropdown(); + + const setHotkeyScope = useSetHotkeyScope(); + + const handleSelectFilter = (availableFilterDefinition: FilterDefinition) => { + setFilterDefinitionUsedInDropdown(availableFilterDefinition); + + if ( + availableFilterDefinition.type === 'RELATION' || + availableFilterDefinition.type === 'SELECT' + ) { + setHotkeyScope(RelationPickerHotkeyScope.RelationPicker); + } + + setSelectedOperandInDropdown( + getOperandsForFilterDefinition(availableFilterDefinition)[0], + ); + + setObjectFilterDropdownSearchInput(''); + + setObjectFilterDropdownFilterIsSelected(true); + }; + const { getIcon } = useIcons(); const handleClick = () => { resetSelectedItem(); selectFilter({ filterDefinition }); + + if (isACompositeField) { + // TODO: create isCompositeFilterableFieldType type guard + setObjectFilterDropdownSubMenuFieldType( + filterDefinition.type as CompositeFilterableFieldType, + ); + setObjectFilterDropdownFirstLevelFilterDefinition(filterDefinition); + setObjectFilterDropdownIsSelectingCompositeField(true); + } else { + handleSelectFilter(filterDefinition); + } }; return ( @@ -38,6 +108,7 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({ onClick={handleClick} LeftIcon={getIcon(filterDefinition.iconName)} text={filterDefinition.label} + hasSubMenu={isACompositeField} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx index 4aa675ec3539..3931c76547e1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton.tsx @@ -10,19 +10,11 @@ export const ObjectFilterDropdownOperandButton = () => { const { selectedOperandInDropdownState, setIsObjectFilterDropdownOperandSelectUnfolded, - isObjectFilterDropdownOperandSelectUnfoldedState, } = useFilterDropdown(); const selectedOperandInDropdown = useRecoilValue( selectedOperandInDropdownState, ); - const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue( - isObjectFilterDropdownOperandSelectUnfoldedState, - ); - - if (isObjectFilterDropdownOperandSelectUnfolded) { - return null; - } return ( <DropdownMenuHeader diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx index 5f500b916461..b33fcbc162e3 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx @@ -2,14 +2,14 @@ import { useRecoilValue } from 'recoil'; import { v4 } from 'uuid'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; -import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { isDefined } from '~/utils/isDefined'; +import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue'; import { getOperandLabel } from '../utils/getOperandLabel'; -import { getOperandsForFilterType } from '../utils/getOperandsForFilterType'; +import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType'; export const ObjectFilterDropdownOperandSelect = () => { const { @@ -31,27 +31,30 @@ export const ObjectFilterDropdownOperandSelect = () => { const selectedFilter = useRecoilValue(selectedFilterState); - const operandsForFilterType = getOperandsForFilterType( - filterDefinitionUsedInDropdown?.type, - ); + const operandsForFilterType = isDefined(filterDefinitionUsedInDropdown) + ? getOperandsForFilterDefinition(filterDefinitionUsedInDropdown) + : []; const handleOperandChange = (newOperand: ViewFilterOperand) => { - const isEmptyOperand = [ + const isValuelessOperand = [ ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, ].includes(newOperand); setSelectedOperandInDropdown(newOperand); setIsObjectFilterDropdownOperandSelectUnfolded(false); - if (isEmptyOperand) { + if (isValuelessOperand && isDefined(filterDefinitionUsedInDropdown)) { selectFilter?.({ id: v4(), fieldMetadataId: filterDefinitionUsedInDropdown?.fieldMetadataId ?? '', displayValue: '', operand: newOperand, value: '', - definition: filterDefinitionUsedInDropdown as FilterDefinition, + definition: filterDefinitionUsedInDropdown, }); return; } @@ -60,12 +63,19 @@ export const ObjectFilterDropdownOperandSelect = () => { isDefined(filterDefinitionUsedInDropdown) && isDefined(selectedFilter) ) { + const { value, displayValue } = getInitialFilterValue( + filterDefinitionUsedInDropdown.type, + newOperand, + selectedFilter.value, + selectedFilter.displayValue, + ); + selectFilter?.({ id: selectedFilter.id ? selectedFilter.id : v4(), fieldMetadataId: selectedFilter.fieldMetadataId, - displayValue: selectedFilter.displayValue, + displayValue, operand: newOperand, - value: selectedFilter.value, + value, definition: filterDefinitionUsedInDropdown, }); } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx index ddaaf2e6ad92..c55496ee61d3 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx @@ -4,9 +4,9 @@ import { v4 } from 'uuid'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; -import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown'; +import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown'; import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; -import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; +import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { isDefined } from '~/utils/isDefined'; @@ -66,7 +66,7 @@ export const ObjectFilterDropdownRecordSelect = ({ }); const handleMultipleRecordSelectChange = ( - recordToSelect: SelectableRecord, + recordToSelect: SelectableItem, newSelectedValue: boolean, ) => { if (loading) { @@ -134,15 +134,15 @@ export const ObjectFilterDropdownRecordSelect = ({ }; return ( - <MultipleRecordSelectDropdown + <MultipleSelectDropdown selectableListId="object-filter-record-select-id" hotkeyScope={RelationPickerHotkeyScope.RelationPicker} - recordsToSelect={recordsToSelect} - filteredSelectedRecords={filteredSelectedRecords} - selectedRecords={selectedRecords} + itemsToSelect={recordsToSelect} + filteredSelectedItems={filteredSelectedRecords} + selectedItems={selectedRecords} onChange={handleMultipleRecordSelectChange} searchFilter={objectFilterDropdownSearchInput} - loadingRecords={loading} + loadingItems={loading} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx new file mode 100644 index 000000000000..153abd72b506 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { v4 } from 'uuid'; + +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; +import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown'; +import { SelectableItem } from '@/object-record/select/types/SelectableItem'; +import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; +import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { isDefined } from '~/utils/isDefined'; + +export const EMPTY_FILTER_VALUE = '[]'; +export const MAX_ITEMS_TO_DISPLAY = 3; + +type ObjectFilterDropdownSourceSelectProps = { + viewComponentId?: string; +}; + +export const ObjectFilterDropdownSourceSelect = ({ + viewComponentId, +}: ObjectFilterDropdownSourceSelectProps) => { + const { + filterDefinitionUsedInDropdownState, + objectFilterDropdownSearchInputState, + selectedOperandInDropdownState, + selectedFilterState, + setObjectFilterDropdownSelectedRecordIds, + objectFilterDropdownSelectedRecordIdsState, + selectFilter, + emptyFilterButKeepDefinition, + } = useFilterDropdown(); + + const { deleteCombinedViewFilter } = + useDeleteCombinedViewFilters(viewComponentId); + + const { currentViewWithCombinedFiltersAndSorts } = + useGetCurrentView(viewComponentId); + + const filterDefinitionUsedInDropdown = useRecoilValue( + filterDefinitionUsedInDropdownState, + ); + const objectFilterDropdownSearchInput = useRecoilValue( + objectFilterDropdownSearchInputState, + ); + const selectedOperandInDropdown = useRecoilValue( + selectedOperandInDropdownState, + ); + const objectFilterDropdownSelectedRecordIds = useRecoilValue( + objectFilterDropdownSelectedRecordIdsState, + ); + const [fieldId] = useState(v4()); + + const selectedFilter = useRecoilValue(selectedFilterState); + + const sourceTypes = getActorSourceMultiSelectOptions( + objectFilterDropdownSelectedRecordIds, + ); + + const filteredSelectedItems = sourceTypes.filter((option) => + objectFilterDropdownSelectedRecordIds.includes(option.id), + ); + + const handleMultipleItemSelectChange = ( + itemToSelect: SelectableItem, + newSelectedValue: boolean, + ) => { + const newSelectedItemIds = newSelectedValue + ? [...objectFilterDropdownSelectedRecordIds, itemToSelect.id] + : objectFilterDropdownSelectedRecordIds.filter( + (id) => id !== itemToSelect.id, + ); + + if (newSelectedItemIds.length === 0) { + emptyFilterButKeepDefinition(); + deleteCombinedViewFilter(fieldId); + return; + } + + setObjectFilterDropdownSelectedRecordIds(newSelectedItemIds); + + const selectedItemNames = sourceTypes + .filter((option) => newSelectedItemIds.includes(option.id)) + .map((option) => option.name); + + const filterDisplayValue = + selectedItemNames.length > MAX_ITEMS_TO_DISPLAY + ? `${selectedItemNames.length} source types` + : selectedItemNames.join(', '); + + if ( + isDefined(filterDefinitionUsedInDropdown) && + isDefined(selectedOperandInDropdown) + ) { + const newFilterValue = + newSelectedItemIds.length > 0 + ? JSON.stringify(newSelectedItemIds) + : EMPTY_FILTER_VALUE; + + const viewFilter = + currentViewWithCombinedFiltersAndSorts?.viewFilters.find( + (viewFilter) => + viewFilter.fieldMetadataId === + filterDefinitionUsedInDropdown.fieldMetadataId, + ); + + const filterId = viewFilter?.id ?? fieldId; + + selectFilter({ + id: selectedFilter?.id ? selectedFilter.id : filterId, + definition: filterDefinitionUsedInDropdown, + operand: selectedOperandInDropdown || ViewFilterOperand.Is, + displayValue: filterDisplayValue, + fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId, + value: newFilterValue, + }); + } + }; + + return ( + <MultipleSelectDropdown + selectableListId="object-filter-source-select-id" + hotkeyScope={RelationPickerHotkeyScope.RelationPicker} + itemsToSelect={sourceTypes.filter( + (item) => + !filteredSelectedItems.some((selected) => selected.id === item.id), + )} + filteredSelectedItems={filteredSelectedItems} + selectedItems={filteredSelectedItems} + onChange={handleMultipleItemSelectChange} + searchFilter={objectFilterDropdownSearchInput} + loadingItems={false} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx index ddfb5125b9dc..571645a5c81e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx @@ -13,7 +13,7 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; -import { getOperandsForFilterType } from '../utils/getOperandsForFilterType'; +import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType'; import { GenericEntityFilterChip } from './GenericEntityFilterChip'; import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect'; import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput'; @@ -36,14 +36,16 @@ export const SingleEntityObjectFilterDropdownButton = ({ ); const selectedFilter = useRecoilValue(selectedFilterState); - const availableFilter = availableFilterDefinitions[0]; + const availableFilterDefinition = availableFilterDefinitions[0]; React.useEffect(() => { - setFilterDefinitionUsedInDropdown(availableFilter); - const defaultOperand = getOperandsForFilterType(availableFilter?.type)[0]; + setFilterDefinitionUsedInDropdown(availableFilterDefinition); + const defaultOperand = getOperandsForFilterDefinition( + availableFilterDefinition, + )[0]; setSelectedOperandInDropdown(defaultOperand); }, [ - availableFilter, + availableFilterDefinition, setFilterDefinitionUsedInDropdown, setSelectedOperandInDropdown, ]); @@ -62,7 +64,7 @@ export const SingleEntityObjectFilterDropdownButton = ({ filter={selectedFilter} Icon={ selectedFilter.operand === ViewFilterOperand.IsNotNull - ? availableFilter.SelectAllIcon + ? availableFilterDefinition.SelectAllIcon : undefined } /> diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx index b97deda7b578..df9044a82f21 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/__stories__/MultipleFiltersDropdownButton.stories.tsx @@ -3,10 +3,15 @@ import { Meta, StoryObj } from '@storybook/react'; import { TaskGroups } from '@/activities/tasks/components/TaskGroups'; import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton'; import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope'; +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext'; +import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { within } from '@storybook/test'; +import { useSetRecoilState } from 'recoil'; import { ComponentDecorator } from 'twenty-ui'; import { FieldMetadataType } from '~/generated/graphql'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; @@ -25,18 +30,49 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = { instanceId, ); - setAvailableFilterDefinitions([ + const { tableColumnsState } = useRecordTableStates(instanceId); + + const setTableColumns = useSetRecoilState(tableColumnsState); + + setTableColumns([ { fieldMetadataId: '1', iconName: 'IconUser', label: 'Text', type: FieldMetadataType.Text, - }, + isVisible: true, + metadata: { + fieldName: 'text', + }, + } as ColumnDefinition<any>, + { + fieldMetadataId: '3', + iconName: 'IconNumber', + label: 'Number', + type: FieldMetadataType.Number, + isVisible: true, + metadata: { + fieldName: 'number', + }, + } as ColumnDefinition<any>, + { + fieldMetadataId: '4', + iconName: 'IconCalendar', + label: 'Date', + type: FieldMetadataType.DateTime, + isVisible: true, + metadata: { + fieldName: 'date', + }, + } as ColumnDefinition<any>, + ]); + + setAvailableFilterDefinitions([ { - fieldMetadataId: '2', - iconName: 'Icon123', - label: 'Email', - type: FieldMetadataType.Email, + fieldMetadataId: '1', + iconName: 'IconUser', + label: 'Text', + type: FieldMetadataType.Text, }, { fieldMetadataId: '3', @@ -52,11 +88,19 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = { }, ]); return ( - <ViewComponentInstanceContext.Provider value={{ instanceId }}> - <ObjectFilterDropdownScope filterScopeId={instanceId}> - <Story /> - </ObjectFilterDropdownScope> - </ViewComponentInstanceContext.Provider> + <ObjectFilterDropdownComponentInstanceContext.Provider + value={{ instanceId }} + > + <RecordTableScopeInternalContext.Provider + value={{ scopeId: instanceId, onColumnsChange: () => {} }} + > + <ViewComponentInstanceContext.Provider value={{ instanceId }}> + <ObjectFilterDropdownScope filterScopeId={instanceId}> + <Story /> + </ObjectFilterDropdownScope> + </ViewComponentInstanceContext.Provider> + </RecordTableScopeInternalContext.Provider> + </ObjectFilterDropdownComponentInstanceContext.Provider> ); }, ObjectMetadataItemsDecorator, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts index b09d33a38396..c8a801336647 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdown.ts @@ -4,6 +4,9 @@ import { useFilterDropdownStates } from '@/object-record/object-filter-dropdown/ import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; +import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { ObjectFilterDropdownScopeInternalContext } from '../scopes/scope-internal-context/ObjectFilterDropdownScopeInternalContext'; import { Filter } from '../types/Filter'; @@ -54,6 +57,18 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => { ], ); + const setObjectFilterDropdownFilterIsSelectedCallbackState = + useRecoilComponentCallbackStateV2( + objectFilterDropdownFilterIsSelectedComponentState, + props?.filterDropdownId, + ); + + const setObjectFilterDropdownIsSelectingCompositeFieldCallbackState = + useRecoilComponentCallbackStateV2( + objectFilterDropdownIsSelectingCompositeFieldComponentState, + props?.filterDropdownId, + ); + const resetFilter = useRecoilCallback( ({ set }) => () => { @@ -62,6 +77,11 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => { set(selectedFilterState, undefined); set(filterDefinitionUsedInDropdownState, null); set(selectedOperandInDropdownState, null); + set(setObjectFilterDropdownFilterIsSelectedCallbackState, false); + set( + setObjectFilterDropdownIsSelectingCompositeFieldCallbackState, + false, + ); }, [ filterDefinitionUsedInDropdownState, @@ -69,6 +89,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => { objectFilterDropdownSelectedRecordIdsState, selectedFilterState, selectedOperandInDropdownState, + setObjectFilterDropdownFilterIsSelectedCallbackState, + setObjectFilterDropdownIsSelectingCompositeFieldCallbackState, ], ); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts index 3954d8d6dc9f..c716971dc010 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts @@ -1,8 +1,10 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; -import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; +import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue'; +import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { v4 } from 'uuid'; type SelectFilterParams = { filterDefinition: FilterDefinition; @@ -13,6 +15,7 @@ export const useSelectFilter = () => { setFilterDefinitionUsedInDropdown, setSelectedOperandInDropdown, setObjectFilterDropdownSearchInput, + selectFilter: filterDropdownSelectFilter, } = useFilterDropdown(); const setHotkeyScope = useSetHotkeyScope(); @@ -28,9 +31,25 @@ export const useSelectFilter = () => { } setSelectedOperandInDropdown( - getOperandsForFilterType(filterDefinition.type)?.[0], + getOperandsForFilterDefinition(filterDefinition)[0], ); + const { value, displayValue } = getInitialFilterValue( + filterDefinition.type, + getOperandsForFilterDefinition(filterDefinition)[0], + ); + + if (value !== '') { + filterDropdownSelectFilter({ + id: v4(), + fieldMetadataId: filterDefinition.fieldMetadataId, + displayValue, + operand: getOperandsForFilterDefinition(filterDefinition)[0], + value, + definition: filterDefinition, + }); + } + setObjectFilterDropdownSearchInput(''); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope.tsx index d5c63c7d13e8..f7950d494804 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope.tsx @@ -1,5 +1,6 @@ import { ReactNode } from 'react'; +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { ObjectFilterDropdownScopeInternalContext } from './scope-internal-context/ObjectFilterDropdownScopeInternalContext'; type ObjectFilterDropdownScopeProps = { @@ -12,10 +13,14 @@ export const ObjectFilterDropdownScope = ({ filterScopeId, }: ObjectFilterDropdownScopeProps) => { return ( - <ObjectFilterDropdownScopeInternalContext.Provider - value={{ scopeId: filterScopeId }} + <ObjectFilterDropdownComponentInstanceContext.Provider + value={{ instanceId: filterScopeId }} > - {children} - </ObjectFilterDropdownScopeInternalContext.Provider> + <ObjectFilterDropdownScopeInternalContext.Provider + value={{ scopeId: filterScopeId }} + > + {children} + </ObjectFilterDropdownScopeInternalContext.Provider> + </ObjectFilterDropdownComponentInstanceContext.Provider> ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext.ts new file mode 100644 index 000000000000..44305191b8d9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext.ts @@ -0,0 +1,4 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const ObjectFilterDropdownComponentInstanceContext = + createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState.ts new file mode 100644 index 000000000000..051f5b9e7b69 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState.ts @@ -0,0 +1,9 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const objectFilterDropdownFilterIsSelectedComponentState = + createComponentStateV2<boolean>({ + key: 'objectFilterDropdownFilterIsSelectedComponentState', + defaultValue: false, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState.ts new file mode 100644 index 000000000000..b686bb1bbd03 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownFirstLevelFilterDefinitionComponentState.ts @@ -0,0 +1,10 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const objectFilterDropdownFirstLevelFilterDefinitionComponentState = + createComponentStateV2<FilterDefinition | null>({ + key: 'objectFilterDropdownFirstLevelFilterDefinitionComponentState', + defaultValue: null, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState.ts new file mode 100644 index 000000000000..68a141fd88ad --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState.ts @@ -0,0 +1,9 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const objectFilterDropdownIsSelectingCompositeFieldComponentState = + createComponentStateV2<boolean>({ + key: 'objectFilterDropdownIsSelectingCompositeFieldComponentState', + defaultValue: false, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState.ts new file mode 100644 index 000000000000..f00347b30ea6 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState.ts @@ -0,0 +1,10 @@ +import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; +import { CompositeFilterableFieldType } from '@/object-record/object-filter-dropdown/types/CompositeFilterableFieldType'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const objectFilterDropdownSubMenuFieldTypeComponentState = + createComponentStateV2<CompositeFilterableFieldType | null>({ + key: 'objectFilterDropdownSubMenuFieldTypeComponentState', + defaultValue: null, + componentInstanceContext: ObjectFilterDropdownComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/CompositeFilterableFieldType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/CompositeFilterableFieldType.ts new file mode 100644 index 000000000000..0bdf399feb6b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/CompositeFilterableFieldType.ts @@ -0,0 +1,5 @@ +import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType'; +import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType'; + +export type CompositeFilterableFieldType = FilterableFieldType & + CompositeFieldType; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts index 52ed99ac5531..4d2eddb8756e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/Filter.ts @@ -1,5 +1,4 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; - import { FilterDefinition } from './FilterDefinition'; export type Filter = { diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterDefinition.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterDefinition.ts index 954562f4fed3..b516bbeced7f 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterDefinition.ts @@ -1,14 +1,15 @@ import { IconComponent } from 'twenty-ui'; -import { FilterType } from './FilterType'; +import { FilterableFieldType } from './FilterableFieldType'; export type FilterDefinition = { fieldMetadataId: string; label: string; iconName: string; - type: FilterType; + type: FilterableFieldType; relationObjectMetadataNamePlural?: string; relationObjectMetadataNameSingular?: string; selectAllLabel?: string; SelectAllIcon?: IconComponent; + compositeFieldName?: string; }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts similarity index 51% rename from packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts rename to packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts index 1803a88a54e4..0624fe937ef7 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/types/FilterableFieldType.ts @@ -1,15 +1,16 @@ -export type FilterType = +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { PickLiteral } from '~/types/PickLiteral'; + +export type FilterableFieldType = PickLiteral< + FieldType, | 'TEXT' - | 'PHONE' | 'PHONES' - | 'EMAIL' | 'EMAILS' | 'DATE_TIME' | 'DATE' | 'NUMBER' | 'CURRENCY' | 'FULL_NAME' - | 'LINK' | 'LINKS' | 'RELATION' | 'ADDRESS' @@ -17,4 +18,5 @@ export type FilterType = | 'RATING' | 'MULTI_SELECT' | 'ACTOR' - | 'ARRAY'; + | 'ARRAY' +>; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.tsx index 7b4b9516e7f4..023a229edeca 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.tsx @@ -1,7 +1,8 @@ -import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { getOperandsForFilterType } from '../getOperandsForFilterType'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType'; +import { getOperandsForFilterDefinition } from '../getOperandsForFilterType'; describe('getOperandsForFilterType', () => { const emptyOperands = [ @@ -19,19 +20,28 @@ describe('getOperandsForFilterType', () => { ViewFilterOperand.LessThan, ]; + const dateOperands = [ + ViewFilterOperand.Is, + ViewFilterOperand.IsRelative, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, + ]; + const relationOperand = [ViewFilterOperand.Is, ViewFilterOperand.IsNot]; const testCases = [ ['TEXT', [...containsOperands, ...emptyOperands]], - ['EMAIL', [...containsOperands, ...emptyOperands]], ['FULL_NAME', [...containsOperands, ...emptyOperands]], ['ADDRESS', [...containsOperands, ...emptyOperands]], - ['LINK', [...containsOperands, ...emptyOperands]], ['LINKS', [...containsOperands, ...emptyOperands]], ['ACTOR', [...containsOperands, ...emptyOperands]], ['CURRENCY', [...numberOperands, ...emptyOperands]], ['NUMBER', [...numberOperands, ...emptyOperands]], - ['DATE_TIME', [...numberOperands, ...emptyOperands]], + ['DATE', [...dateOperands, ...emptyOperands]], + ['DATE_TIME', [...dateOperands, ...emptyOperands]], ['RELATION', [...relationOperand, ...emptyOperands]], [undefined, []], [null, []], @@ -40,7 +50,9 @@ describe('getOperandsForFilterType', () => { testCases.forEach(([filterType, expectedOperands]) => { it(`should return correct operands for FilterType.${filterType}`, () => { - const result = getOperandsForFilterType(filterType as FilterType); + const result = getOperandsForFilterDefinition({ + type: filterType as FilterableFieldType, + } as FilterDefinition); expect(result).toEqual(expectedOperands); }); }); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions.ts new file mode 100644 index 000000000000..b116a6fdb9a8 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions.ts @@ -0,0 +1,64 @@ +import { SelectableItem } from '@/object-record/select/types/SelectableItem'; +import { + IconApi, + IconCsv, + IconGmail, + IconGoogleCalendar, + IconRobot, + IconSettingsAutomation, + IconUserCircle, +} from 'twenty-ui'; + +export const getActorSourceMultiSelectOptions = ( + selectedSourceNames: string[], +): SelectableItem[] => { + return [ + { + id: 'MANUAL', + name: 'User', + isSelected: selectedSourceNames.includes('MANUAL'), + AvatarIcon: IconUserCircle, + isIconInverted: true, + }, + { + id: 'IMPORT', + name: 'Import', + isSelected: selectedSourceNames.includes('IMPORT'), + AvatarIcon: IconCsv, + isIconInverted: true, + }, + { + id: 'API', + name: 'Api', + isSelected: selectedSourceNames.includes('API'), + AvatarIcon: IconApi, + isIconInverted: true, + }, + { + id: 'EMAIL', + name: 'Email', + isSelected: selectedSourceNames.includes('EMAIL'), + AvatarIcon: IconGmail, + }, + { + id: 'CALENDAR', + name: 'Calendar', + isSelected: selectedSourceNames.includes('CALENDAR'), + AvatarIcon: IconGoogleCalendar, + }, + { + id: 'WORKFLOW', + name: 'Workflow', + isSelected: selectedSourceNames.includes('WORKFLOW'), + AvatarIcon: IconSettingsAutomation, + isIconInverted: true, + }, + { + id: 'SYSTEM', + name: 'System', + isSelected: selectedSourceNames.includes('SYSTEM'), + AvatarIcon: IconRobot, + isIconInverted: true, + }, + ]; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel.ts new file mode 100644 index 000000000000..36c6f04a46c9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel.ts @@ -0,0 +1,12 @@ +import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; +import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType'; + +export const getCompositeSubFieldLabel = ( + compositeFieldType: CompositeFieldType, + subFieldName: (typeof SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS)[CompositeFieldType]['subFields'][number], +): string => { + return ( + SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[compositeFieldType] + .labelBySubField as any + )[subFieldName]; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel.ts new file mode 100644 index 000000000000..69f9334e0cf2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel.ts @@ -0,0 +1,8 @@ +import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType'; +import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; + +export const getFilterableFieldTypeLabel = ( + filterableFieldType: FilterableFieldType, +) => { + return SETTINGS_FIELD_TYPE_CONFIGS[filterableFieldType].label; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts new file mode 100644 index 000000000000..12c9ecb74d7d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts @@ -0,0 +1,43 @@ +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { z } from 'zod'; + +export const getInitialFilterValue = ( + newType: FilterableFieldType, + newOperand: ViewFilterOperand, + oldValue?: string, + oldDisplayValue?: string, +): Pick<Filter, 'value' | 'displayValue'> | Record<string, never> => { + switch (newType) { + case 'DATE': + case 'DATE_TIME': { + const activeDatePickerOperands = [ + ViewFilterOperand.IsBefore, + ViewFilterOperand.Is, + ViewFilterOperand.IsAfter, + ]; + + if (activeDatePickerOperands.includes(newOperand)) { + const date = z.coerce.date().safeParse(oldValue).data ?? new Date(); + const value = date.toISOString(); + const displayValue = + newType === 'DATE' + ? date.toLocaleString() + : date.toLocaleDateString(); + + return { value, displayValue }; + } + + if (newOperand === ViewFilterOperand.IsRelative) { + return { value: '', displayValue: '' }; + } + break; + } + } + + return { + value: oldValue ?? '', + displayValue: oldDisplayValue ?? '', + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts index 9c9e297ef960..b68049b51750 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts @@ -12,6 +12,10 @@ export const getOperandLabel = ( return 'Greater than'; case ViewFilterOperand.LessThan: return 'Less than'; + case ViewFilterOperand.IsBefore: + return 'Is before'; + case ViewFilterOperand.IsAfter: + return 'Is after'; case ViewFilterOperand.Is: return 'Is'; case ViewFilterOperand.IsNot: @@ -22,6 +26,14 @@ export const getOperandLabel = ( return 'Is empty'; case ViewFilterOperand.IsNotEmpty: return 'Is not empty'; + case ViewFilterOperand.IsRelative: + return 'Is relative'; + case ViewFilterOperand.IsInPast: + return 'Is in past'; + case ViewFilterOperand.IsInFuture: + return 'Is in future'; + case ViewFilterOperand.IsToday: + return 'Is today'; default: return ''; } @@ -47,6 +59,16 @@ export const getOperandLabelShort = ( return '\u00A0> '; case ViewFilterOperand.LessThan: return '\u00A0< '; + case ViewFilterOperand.IsBefore: + return '\u00A0< '; + case ViewFilterOperand.IsAfter: + return '\u00A0> '; + case ViewFilterOperand.IsInPast: + return ': Past'; + case ViewFilterOperand.IsInFuture: + return ': Future'; + case ViewFilterOperand.IsToday: + return ': Today'; default: return ': '; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts index f75dca40f76c..688aa02b6c79 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts @@ -1,9 +1,9 @@ +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { isActorSourceCompositeFilter } from '@/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { FilterType } from '../types/FilterType'; - -export const getOperandsForFilterType = ( - filterType: FilterType | null | undefined, +export const getOperandsForFilterDefinition = ( + filterDefinition: FilterDefinition, ): ViewFilterOperand[] => { const emptyOperands = [ ViewFilterOperand.IsEmpty, @@ -12,16 +12,12 @@ export const getOperandsForFilterType = ( const relationOperands = [ViewFilterOperand.Is, ViewFilterOperand.IsNot]; - switch (filterType) { + switch (filterDefinition.type) { case 'TEXT': - case 'EMAIL': case 'EMAILS': case 'FULL_NAME': case 'ADDRESS': - case 'PHONE': - case 'LINK': case 'LINKS': - case 'ACTOR': case 'ARRAY': case 'PHONES': return [ @@ -31,13 +27,23 @@ export const getOperandsForFilterType = ( ]; case 'CURRENCY': case 'NUMBER': - case 'DATE_TIME': - case 'DATE': return [ ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan, ...emptyOperands, ]; + case 'DATE_TIME': + case 'DATE': + return [ + ViewFilterOperand.Is, + ViewFilterOperand.IsRelative, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, + ...emptyOperands, + ]; case 'RATING': return [ ViewFilterOperand.Is, @@ -49,6 +55,21 @@ export const getOperandsForFilterType = ( return [...relationOperands, ...emptyOperands]; case 'SELECT': return [...relationOperands]; + case 'ACTOR': { + if (isActorSourceCompositeFilter(filterDefinition)) { + return [ + ViewFilterOperand.Is, + ViewFilterOperand.IsNot, + ...emptyOperands, + ]; + } + + return [ + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ...emptyOperands, + ]; + } default: return []; } diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts new file mode 100644 index 000000000000..fb59e540180b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue.ts @@ -0,0 +1,28 @@ +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; +import { plural } from 'pluralize'; +import { capitalize } from '~/utils/string/capitalize'; +export const getRelativeDateDisplayValue = ( + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + } | null, +) => { + if (!relativeDate) return ''; + const { direction, amount, unit } = relativeDate; + + const directionStr = capitalize(direction.toLowerCase()); + const amountStr = direction === 'THIS' ? '' : amount; + const unitStr = amount + ? amount > 1 + ? plural(unit.toLowerCase()) + : unit.toLowerCase() + : undefined; + + return [directionStr, amountStr, unitStr] + .filter((item) => item !== undefined) + .join(' '); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSettingsNonCompositeFieldTypeLabels.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSettingsNonCompositeFieldTypeLabels.ts new file mode 100644 index 000000000000..67a40d16400c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSettingsNonCompositeFieldTypeLabels.ts @@ -0,0 +1,10 @@ +import { SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs'; +import { SettingsNonCompositeFieldType } from '@/settings/data-model/types/SettingsNonCompositeFieldType'; + +export const getSettingsNonCompositeFieldTypeLabel = ( + settingsNonCompositeFieldType: SettingsNonCompositeFieldType, +) => { + return SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS[ + settingsNonCompositeFieldType + ].label; +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSubMenuOptions.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSubMenuOptions.ts new file mode 100644 index 000000000000..0c3bfb1fb097 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSubMenuOptions.ts @@ -0,0 +1,21 @@ +import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType'; + +export const getSubMenuOptions = (subMenu: FilterableFieldType | null) => { + switch (subMenu) { + case 'ACTOR': + return [ + { + name: 'Creation Source', + icon: 'IconPlug', + type: 'SOURCE', + }, + { + name: 'Creator Name', + icon: 'IconId', + type: 'ACTOR', + }, + ]; + default: + return []; + } +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter.ts new file mode 100644 index 000000000000..0d1d5046ab41 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isActorSourceCompositeFilter.ts @@ -0,0 +1,11 @@ +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { FieldActorValue } from '@/object-record/record-field/types/FieldMetadata'; + +export const isActorSourceCompositeFilter = ( + filterDefinition: FilterDefinition, +) => { + return ( + filterDefinition.compositeFieldName === + ('source' satisfies keyof FieldActorValue) + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isCompositeField.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isCompositeField.ts new file mode 100644 index 000000000000..6de44cd44e18 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isCompositeField.ts @@ -0,0 +1,8 @@ +import { + COMPOSITE_FIELD_TYPES, + CompositeFieldType, +} from '@/settings/data-model/types/CompositeFieldType'; +import { FieldType } from '@/settings/data-model/types/FieldType'; + +export const isCompositeField = (type: FieldType): type is CompositeFieldType => + COMPOSITE_FIELD_TYPES.includes(type as any); diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx index 35869601c988..f1e399c1a0b9 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx @@ -5,14 +5,17 @@ import { IconChevronDown, useIcons } from 'twenty-ui'; import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId'; import { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown'; import { ObjectSortDropdownScope } from '@/object-record/object-sort-dropdown/scopes/ObjectSortDropdownScope'; +import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; +import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; - +import { useContext } from 'react'; import { SORT_DIRECTIONS } from '../types/SortDirection'; export const StyledInput = styled.input` @@ -39,6 +42,21 @@ export const StyledInput = styled.input` } `; +const StyledContainer = styled.div` + position: relative; +`; + +const StyledSelectedSortDirectionContainer = styled.div` + background: ${({ theme }) => theme.background.secondary}; + box-shadow: ${({ theme }) => theme.boxShadow.light}; + border-radius: ${({ theme }) => theme.border.radius.md}; + left: 10px; + position: absolute; + top: 10px; + width: 100%; + z-index: 1000; +`; + export type ObjectSortDropdownButtonProps = { sortDropdownId: string; hotkeyScope: HotkeyScope; @@ -79,6 +97,43 @@ export const ObjectSortDropdownButton = ({ const { getIcon } = useIcons(); + const { recordIndexId } = useContext(RecordIndexRootPropsContext); + const { hiddenTableColumnsSelector, visibleTableColumnsSelector } = + useRecordTableStates(recordIndexId); + + const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector()); + const visibleColumnsIds = visibleTableColumns.map( + (column) => column.fieldMetadataId, + ); + const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector()); + const hiddenColumnIds = hiddenTableColumns.map( + (column) => column.fieldMetadataId, + ); + + const filteredSearchInputSortDefinitions = availableSortDefinitions.filter( + (item) => + item.label + .toLocaleLowerCase() + .includes(objectSortDropdownSearchInput.toLocaleLowerCase()), + ); + + const visibleColumnsSortDefinitions = filteredSearchInputSortDefinitions + .sort((a, b) => { + return ( + visibleColumnsIds.indexOf(a.fieldMetadataId) - + visibleColumnsIds.indexOf(b.fieldMetadataId) + ); + }) + .filter((item) => visibleColumnsIds.includes(item.fieldMetadataId)); + + const hiddenColumnsSortDefinitions = filteredSearchInputSortDefinitions + .sort((a, b) => a.label.localeCompare(b.label)) + .filter((item) => hiddenColumnIds.includes(item.fieldMetadataId)); + + const shoudShowSeparator = + visibleColumnsSortDefinitions.length > 0 && + hiddenColumnsSortDefinitions.length > 0; + return ( <ObjectSortDropdownScope sortScopeId={sortDropdownId}> <Dropdown @@ -95,60 +150,71 @@ export const ObjectSortDropdownButton = ({ } dropdownComponents={ <> - {isSortDirectionMenuUnfolded ? ( - <DropdownMenuItemsContainer> - {SORT_DIRECTIONS.map((sortOrder, index) => ( - <MenuItem - key={index} - onClick={() => { - setSelectedSortDirection(sortOrder); - setIsSortDirectionMenuUnfolded(false); - }} - text={sortOrder === 'asc' ? 'Ascending' : 'Descending'} - /> - ))} - </DropdownMenuItemsContainer> - ) : ( - <> - <DropdownMenuHeader - EndIcon={IconChevronDown} - onClick={() => setIsSortDirectionMenuUnfolded(true)} - > - {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} - </DropdownMenuHeader> - <StyledInput - autoFocus - value={objectSortDropdownSearchInput} - placeholder="Search fields" - onChange={(event) => - setObjectSortDropdownSearchInput(event.target.value) - } - /> + {isSortDirectionMenuUnfolded && ( + <StyledSelectedSortDirectionContainer> <DropdownMenuItemsContainer> - {[...availableSortDefinitions] - .sort((a, b) => a.label.localeCompare(b.label)) - .filter((item) => - item.label - .toLocaleLowerCase() - .includes( - objectSortDropdownSearchInput.toLocaleLowerCase(), - ), - ) - .map((availableSortDefinition, index) => ( - <MenuItem - testId={`select-sort-${index}`} - key={index} - onClick={() => { - setObjectSortDropdownSearchInput(''); - handleAddSort(availableSortDefinition); - }} - LeftIcon={getIcon(availableSortDefinition.iconName)} - text={availableSortDefinition.label} - /> - ))} + {SORT_DIRECTIONS.map((sortOrder, index) => ( + <MenuItem + key={index} + onClick={() => { + setSelectedSortDirection(sortOrder); + setIsSortDirectionMenuUnfolded(false); + }} + text={sortOrder === 'asc' ? 'Ascending' : 'Descending'} + /> + ))} </DropdownMenuItemsContainer> - </> + </StyledSelectedSortDirectionContainer> )} + <StyledContainer> + <DropdownMenuHeader + EndIcon={IconChevronDown} + onClick={() => setIsSortDirectionMenuUnfolded(true)} + > + {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} + </DropdownMenuHeader> + <StyledInput + autoFocus + value={objectSortDropdownSearchInput} + placeholder="Search fields" + onChange={(event) => + setObjectSortDropdownSearchInput(event.target.value) + } + /> + <DropdownMenuItemsContainer> + {visibleColumnsSortDefinitions.map( + (visibleSortDefinition, index) => ( + <MenuItem + testId={`visible-select-sort-${index}`} + key={index} + onClick={() => { + setObjectSortDropdownSearchInput(''); + handleAddSort(visibleSortDefinition); + }} + LeftIcon={getIcon(visibleSortDefinition.iconName)} + text={visibleSortDefinition.label} + /> + ), + )} + </DropdownMenuItemsContainer> + {shoudShowSeparator && <DropdownMenuSeparator />} + <DropdownMenuItemsContainer> + {hiddenColumnsSortDefinitions.map( + (hiddenSortDefinition, index) => ( + <MenuItem + testId={`hidden-select-sort-${index}`} + key={index} + onClick={() => { + setObjectSortDropdownSearchInput(''); + handleAddSort(hiddenSortDefinition); + }} + LeftIcon={getIcon(hiddenSortDefinition.iconName)} + text={hiddenSortDefinition.label} + /> + ), + )} + </DropdownMenuItemsContainer> + </StyledContainer> </> } onClose={handleDropdownButtonClose} diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.tsx index 8534f11cabc3..12fcaff75558 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/utils/__tests__/turnSortsIntoOrderBy.test.tsx @@ -13,6 +13,7 @@ const sortDefinition: SortDefinition = { const objectMetadataItem: ObjectMetadataItem = { id: 'object1', fields: [], + indexMetadatas: [], createdAt: '2021-01-01', updatedAt: '2021-01-01', nameSingular: 'object1', diff --git a/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx b/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx deleted file mode 100644 index b7cce8057efb..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { isNonEmptyString } from '@sniptt/guards'; -import { useCallback, useMemo, useState } from 'react'; -import { useRecoilCallback, useSetRecoilState } from 'recoil'; -import { IconFileExport, IconHeart, IconHeartOff, IconTrash } from 'twenty-ui'; - -import { useFavorites } from '@/favorites/hooks/useFavorites'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; -import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData'; -import { - displayedExportProgress, - useExportTableData, -} from '@/object-record/record-index/options/hooks/useExportTableData'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; -import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; -import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; -import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry'; -import { isDefined } from '~/utils/isDefined'; - -type useRecordActionBarProps = { - objectMetadataItem: ObjectMetadataItem; - selectedRecordIds: string[]; - callback?: () => void; - totalNumberOfRecordsSelected?: number; -}; - -export const useRecordActionBar = ({ - objectMetadataItem, - selectedRecordIds, - callback, - totalNumberOfRecordsSelected, -}: useRecordActionBarProps) => { - const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); - const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState); - const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = - useState(false); - - const { createFavorite, favorites, deleteFavorite } = useFavorites(); - - const handleFavoriteButtonClick = useRecoilCallback( - ({ snapshot }) => - () => { - if (selectedRecordIds.length > 1) { - return; - } - - const selectedRecordId = selectedRecordIds[0]; - const selectedRecord = snapshot - .getLoadable(recordStoreFamilyState(selectedRecordId)) - .getValue(); - - const foundFavorite = favorites?.find( - (favorite) => favorite.recordId === selectedRecordId, - ); - - const isFavorite = !!selectedRecordId && !!foundFavorite; - - if (isFavorite) { - deleteFavorite(foundFavorite.id); - } else if (isDefined(selectedRecord)) { - createFavorite(selectedRecord, objectMetadataItem.nameSingular); - } - callback?.(); - }, - [ - callback, - createFavorite, - deleteFavorite, - favorites, - objectMetadataItem.nameSingular, - selectedRecordIds, - ], - ); - - const baseTableDataParams = { - delayMs: 100, - objectNameSingular: objectMetadataItem.nameSingular, - recordIndexId: objectMetadataItem.namePlural, - }; - - const { deleteTableData } = useDeleteTableData(baseTableDataParams); - - const handleDeleteClick = useCallback(() => { - deleteTableData(selectedRecordIds); - }, [deleteTableData, selectedRecordIds]); - - const { progress, download } = useExportTableData({ - ...baseTableDataParams, - filename: `${objectMetadataItem.nameSingular}.csv`, - }); - - const isRemoteObject = objectMetadataItem.isRemote; - - const numberOfSelectedRecords = - totalNumberOfRecordsSelected ?? selectedRecordIds.length; - const canDelete = - !isRemoteObject && numberOfSelectedRecords < DELETE_MAX_COUNT; - - const menuActions: ContextMenuEntry[] = useMemo( - () => - [ - { - label: displayedExportProgress(progress), - Icon: IconFileExport, - accent: 'default', - onClick: () => download(), - } satisfies ContextMenuEntry, - canDelete - ? ({ - label: 'Delete', - Icon: IconTrash, - accent: 'danger', - onClick: () => { - setIsDeleteRecordsModalOpen(true); - }, - ConfirmationModal: ( - <ConfirmationModal - isOpen={isDeleteRecordsModalOpen} - setIsOpen={setIsDeleteRecordsModalOpen} - title={`Delete ${numberOfSelectedRecords} ${ - numberOfSelectedRecords === 1 ? `record` : 'records' - }`} - subtitle={`Are you sure you want to delete ${ - numberOfSelectedRecords === 1 - ? 'this record' - : 'these records' - }? ${ - numberOfSelectedRecords === 1 ? 'It' : 'They' - } can be recovered from the Options menu.`} - onConfirmClick={() => handleDeleteClick()} - deleteButtonText={`Delete ${ - numberOfSelectedRecords > 1 ? 'Records' : 'Record' - }`} - /> - ), - } satisfies ContextMenuEntry) - : undefined, - ].filter(isDefined), - [ - download, - progress, - canDelete, - handleDeleteClick, - isDeleteRecordsModalOpen, - numberOfSelectedRecords, - ], - ); - - const hasOnlyOneRecordSelected = selectedRecordIds.length === 1; - - const isFavorite = - isNonEmptyString(selectedRecordIds[0]) && - !!favorites?.find((favorite) => favorite.recordId === selectedRecordIds[0]); - - return { - setContextMenuEntries: useCallback(() => { - setContextMenuEntries([ - ...menuActions, - ...(!isRemoteObject && isFavorite && hasOnlyOneRecordSelected - ? [ - { - label: 'Remove from favorites', - Icon: IconHeartOff, - onClick: handleFavoriteButtonClick, - }, - ] - : []), - ...(!isRemoteObject && !isFavorite && hasOnlyOneRecordSelected - ? [ - { - label: 'Add to favorites', - Icon: IconHeart, - onClick: handleFavoriteButtonClick, - }, - ] - : []), - ]); - }, [ - menuActions, - handleFavoriteButtonClick, - hasOnlyOneRecordSelected, - isFavorite, - isRemoteObject, - setContextMenuEntries, - ]), - - setActionBarEntries: useCallback(() => { - setActionBarEntriesState([ - /* - { - label: 'Actions', - Icon: IconClick, - subActions: - - /* [ - { - label: 'Enrich', - Icon: IconPuzzle, - onClick: handleExecuteQuickActionOnClick, - }, - { - label: 'Send to mailjet', - Icon: IconMail, - }, - ], - */ - ...menuActions, - ]); - }, [menuActions, setActionBarEntriesState]), - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/action-bar/components/RecordBoardActionBar.tsx b/packages/twenty-front/src/modules/object-record/record-board/action-bar/components/RecordBoardActionBar.tsx deleted file mode 100644 index 584cbeab3b5f..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/action-bar/components/RecordBoardActionBar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; -import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar'; - -type RecordBoardActionBarProps = { - recordBoardId: string; -}; - -export const RecordBoardActionBar = ({ - recordBoardId, -}: RecordBoardActionBarProps) => { - const { selectedRecordIdsSelector } = useRecordBoardStates(recordBoardId); - - const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector()); - - if (!selectedRecordIds.length) { - return null; - } - - return <ActionBar selectedIds={selectedRecordIds} />; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx index 52ca93001631..ccf08efe694d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx @@ -74,7 +74,7 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => { useListenClickOutsideByClassName({ classNames: ['record-board-card'], - excludeClassNames: ['action-bar', 'context-menu'], + excludeClassNames: ['bottom-bar', 'context-menu'], callback: resetRecordSelection, }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/context-menu/components/RecordBoardContextMenu.tsx b/packages/twenty-front/src/modules/object-record/record-board/context-menu/components/RecordBoardContextMenu.tsx deleted file mode 100644 index fed35a6506d3..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/context-menu/components/RecordBoardContextMenu.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; -import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu'; - -type RecordBoardContextMenuProps = { - recordBoardId: string; -}; - -export const RecordBoardContextMenu = ({ - recordBoardId, -}: RecordBoardContextMenuProps) => { - const { selectedRecordIdsSelector } = useRecordBoardStates(recordBoardId); - - const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector()); - - if (!selectedRecordIds.length) { - return null; - } - - return <ContextMenu />; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts index a14c85119d51..8b2d11837ae1 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts @@ -1,17 +1,23 @@ -import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; -import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; +import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; -export const useRecordBoardSelection = (recordBoardId?: string) => { - const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); +export const useRecordBoardSelection = (recordBoardId: string) => { const { selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } = useRecordBoardStates(recordBoardId); const resetRecordSelection = useRecoilCallback( ({ snapshot, set }) => () => { - setContextMenuOpenState(false); + const isActionMenuDropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + `action-menu-dropdown-${recordBoardId}`, + ); + + set(isActionMenuDropdownOpenState, false); + const recordIds = snapshot .getLoadable(selectedRecordIdsSelector()) .getValue(); @@ -21,9 +27,9 @@ export const useRecordBoardSelection = (recordBoardId?: string) => { } }, [ + recordBoardId, selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState, - setContextMenuOpenState, ], ); 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 89f8952e3bbe..a2ea5b03def3 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 @@ -1,12 +1,9 @@ -import styled from '@emotion/styled'; -import { ReactNode, useContext, useState } from 'react'; -import { useInView } from 'react-intersection-observer'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import { AvatarChipVariant, IconEye } from 'twenty-ui'; - +import { useActionMenu } from '@/action-menu/hooks/useActionMenu'; +import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; +import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext'; import { FieldContext, RecordUpdateHook, @@ -15,15 +12,25 @@ import { import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon'; import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; +import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode'; import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox'; -import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; -import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; +import { TextInput } from '@/ui/input/components/TextInput'; import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; +import styled from '@emotion/styled'; +import { ReactNode, useContext, useState } from 'react'; +import { useInView } from 'react-intersection-observer'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { AvatarChipVariant, IconEye, IconEyeOff } from 'twenty-ui'; +import { useDebouncedCallback } from 'use-debounce'; +import { useAddNewCard } from '../../record-board-column/hooks/useAddNewCard'; const StyledBoardCard = styled.div<{ selected: boolean }>` background-color: ${({ theme, selected }) => @@ -61,6 +68,14 @@ const StyledBoardCard = styled.div<{ selected: boolean }>` } `; +const StyledTextInput = styled(TextInput)` + backdrop-filter: blur(12px) saturate(200%) contrast(50%) brightness(130%); + background: ${({ theme }) => theme.background.primary}; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; + width: ${({ theme }) => theme.spacing(53)}; + border-radius: ${({ theme }) => theme.border.radius.sm}; +`; + const StyledBoardCardWrapper = styled.div` padding-bottom: ${({ theme }) => theme.spacing(2)}; width: 100%; @@ -75,7 +90,7 @@ export const StyledBoardCardHeader = styled.div<{ font-weight: ${({ theme }) => theme.font.weight.medium}; height: 24px; padding-bottom: ${({ theme, showCompactView }) => - theme.spacing(showCompactView ? 0 : 1)}; + theme.spacing(showCompactView ? 2 : 1)}; padding-left: ${({ theme }) => theme.spacing(2)}; padding-right: ${({ theme }) => theme.spacing(2)}; padding-top: ${({ theme }) => theme.spacing(2)}; @@ -130,7 +145,17 @@ const StyledRecordInlineCellPlaceholder = styled.div` height: 24px; `; -export const RecordBoardCard = () => { +export const RecordBoardCard = ({ + isCreating = false, + onCreateSuccess, + position, +}: { + isCreating?: boolean; + onCreateSuccess?: () => void; + position?: 'first' | 'last'; +}) => { + const [newLabelValue, setNewLabelValue] = useState(''); + const { handleBlur, handleInputEnter } = useAddNewCard(); const { recordId } = useContext(RecordBoardCardContext); const { updateOneRecord, objectMetadataItem } = useContext(RecordBoardContext); @@ -139,10 +164,9 @@ export const RecordBoardCard = () => { isRecordBoardCardSelectedFamilyState, visibleFieldDefinitionsState, } = useRecordBoardStates(); - const isCompactModeActive = useRecoilValue(isCompactModeActiveState); - const [isCardInCompactMode, setIsCardInCompactMode] = useState(true); + const [isCardExpanded, setIsCardExpanded] = useState(false); const [isCurrentCardSelected, setIsCurrentCardSelected] = useRecoilState( isRecordBoardCardSelectedFamilyState(recordId), @@ -154,17 +178,27 @@ export const RecordBoardCard = () => { const record = useRecoilValue(recordStoreFamilyState(recordId)); - const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); - const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); + const recordBoardId = useAvailableScopeIdOrThrow( + RecordBoardScopeInternalContext, + ); + + const setActionMenuDropdownPosition = useSetRecoilState( + extractComponentState( + actionMenuDropdownPositionComponentState, + `action-menu-dropdown-${recordBoardId}`, + ), + ); + + const { openActionMenuDropdown } = useActionMenu(recordBoardId); - const handleContextMenu = (event: React.MouseEvent) => { + const handleActionMenuDropdown = (event: React.MouseEvent) => { event.preventDefault(); setIsCurrentCardSelected(true); - setContextMenuPosition({ + setActionMenuDropdownPosition({ x: event.clientX, y: event.clientY, }); - setContextMenuOpenState(true); + openActionMenuDropdown(); }; const PreventSelectOnClickContainer = ({ @@ -181,11 +215,11 @@ export const RecordBoardCard = () => { </StyledFieldContainer> ); - const onMouseLeaveBoard = () => { - if (isCompactModeActive) { - setIsCardInCompactMode(true); + const onMouseLeaveBoard = useDebouncedCallback(() => { + if (isCompactModeActive && isCardExpanded) { + setIsCardExpanded(false); } - }; + }, 800); const useUpdateOneRecordHook: RecordUpdateHook = () => { const updateEntity = ({ variables }: RecordUpdateHookParams) => { @@ -205,66 +239,106 @@ export const RecordBoardCard = () => { rootMargin: '1000px', }); - if (!record) { - return null; - } - const visibleFieldDefinitionsFiltered = visibleFieldDefinitions.filter( (boardField) => !boardField.isLabelIdentifier, ); + const labelIdentifierField = visibleFieldDefinitions.find( + (field) => field.isLabelIdentifier, + ); + return ( - <StyledBoardCardWrapper onContextMenu={handleContextMenu}> - <RecordValueSetterEffect recordId={recordId} /> + <StyledBoardCardWrapper onContextMenu={handleActionMenuDropdown}> + {!isCreating && <RecordValueSetterEffect recordId={recordId} />} <StyledBoardCard ref={cardRef} selected={isCurrentCardSelected} onMouseLeave={onMouseLeaveBoard} onClick={() => { - setIsCurrentCardSelected(!isCurrentCardSelected); + if (!isCreating) { + setIsCurrentCardSelected(!isCurrentCardSelected); + } }} > <StyledBoardCardHeader showCompactView={isCompactModeActive}> - <RecordIdentifierChip - objectNameSingular={objectMetadataItem.nameSingular} - record={record} - variant={AvatarChipVariant.Transparent} - /> - {isCompactModeActive && ( - <StyledCompactIconContainer className="compact-icon-container"> - <LightIconButton - Icon={IconEye} - accent="tertiary" - onClick={(e) => { - e.stopPropagation(); - setIsCardInCompactMode(false); - }} + {isCreating && position !== undefined ? ( + <RecordInlineCellEditMode> + <StyledTextInput + autoFocus + value={newLabelValue} + onInputEnter={() => + handleInputEnter( + labelIdentifierField?.label ?? '', + newLabelValue, + position, + onCreateSuccess, + ) + } + onBlur={() => + handleBlur( + labelIdentifierField?.label ?? '', + newLabelValue, + position, + onCreateSuccess, + ) + } + onChange={(text: string) => setNewLabelValue(text)} + placeholder={labelIdentifierField?.label} /> - </StyledCompactIconContainer> - )} - <StyledCheckboxContainer className="checkbox-container"> - <Checkbox - hoverable - checked={isCurrentCardSelected} - onChange={() => setIsCurrentCardSelected(!isCurrentCardSelected)} - variant={CheckboxVariant.Secondary} + </RecordInlineCellEditMode> + ) : ( + <RecordIdentifierChip + objectNameSingular={objectMetadataItem.nameSingular} + record={record as ObjectRecord} + variant={AvatarChipVariant.Transparent} /> - </StyledCheckboxContainer> + )} + + {!isCreating && ( + <> + {isCompactModeActive && ( + <StyledCompactIconContainer className="compact-icon-container"> + <LightIconButton + Icon={isCardExpanded ? IconEyeOff : IconEye} + accent="tertiary" + onClick={(e) => { + e.stopPropagation(); + setIsCardExpanded((prev) => !prev); + }} + /> + </StyledCompactIconContainer> + )} + + <StyledCheckboxContainer className="checkbox-container"> + <Checkbox + hoverable + checked={isCurrentCardSelected} + onChange={() => + setIsCurrentCardSelected(!isCurrentCardSelected) + } + variant={CheckboxVariant.Secondary} + /> + </StyledCheckboxContainer> + </> + )} </StyledBoardCardHeader> - <StyledBoardCardBody> - <AnimatedEaseInOut - isOpen={!isCardInCompactMode || !isCompactModeActive} - initial={false} - > + + <AnimatedEaseInOut + isOpen={isCardExpanded || !isCompactModeActive} + initial={false} + > + <StyledBoardCardBody> {visibleFieldDefinitionsFiltered.map((fieldDefinition) => ( <PreventSelectOnClickContainer key={fieldDefinition.fieldMetadataId} > <FieldContext.Provider value={{ - recordId, + recordId: isCreating ? '' : recordId, maxWidth: 156, - recoilScopeId: recordId + fieldDefinition.fieldMetadataId, + recoilScopeId: + (isCreating ? 'new' : recordId) + + fieldDefinition.fieldMetadataId, isLabelIdentifier: false, fieldDefinition: { disableTooltip: false, @@ -278,6 +352,7 @@ export const RecordBoardCard = () => { metadata: fieldDefinition.metadata, type: fieldDefinition.type, }), + settings: fieldDefinition.settings, }, useUpdateRecord: useUpdateOneRecordHook, hotkeyScope: InlineCellHotkeyScope.InlineCell, @@ -291,8 +366,8 @@ export const RecordBoardCard = () => { </FieldContext.Provider> </PreventSelectOnClickContainer> ))} - </AnimatedEaseInOut> - </StyledBoardCardBody> + </StyledBoardCardBody> + </AnimatedEaseInOut> </StyledBoardCard> </StyledBoardCardWrapper> ); 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 = ({ > <StyledBoardCardHeader showCompactView={isCompactModeActive}> <StyledSkeletonTitle> - <Skeleton width={titleSkeletonWidth} height={16} /> + <Skeleton + width={titleSkeletonWidth} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </StyledSkeletonTitle> </StyledBoardCardHeader> <StyledSeparator /> @@ -51,8 +55,14 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({ skeletonItems.map(({ id }) => ( <StyledBoardCardBody key={id}> <StyledSkeletonIconAndText> - <Skeleton width={16} height={16} /> - <Skeleton width={151} height={16} /> + <Skeleton + width={16} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> + <Skeleton + width={151} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </StyledSkeletonIconAndText> </StyledBoardCardBody> ))} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx index 79c786df1225..8cd90bf79f44 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx @@ -1,6 +1,6 @@ -import React, { useContext } from 'react'; import styled from '@emotion/styled'; import { Draggable, DroppableProvided } from '@hello-pangea/dnd'; +import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -12,6 +12,7 @@ import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/r import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton'; import { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; +import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading'; import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState'; @@ -64,6 +65,8 @@ export const RecordBoardColumnCardsContainer = ({ const numberOfFields = visibleFieldDefinitions.length; const isCompactModeActive = useRecoilValue(isCompactModeActiveState); + const { isOpportunitiesCompanyFieldDisabled } = + useIsOpportunitiesCompanyFieldDisabled(); return ( <StyledColumnCardsContainer @@ -107,15 +110,19 @@ export const RecordBoardColumnCardsContainer = ({ > <StyledNewButtonContainer> {objectMetadataItem.nameSingular === - CoreObjectNameSingular.Opportunity ? ( - <RecordBoardColumnNewOpportunityButton /> + CoreObjectNameSingular.Opportunity && + !isOpportunitiesCompanyFieldDisabled ? ( + <RecordBoardColumnNewOpportunityButton + columnId={columnDefinition.id} + /> ) : ( - <RecordBoardColumnNewButton /> + <RecordBoardColumnNewButton columnId={columnDefinition.id} /> )} </StyledNewButtonContainer> </div> )} </Draggable> + {droppableProvided?.placeholder} </StyledColumnCardsContainer> ); }; 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 98fd523aa3b5..ec3805977e96 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 @@ -4,10 +4,11 @@ import { IconDotsVertical, IconPlus, Tag } from 'twenty-ui'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard'; import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; -import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; -import { useAddNewOpportunity } from '@/object-record/record-board/record-board-column/hooks/useAddNewOpportunity'; +import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions'; +import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; @@ -89,21 +90,17 @@ export const RecordBoardColumnHeader = () => { const boardColumnTotal = 0; const { - isCreatingCard, - handleAddNewOpportunityClick, - handleCancel, + newRecord, + handleNewButtonClick, + handleCreateSuccess, handleEntitySelect, - } = useAddNewOpportunity('first'); - const { handleAddNewCardClick } = useAddNewCard('first'); + } = useColumnNewCardActions(columnDefinition.id); + const { isOpportunitiesCompanyFieldDisabled } = + useIsOpportunitiesCompanyFieldDisabled(); const isOpportunity = - objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity; - - const handleClick = isOpportunity - ? handleAddNewOpportunityClick - : () => { - handleAddNewCardClick(); - }; + objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity && + !isOpportunitiesCompanyFieldDisabled; return ( <> @@ -151,7 +148,7 @@ export const RecordBoardColumnHeader = () => { <LightIconButton accent="tertiary" Icon={IconPlus} - onClick={handleClick} + onClick={() => handleNewButtonClick('first', isOpportunity)} /> </StyledHeaderActions> )} @@ -164,16 +161,26 @@ export const RecordBoardColumnHeader = () => { stageId={columnDefinition.id} /> )} - {isCreatingCard && ( - <SingleEntitySelect - disableBackgroundBlur - onCancel={handleCancel} - onEntitySelected={handleEntitySelect} - relationObjectNameSingular={CoreObjectNameSingular.Company} - relationPickerScopeId="relation-picker" - selectedRelationRecordIds={[]} - /> - )} + {newRecord?.isCreating && + newRecord.position === 'first' && + (newRecord.isOpportunity ? ( + <SingleEntitySelect + disableBackgroundBlur + onCancel={() => handleCreateSuccess('first', columnDefinition.id)} + onEntitySelected={(company) => + company && handleEntitySelect('first', company) + } + relationObjectNameSingular={CoreObjectNameSingular.Company} + relationPickerScopeId="relation-picker" + selectedRelationRecordIds={[]} + /> + ) : ( + <RecordBoardCard + isCreating={true} + onCreateSuccess={() => handleCreateSuccess('first')} + position="first" + /> + ))} </> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton.tsx index c12e0c492374..428b9921f55d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton.tsx @@ -1,10 +1,10 @@ +import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard'; +import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { IconPlus } from 'twenty-ui'; -import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; - -const StyledButton = styled.button` +const StyledNewButton = styled.button` align-items: center; align-self: baseline; background-color: ${({ theme }) => theme.background.primary}; @@ -15,19 +15,39 @@ const StyledButton = styled.button` display: flex; gap: ${({ theme }) => theme.spacing(1)}; padding: ${({ theme }) => theme.spacing(1)}; - &:hover { background-color: ${({ theme }) => theme.background.tertiary}; } `; -export const RecordBoardColumnNewButton = () => { +export const RecordBoardColumnNewButton = ({ + columnId, +}: { + columnId: string; +}) => { const theme = useTheme(); - const { handleAddNewCardClick } = useAddNewCard('last'); + + const { newRecord, handleNewButtonClick, handleCreateSuccess } = + useColumnNewCardActions(columnId); + + if ( + newRecord.isCreating && + newRecord.position === 'last' && + !newRecord.isOpportunity + ) { + return ( + <RecordBoardCard + isCreating={true} + onCreateSuccess={() => handleCreateSuccess('last')} + position="last" + /> + ); + } + return ( - <StyledButton onClick={handleAddNewCardClick}> + <StyledNewButton onClick={() => handleNewButtonClick('last', false)}> <IconPlus size={theme.icon.size.md} /> New - </StyledButton> + </StyledNewButton> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx index 6464948dcfcc..55e9e2e1e581 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; import { IconPlus } from 'twenty-ui'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useAddNewOpportunity } from '@/object-record/record-board/record-board-column/hooks/useAddNewOpportunity'; +import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; const StyledButton = styled.button` @@ -23,27 +23,36 @@ const StyledButton = styled.button` } `; -export const RecordBoardColumnNewOpportunityButton = () => { +export const RecordBoardColumnNewOpportunityButton = ({ + columnId, +}: { + columnId: string; +}) => { const theme = useTheme(); + const { - isCreatingCard, - handleAddNewOpportunityClick, - handleCancel, + newRecord, + handleNewButtonClick, handleEntitySelect, - } = useAddNewOpportunity('last'); + handleCreateSuccess, + } = useColumnNewCardActions(columnId); return ( <> - {isCreatingCard ? ( + {newRecord.isCreating && + newRecord.position === 'last' && + newRecord.isOpportunity ? ( <SingleEntitySelect disableBackgroundBlur - onCancel={handleCancel} - onEntitySelected={handleEntitySelect} + onCancel={() => handleCreateSuccess('last', columnId, false)} + onEntitySelected={(company) => + company ? handleEntitySelect('last', company) : null + } relationObjectNameSingular={CoreObjectNameSingular.Company} relationPickerScopeId="relation-picker" selectedRelationRecordIds={[]} /> ) : ( - <StyledButton onClick={handleAddNewOpportunityClick}> + <StyledButton onClick={() => handleNewButtonClick('last', true)}> <IconPlus size={theme.icon.size.md} /> New </StyledButton> 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 24cb1e158548..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,20 +1,208 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; -import { useContext } from 'react'; +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'; +import { useCallback, useContext } from 'react'; +import { RecoilState, useRecoilCallback } from 'recoil'; +import { v4 as uuidv4 } from 'uuid'; -export const useAddNewCard = (position: string) => { - const { columnDefinition } = useContext(RecordBoardColumnContext); +type SetFunction = <T>( + recoilVal: RecoilState<T>, + valOrUpdater: T | ((currVal: T) => T), +) => void; + +export const useAddNewCard = () => { + const columnContext = useContext(RecordBoardColumnContext); const { createOneRecord, selectFieldMetadataItem } = useContext(RecordBoardContext); + const { resetSearchFilter } = useEntitySelectSearch({ + relationPickerScopeId: 'relation-picker', + }); + + const { + goBackToPreviousHotkeyScope, + setHotkeyScopeAndMemorizePreviousScope, + } = usePreviousHotkeyScope(); + + const getColumnDefinitionId = useCallback( + (columnId?: string) => { + const columnDefinitionId = columnId || columnContext?.columnDefinition.id; + if (!columnDefinitionId) { + throw new Error('Column ID is required'); + } + return columnDefinitionId; + }, + [columnContext], + ); + + const addNewItem = useCallback( + ( + set: SetFunction, + columnDefinitionId: string, + position: 'first' | 'last', + isOpportunity: boolean, + ) => { + set( + recordBoardNewRecordByColumnIdSelector({ + familyKey: columnDefinitionId, + scopeId: columnDefinitionId, + }), + { + id: uuidv4(), + columnId: columnDefinitionId, + isCreating: true, + position, + isOpportunity, + company: null, + }, + ); + }, + [], + ); + + const createRecord = useCallback( + ( + labelIdentifier: string, + labelValue: string, + position: 'first' | 'last', + isOpportunity: boolean, + company?: EntityForSelect, + ) => { + if ( + (isOpportunity && company !== null) || + (!isOpportunity && labelValue !== '') + ) { + createOneRecord({ + [selectFieldMetadataItem.name]: columnContext?.columnDefinition.value, + position, + ...(isOpportunity + ? { companyId: company?.id, name: company?.name } + : { [labelIdentifier.toLowerCase()]: labelValue }), + }); + } + }, + [createOneRecord, columnContext, selectFieldMetadataItem], + ); + + const handleAddNewCardClick = useRecoilCallback( + ({ set }) => + ( + labelIdentifier: string, + labelValue: string, + position: 'first' | 'last', + isOpportunity: boolean, + columnId?: string, + ): void => { + const columnDefinitionId = getColumnDefinitionId(columnId); + addNewItem(set, columnDefinitionId, position, isOpportunity); + if (isOpportunity) { + setHotkeyScopeAndMemorizePreviousScope( + RelationPickerHotkeyScope.RelationPicker, + ); + } else { + createRecord(labelIdentifier, labelValue, position, isOpportunity); + } + }, + [ + addNewItem, + createRecord, + getColumnDefinitionId, + setHotkeyScopeAndMemorizePreviousScope, + ], + ); - const handleAddNewCardClick = () => { - createOneRecord({ - [selectFieldMetadataItem.name]: columnDefinition.value, - position: position, - }); + const handleCreateSuccess = useRecoilCallback( + ({ set }) => + ( + position: 'first' | 'last', + columnId?: string, + isOpportunity = false, + ): void => { + const columnDefinitionId = getColumnDefinitionId(columnId); + set( + recordBoardNewRecordByColumnIdSelector({ + familyKey: columnDefinitionId, + scopeId: columnDefinitionId, + }), + { + id: '', + columnId: columnDefinitionId, + isCreating: false, + position, + isOpportunity: Boolean(isOpportunity), + company: null, + }, + ); + resetSearchFilter(); + if (isOpportunity === true) { + goBackToPreviousHotkeyScope(); + } + }, + [getColumnDefinitionId, goBackToPreviousHotkeyScope, resetSearchFilter], + ); + + const handleCreate = ( + labelIdentifier: string, + labelValue: string, + position: 'first' | 'last', + onCreateSuccess?: () => void, + ) => { + if (labelValue.trim() !== '' && position !== undefined) { + handleAddNewCardClick( + labelIdentifier, + labelValue.trim(), + position, + false, + '', + ); + onCreateSuccess?.(); + } + }; + + const handleBlur = ( + labelIdentifier: string, + labelValue: string, + position: 'first' | 'last', + onCreateSuccess?: () => void, + ) => { + if (labelValue.trim() === '') { + onCreateSuccess?.(); + } else { + handleCreate(labelIdentifier, labelValue, position, onCreateSuccess); + } }; + const handleInputEnter = ( + labelIdentifier: string, + labelValue: string, + position: 'first' | 'last', + onCreateSuccess?: () => void, + ) => { + handleCreate(labelIdentifier, labelValue, position, onCreateSuccess); + }; + + const handleEntitySelect = useCallback( + ( + position: 'first' | 'last', + company: EntityForSelect, + columnId?: string, + ) => { + const columnDefinitionId = getColumnDefinitionId(columnId); + createRecord('', '', position, true, company); + handleCreateSuccess(position, columnDefinitionId, true); + }, + [createRecord, handleCreateSuccess, getColumnDefinitionId], + ); + return { handleAddNewCardClick, + handleCreateSuccess, + handleCreate, + handleBlur, + handleInputEnter, + handleEntitySelect, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewOpportunity.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewOpportunity.ts deleted file mode 100644 index d5ce6341646d..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewOpportunity.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; -import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; -import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; -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'; -import { useCallback, useContext, useState } from 'react'; - -export const useAddNewOpportunity = (position: string) => { - const [isCreatingCard, setIsCreatingCard] = useState(false); - - const { columnDefinition } = useContext(RecordBoardColumnContext); - const { createOneRecord, selectFieldMetadataItem } = - useContext(RecordBoardContext); - - const { - goBackToPreviousHotkeyScope, - setHotkeyScopeAndMemorizePreviousScope, - } = usePreviousHotkeyScope(); - const { resetSearchFilter } = useEntitySelectSearch({ - relationPickerScopeId: 'relation-picker', - }); - const { isOpportunitiesCompanyFieldDisabled } = - useIsOpportunitiesCompanyFieldDisabled(); - const handleEntitySelect = useCallback( - (company?: EntityForSelect) => { - setIsCreatingCard(false); - goBackToPreviousHotkeyScope(); - resetSearchFilter(); - createOneRecord({ - name: company?.name, - companyId: company?.id, - position: position, - [selectFieldMetadataItem.name]: columnDefinition.value, - }); - }, - [ - columnDefinition, - createOneRecord, - goBackToPreviousHotkeyScope, - resetSearchFilter, - selectFieldMetadataItem, - position, - ], - ); - - const handleAddNewOpportunityClick = useCallback(() => { - if (isOpportunitiesCompanyFieldDisabled) { - handleEntitySelect(); - } else { - setIsCreatingCard(true); - } - setHotkeyScopeAndMemorizePreviousScope( - RelationPickerHotkeyScope.RelationPicker, - ); - }, [ - setHotkeyScopeAndMemorizePreviousScope, - isOpportunitiesCompanyFieldDisabled, - handleEntitySelect, - ]); - - const handleCancel = useCallback(() => { - resetSearchFilter(); - goBackToPreviousHotkeyScope(); - setIsCreatingCard(false); - }, [goBackToPreviousHotkeyScope, resetSearchFilter]); - - return { - isCreatingCard, - handleEntitySelect, - handleAddNewOpportunityClick, - handleCancel, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useColumnNewCardActions.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useColumnNewCardActions.ts new file mode 100644 index 000000000000..eb498d5ae447 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useColumnNewCardActions.ts @@ -0,0 +1,44 @@ +import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; +import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; +import { useRecoilValue } from 'recoil'; + +export const useColumnNewCardActions = (columnId: string) => { + const { visibleFieldDefinitionsState } = useRecordBoardStates(); + const visibleFieldDefinitions = useRecoilValue( + visibleFieldDefinitionsState(), + ); + const labelIdentifierField = visibleFieldDefinitions.find( + (field) => field.isLabelIdentifier, + ); + + const { handleAddNewCardClick, handleCreateSuccess, handleEntitySelect } = + useAddNewCard(); + + const newRecord = useRecoilValue( + recordBoardNewRecordByColumnIdSelector({ + familyKey: columnId, + scopeId: columnId, + }), + ); + + const handleNewButtonClick = ( + position: 'first' | 'last', + isOpportunity: boolean, + ) => { + handleAddNewCardClick( + labelIdentifierField?.label ?? '', + '', + position, + isOpportunity, + columnId, + ); + }; + + return { + newRecord, + handleNewButtonClick, + handleCreateSuccess, + handleEntitySelect, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardNewRecordByColumnIdComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardNewRecordByColumnIdComponentFamilyState.ts new file mode 100644 index 000000000000..e3286f253d60 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardNewRecordByColumnIdComponentFamilyState.ts @@ -0,0 +1,24 @@ +import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; +import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; + +export type NewCard = { + id: string; + columnId: string; + isCreating: boolean; + position: 'first' | 'last'; + isOpportunity: boolean; + company: EntityForSelect | null; +}; + +export const recordBoardNewRecordByColumnIdComponentFamilyState = + createComponentFamilyState<NewCard, string>({ + key: 'recordBoardNewRecordByColumnIdComponentFamilyState', + defaultValue: { + id: '', + columnId: '', + isCreating: false, + position: 'last', + isOpportunity: false, + company: null, + }, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector.ts b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector.ts new file mode 100644 index 000000000000..122daffa3af1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector.ts @@ -0,0 +1,31 @@ +import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector'; +import { + NewCard, + recordBoardNewRecordByColumnIdComponentFamilyState, +} from '../recordBoardNewRecordByColumnIdComponentFamilyState'; + +export const recordBoardNewRecordByColumnIdSelector = + createComponentFamilySelector<NewCard, string>({ + key: 'recordBoardNewRecordByColumnIdSelector', + get: + ({ familyKey, scopeId }: { familyKey: string; scopeId: string }) => + ({ get }) => { + return get( + recordBoardNewRecordByColumnIdComponentFamilyState({ + familyKey, + scopeId, + }), + ) as NewCard; + }, + set: + ({ familyKey, scopeId }: { familyKey: string; scopeId: string }) => + ({ set }, newValue) => { + set( + recordBoardNewRecordByColumnIdComponentFamilyState({ + familyKey, + scopeId, + }), + newValue as NewCard, + ); + }, + }); 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 3a3b49e69f28..07cb30704498 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 @@ -3,16 +3,13 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit import { FieldActorMetadata, FieldFullNameMetadata, - FieldLinkMetadata, FieldRatingMetadata, FieldSelectMetadata, 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<FieldTextMetadata> = { @@ -24,7 +21,16 @@ export const textfieldDefinition: FieldDefinition<FieldTextMetadata> = { 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', ); @@ -60,23 +66,11 @@ export const fullNameFieldDefinition: FieldDefinition<FieldFullNameMetadata> = { }, }; -export const linkFieldDefinition: FieldDefinition<FieldLinkMetadata> = { - fieldMetadataId, - label: 'LinkedIn URL', - iconName: 'url', - type: FieldMetadataType.Link, - defaultValue: { url: '', label: '' }, - metadata: { - fieldName: 'linkedInURL', - placeHolder: 'https://linkedin.com/user', - }, -}; - -const phoneFieldMetadataItem = mockedPersonObjectMetadataItem.fields?.find( - ({ name }) => name === 'phone', +const phonesFieldMetadataItem = mockedPersonObjectMetadataItem.fields?.find( + ({ name }) => name === 'phones', ); -export const phoneFieldDefinition = formatFieldMetadataItemAsFieldDefinition({ - field: phoneFieldMetadataItem!, +export const phonesFieldDefinition = formatFieldMetadataItemAsFieldDefinition({ + field: phonesFieldMetadataItem!, objectMetadataItem: mockedPersonObjectMetadataItem, }); @@ -91,7 +85,15 @@ export const ratingFieldDefinition: FieldDefinition<FieldRatingMetadata> = { }, }; -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/components/FieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx index 781ac98a650f..41740a1a4191 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldDisplay.tsx @@ -13,7 +13,6 @@ import { isFieldIdentifierDisplay } from '@/object-record/record-field/meta-type import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor'; import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray'; import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean'; -import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone'; import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones'; @@ -27,13 +26,10 @@ import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisp import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay'; import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay'; import { DateTimeFieldDisplay } from '../meta-types/display/components/DateTimeFieldDisplay'; -import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay'; import { FullNameFieldDisplay } from '../meta-types/display/components/FullNameFieldDisplay'; import { JsonFieldDisplay } from '../meta-types/display/components/JsonFieldDisplay'; -import { LinkFieldDisplay } from '../meta-types/display/components/LinkFieldDisplay'; import { MultiSelectFieldDisplay } from '../meta-types/display/components/MultiSelectFieldDisplay'; import { NumberFieldDisplay } from '../meta-types/display/components/NumberFieldDisplay'; -import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDisplay'; import { RelationToOneFieldDisplay } from '../meta-types/display/components/RelationToOneFieldDisplay'; import { SelectFieldDisplay } from '../meta-types/display/components/SelectFieldDisplay'; import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay'; @@ -42,12 +38,9 @@ import { isFieldAddress } from '../types/guards/isFieldAddress'; import { isFieldCurrency } from '../types/guards/isFieldCurrency'; import { isFieldDate } from '../types/guards/isFieldDate'; import { isFieldDateTime } from '../types/guards/isFieldDateTime'; -import { isFieldEmail } from '../types/guards/isFieldEmail'; import { isFieldFullName } from '../types/guards/isFieldFullName'; -import { isFieldLink } from '../types/guards/isFieldLink'; import { isFieldMultiSelect } from '../types/guards/isFieldMultiSelect'; import { isFieldNumber } from '../types/guards/isFieldNumber'; -import { isFieldPhone } from '../types/guards/isFieldPhone'; import { isFieldRawJson } from '../types/guards/isFieldRawJson'; import { isFieldSelect } from '../types/guards/isFieldSelect'; import { isFieldText } from '../types/guards/isFieldText'; @@ -67,23 +60,16 @@ export const FieldDisplay = () => { <RelationToOneFieldDisplay /> ) : isFieldRelationFromManyObjects(fieldDefinition) ? ( <RelationFromManyFieldDisplay /> - ) : isFieldPhone(fieldDefinition) || - isFieldDisplayedAsPhone(fieldDefinition) ? ( - <PhoneFieldDisplay /> ) : isFieldText(fieldDefinition) ? ( <TextFieldDisplay /> ) : isFieldUuid(fieldDefinition) ? ( <UuidFieldDisplay /> - ) : isFieldEmail(fieldDefinition) ? ( - <EmailFieldDisplay /> ) : isFieldDateTime(fieldDefinition) ? ( <DateTimeFieldDisplay /> ) : isFieldDate(fieldDefinition) ? ( <DateFieldDisplay /> ) : isFieldNumber(fieldDefinition) ? ( <NumberFieldDisplay /> - ) : isFieldLink(fieldDefinition) ? ( - <LinkFieldDisplay /> ) : isFieldLinks(fieldDefinition) ? ( <LinksFieldDisplay /> ) : isFieldCurrency(fieldDefinition) ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx index 144a24a8b8b0..1f36c34548d8 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldInput.tsx @@ -11,44 +11,37 @@ import { RawJsonFieldInput } from '@/object-record/record-field/meta-types/input import { RelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput'; import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput'; import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope'; -import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; -import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone'; -import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails'; -import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; -import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; -import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect'; import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones'; -import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects'; import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject'; -import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { ArrayFieldInput } from '@/object-record/record-field/meta-types/input/components/ArrayFieldInput'; import { RichTextFieldInput } from '@/object-record/record-field/meta-types/input/components/RichTextFieldInput'; +import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress'; import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray'; +import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean'; +import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; +import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; +import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; +import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails'; +import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; +import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; +import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect'; +import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; +import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating'; +import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson'; import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText'; +import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { FieldContext } from '../contexts/FieldContext'; import { BooleanFieldInput } from '../meta-types/input/components/BooleanFieldInput'; import { CurrencyFieldInput } from '../meta-types/input/components/CurrencyFieldInput'; import { DateTimeFieldInput } from '../meta-types/input/components/DateTimeFieldInput'; -import { EmailFieldInput } from '../meta-types/input/components/EmailFieldInput'; -import { LinkFieldInput } from '../meta-types/input/components/LinkFieldInput'; import { NumberFieldInput } from '../meta-types/input/components/NumberFieldInput'; -import { PhoneFieldInput } from '../meta-types/input/components/PhoneFieldInput'; import { RatingFieldInput } from '../meta-types/input/components/RatingFieldInput'; import { RelationToOneFieldInput } from '../meta-types/input/components/RelationToOneFieldInput'; import { TextFieldInput } from '../meta-types/input/components/TextFieldInput'; import { FieldInputEvent } from '../types/FieldInputEvent'; -import { isFieldAddress } from '../types/guards/isFieldAddress'; -import { isFieldBoolean } from '../types/guards/isFieldBoolean'; -import { isFieldCurrency } from '../types/guards/isFieldCurrency'; -import { isFieldDateTime } from '../types/guards/isFieldDateTime'; -import { isFieldEmail } from '../types/guards/isFieldEmail'; -import { isFieldLink } from '../types/guards/isFieldLink'; -import { isFieldNumber } from '../types/guards/isFieldNumber'; -import { isFieldPhone } from '../types/guards/isFieldPhone'; -import { isFieldRating } from '../types/guards/isFieldRating'; import { isFieldText } from '../types/guards/isFieldText'; type FieldInputProps = { @@ -84,15 +77,6 @@ export const FieldInput = ({ <RelationToOneFieldInput onSubmit={onSubmit} onCancel={onCancel} /> ) : isFieldRelationFromManyObjects(fieldDefinition) ? ( <RelationFromManyFieldInput onSubmit={onSubmit} /> - ) : isFieldPhone(fieldDefinition) || - isFieldDisplayedAsPhone(fieldDefinition) ? ( - <PhoneFieldInput - onEnter={onEnter} - onEscape={onEscape} - onClickOutside={onClickOutside} - onTab={onTab} - onShiftTab={onShiftTab} - /> ) : isFieldPhones(fieldDefinition) ? ( <PhonesFieldInput onCancel={onCancel} /> ) : isFieldText(fieldDefinition) ? ( @@ -103,14 +87,6 @@ export const FieldInput = ({ onTab={onTab} onShiftTab={onShiftTab} /> - ) : isFieldEmail(fieldDefinition) ? ( - <EmailFieldInput - onEnter={onEnter} - onEscape={onEscape} - onClickOutside={onClickOutside} - onTab={onTab} - onShiftTab={onShiftTab} - /> ) : isFieldEmails(fieldDefinition) ? ( <EmailsFieldInput onCancel={onCancel} /> ) : isFieldFullName(fieldDefinition) ? ( @@ -145,14 +121,6 @@ export const FieldInput = ({ onTab={onTab} onShiftTab={onShiftTab} /> - ) : isFieldLink(fieldDefinition) ? ( - <LinkFieldInput - onEnter={onEnter} - onEscape={onEscape} - onClickOutside={onClickOutside} - onTab={onTab} - onShiftTab={onShiftTab} - /> ) : isFieldLinks(fieldDefinition) ? ( <LinksFieldInput onCancel={onCancel} /> ) : isFieldCurrency(fieldDefinition) ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useGetButtonIcon.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useGetButtonIcon.test.tsx index 8237c4ace637..e7c147a580cc 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useGetButtonIcon.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useGetButtonIcon.test.tsx @@ -1,10 +1,10 @@ -import { ReactNode } from 'react'; import { renderHook } from '@testing-library/react'; +import { ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; import { IconPencil } from 'twenty-ui'; import { - phoneFieldDefinition, + phonesFieldDefinition, relationFieldDefinition, } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -29,7 +29,7 @@ const getWrapper = </FieldContext.Provider> ); -const PhoneWrapper = getWrapper(phoneFieldDefinition); +const PhoneWrapper = getWrapper(phonesFieldDefinition); const RelationWrapper = getWrapper(relationFieldDefinition); describe('useGetButtonIcon', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldEmpty.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldEmpty.test.tsx index 02881a761474..0e32c1da8731 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldEmpty.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldEmpty.test.tsx @@ -1,8 +1,8 @@ -import { ReactNode } from 'react'; import { act, renderHook } from '@testing-library/react'; +import { ReactNode } from 'react'; import { RecoilRoot, useSetRecoilState } from 'recoil'; -import { phoneFieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; +import { phonesFieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -12,7 +12,7 @@ const recordId = 'recordId'; const Wrapper = ({ children }: { children: ReactNode }) => ( <FieldContext.Provider value={{ - fieldDefinition: phoneFieldDefinition, + fieldDefinition: phonesFieldDefinition, recordId, hotkeyScope: 'hotkeyScope', isLabelIdentifier: false, diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldInputOnly.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldInputOnly.test.tsx index 50e39ac55cd9..0d9d62368f3d 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldInputOnly.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldInputOnly.test.tsx @@ -3,7 +3,7 @@ import { ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; import { - phoneFieldDefinition, + phonesFieldDefinition, ratingFieldDefinition, } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -29,7 +29,7 @@ const getWrapper = ); const RatingWrapper = getWrapper(ratingFieldDefinition); -const PhoneWrapper = getWrapper(phoneFieldDefinition); +const PhoneWrapper = getWrapper(phonesFieldDefinition); describe('useIsFieldInputOnly', () => { it('should return true', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldReadOnly.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldReadOnly.test.tsx index 2baa6c2d34d8..f51cf795f861 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldReadOnly.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useIsFieldReadOnly.test.tsx @@ -4,7 +4,7 @@ import { RecoilRoot } from 'recoil'; import { actorFieldDefinition, - phoneFieldDefinition, + phonesFieldDefinition, } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly'; @@ -29,7 +29,7 @@ const getWrapper = ); const ActorWrapper = getWrapper(actorFieldDefinition); -const PhoneWrapper = getWrapper(phoneFieldDefinition); +const PhoneWrapper = getWrapper(phonesFieldDefinition); describe('useIsFieldReadOnly', () => { it('should return true', () => { 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 6583c23d9d88..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,14 +1,14 @@ 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 { - phoneFieldDefinition, + phonesFieldDefinition, relationFieldDefinition, } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { @@ -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} } } `; @@ -33,7 +34,16 @@ const mocks: MockedResponse[] = [ { request: { query, - variables: { idToUpdate: 'recordId', input: { phone: '+1 123 456' } }, + variables: { + idToUpdate: 'recordId', + input: { + phones: { + primaryPhoneNumber: '123 456', + primaryPhoneCountryCode: '+1', + additionalPhones: [], + }, + }, + }, }, result: jest.fn(() => ({ data: { @@ -63,6 +73,10 @@ const mocks: MockedResponse[] = [ const recordId = 'recordId'; +const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + const getWrapper = (fieldDefinition: FieldDefinition<FieldMetadata>) => ({ children }: { children: ReactNode }) => { @@ -82,7 +96,7 @@ const getWrapper = }; return ( - <MockedProvider mocks={mocks} addTypename={false}> + <JestMetadataAndApolloMocksWrapper> <FieldContext.Provider value={{ fieldDefinition, @@ -92,13 +106,13 @@ const getWrapper = useUpdateRecord: useUpdateOneRecordMutation, }} > - <RecoilRoot>{children}</RecoilRoot> + {children} </FieldContext.Provider> - </MockedProvider> + </JestMetadataAndApolloMocksWrapper> ); }; -const PhoneWrapper = getWrapper(phoneFieldDefinition); +const PhoneWrapper = getWrapper(phonesFieldDefinition); const RelationWrapper = getWrapper(relationFieldDefinition); describe('usePersistField', () => { @@ -118,7 +132,11 @@ describe('usePersistField', () => { ); act(() => { - result.current.persistField('+1 123 456'); + result.current.persistField({ + primaryPhoneNumber: '123 456', + primaryPhoneCountryCode: '+1', + additionalPhones: [], + }); }); await waitFor(() => { 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 4747c76bef90..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,57 +27,269 @@ const mocks: MockedResponse[] = [ ) { updateCompany(id: $idToUpdate, data: $input) { __typename - id - visaSponsorship + 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 + } + } + } + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } + createdAt createdBy { source workspaceMemberId name } + 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 secondaryLinks } - position - annualRecurringRevenue { - amountMicros - currencyCode - } - employees linkedinLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - workPolicy - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } 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 + 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 } - myCustomField - createdAt - accountOwnerId - tagline - idealCustomerProfile } } `, @@ -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 ( - <MockedProvider mocks={mocks} addTypename={false}> + <JestMetadataAndApolloMocksWrapper> <FieldContext.Provider value={{ fieldDefinition: booleanFieldDefinition, @@ -122,9 +344,9 @@ const Wrapper = ({ children }: { children: ReactNode }) => { useUpdateRecord: useUpdateOneRecordMutation, }} > - <RecoilRoot>{children}</RecoilRoot> + {children} </FieldContext.Provider> - </MockedProvider> + </JestMetadataAndApolloMocksWrapper> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts index e4e4970c0b0d..48bb034244ea 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts @@ -3,14 +3,16 @@ import { useContext } from 'react'; import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor'; import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText'; import { FieldContext } from '../contexts/FieldContext'; +import { isFieldMetadataReadOnly } from '../utils/isFieldMetadataReadOnly'; export const useIsFieldReadOnly = () => { const { fieldDefinition } = useContext(FieldContext); + const { metadata } = fieldDefinition; + return ( - fieldDefinition.metadata.fieldName === 'noteTargets' || - fieldDefinition.metadata.fieldName === 'taskTargets' || isFieldActor(fieldDefinition) || - isFieldRichText(fieldDefinition) + isFieldRichText(fieldDefinition) || + isFieldMetadataReadOnly(metadata) ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts index 0aa155a868c9..d12e1b73bbb8 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/usePersistField.ts @@ -35,14 +35,8 @@ import { isFieldCurrency } from '../types/guards/isFieldCurrency'; import { isFieldCurrencyValue } from '../types/guards/isFieldCurrencyValue'; import { isFieldDateTime } from '../types/guards/isFieldDateTime'; import { isFieldDateTimeValue } from '../types/guards/isFieldDateTimeValue'; -import { isFieldEmail } from '../types/guards/isFieldEmail'; -import { isFieldEmailValue } from '../types/guards/isFieldEmailValue'; -import { isFieldLink } from '../types/guards/isFieldLink'; -import { isFieldLinkValue } from '../types/guards/isFieldLinkValue'; import { isFieldNumber } from '../types/guards/isFieldNumber'; import { isFieldNumberValue } from '../types/guards/isFieldNumberValue'; -import { isFieldPhone } from '../types/guards/isFieldPhone'; -import { isFieldPhoneValue } from '../types/guards/isFieldPhoneValue'; import { isFieldRating } from '../types/guards/isFieldRating'; import { isFieldRatingValue } from '../types/guards/isFieldRatingValue'; import { isFieldText } from '../types/guards/isFieldText'; @@ -68,9 +62,6 @@ export const usePersistField = () => { const fieldIsText = isFieldText(fieldDefinition) && isFieldTextValue(valueToPersist); - const fieldIsEmail = - isFieldEmail(fieldDefinition) && isFieldEmailValue(valueToPersist); - const fieldIsEmails = isFieldEmails(fieldDefinition) && isFieldEmailsValue(valueToPersist); @@ -81,9 +72,6 @@ export const usePersistField = () => { const fieldIsDate = isFieldDate(fieldDefinition) && isFieldDateValue(valueToPersist); - const fieldIsLink = - isFieldLink(fieldDefinition) && isFieldLinkValue(valueToPersist); - const fieldIsLinks = isFieldLinks(fieldDefinition) && isFieldLinksValue(valueToPersist); @@ -105,9 +93,6 @@ export const usePersistField = () => { isFieldFullName(fieldDefinition) && isFieldFullNameValue(valueToPersist); - const fieldIsPhone = - isFieldPhone(fieldDefinition) && isFieldPhoneValue(valueToPersist); - const fieldIsPhones = isFieldPhones(fieldDefinition) && isFieldPhonesValue(valueToPersist); @@ -133,15 +118,12 @@ export const usePersistField = () => { fieldIsRelationToOneObject || fieldIsText || fieldIsBoolean || - fieldIsEmail || fieldIsEmails || fieldIsRating || fieldIsNumber || fieldIsDateTime || fieldIsDate || - fieldIsPhone || fieldIsPhones || - fieldIsLink || fieldIsLinks || fieldIsCurrency || fieldIsFullName || diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx index 05b88fd267ec..86039881b0e4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateFieldDisplay.tsx @@ -2,7 +2,15 @@ import { useDateFieldDisplay } from '@/object-record/record-field/meta-types/hoo import { DateDisplay } from '@/ui/field/display/components/DateDisplay'; export const DateFieldDisplay = () => { - const { fieldValue } = useDateFieldDisplay(); + const { fieldValue, fieldDefinition } = useDateFieldDisplay(); - return <DateDisplay value={fieldValue} />; + const displayAsRelativeDate = + fieldDefinition.metadata?.settings?.displayAsRelativeDate; + + return ( + <DateDisplay + value={fieldValue} + displayAsRelativeDate={displayAsRelativeDate} + /> + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx index 03ffc92d395b..9d67dff920d3 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay.tsx @@ -2,7 +2,15 @@ import { useDateTimeFieldDisplay } from '@/object-record/record-field/meta-types import { DateTimeDisplay } from '@/ui/field/display/components/DateTimeDisplay'; export const DateTimeFieldDisplay = () => { - const { fieldValue } = useDateTimeFieldDisplay(); + const { fieldValue, fieldDefinition } = useDateTimeFieldDisplay(); - return <DateTimeDisplay value={fieldValue} />; + const displayAsRelativeDate = + fieldDefinition.metadata?.settings?.displayAsRelativeDate; + + return ( + <DateTimeDisplay + value={fieldValue} + displayAsRelativeDate={displayAsRelativeDate} + /> + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/EmailFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/EmailFieldDisplay.tsx deleted file mode 100644 index 60a7caa6e32a..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/EmailFieldDisplay.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useEmailFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useEmailFieldDisplay'; -import { EmailDisplay } from '@/ui/field/display/components/EmailDisplay'; - -export const EmailFieldDisplay = () => { - const { fieldValue } = useEmailFieldDisplay(); - - return <EmailDisplay value={fieldValue} />; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/LinkFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/LinkFieldDisplay.tsx deleted file mode 100644 index bb1989852f4c..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/LinkFieldDisplay.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useLinkFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useLinkFieldDisplay'; -import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay'; - -export const LinkFieldDisplay = () => { - const { fieldValue } = useLinkFieldDisplay(); - - return <LinkDisplay value={fieldValue} />; -}; 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 <NumberDisplay value={fieldValue} />; + const { fieldValue, fieldDefinition } = useNumberFieldDisplay(); + return ( + <NumberDisplay + value={fieldValue} + decimals={fieldDefinition.settings?.decimals} + /> + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/PhoneFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/PhoneFieldDisplay.tsx deleted file mode 100644 index acfd141a3240..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/PhoneFieldDisplay.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { usePhoneFieldDisplay } from '@/object-record/record-field/meta-types/hooks/usePhoneFieldDisplay'; -import { PhoneDisplay } from '@/ui/field/display/components/PhoneDisplay'; - -export const PhoneFieldDisplay = () => { - const { fieldValue } = usePhoneFieldDisplay(); - - return <PhoneDisplay value={fieldValue} />; -}; 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<typeof EmailFieldDisplay>; +type Story = StoryObj<typeof EmailsFieldDisplay>; 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<typeof JsonFieldDisplay>; - -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<typeof PhoneFieldDisplay>; +type Story = StoryObj<typeof PhonesFieldDisplay>; 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/useDateFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateFieldDisplay.ts index 3f7e5407f64c..2e400704d9bb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateFieldDisplay.ts @@ -2,6 +2,8 @@ import { useContext } from 'react'; import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; +import { FieldDateMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldContext } from '../../contexts/FieldContext'; export const useDateFieldDisplay = () => { @@ -16,7 +18,10 @@ export const useDateFieldDisplay = () => { ); return { - fieldDefinition, + // TODO: we have to use this because we removed the assertion that would have otherwise narrowed the type because + // it impacts performance. We should find a way to assert the type in a way that doesn't impact performance. + // Maybe a level above ? + fieldDefinition: fieldDefinition as FieldDefinition<FieldDateMetadata>, fieldValue, hotkeyScope, clearable, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateTimeFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateTimeFieldDisplay.ts index bc41e36a47f0..412271428fa4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateTimeFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useDateTimeFieldDisplay.ts @@ -2,6 +2,8 @@ import { useContext } from 'react'; import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; +import { FieldDateTimeMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldContext } from '../../contexts/FieldContext'; export const useDateTimeFieldDisplay = () => { @@ -16,7 +18,7 @@ export const useDateTimeFieldDisplay = () => { ); return { - fieldDefinition, + fieldDefinition: fieldDefinition as FieldDefinition<FieldDateTimeMetadata>, fieldValue, hotkeyScope, clearable, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useEmailField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useEmailField.ts deleted file mode 100644 index a12e4a54f609..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useEmailField.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; - -import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; -import { FieldEmailValue } from '@/object-record/record-field/types/FieldMetadata'; -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -import { FieldContext } from '../../contexts/FieldContext'; -import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; -import { isFieldEmail } from '../../types/guards/isFieldEmail'; - -export const useEmailField = () => { - const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext); - - assertFieldMetadata(FieldMetadataType.Email, isFieldEmail, fieldDefinition); - - const fieldName = fieldDefinition.metadata.fieldName; - - const [fieldValue, setFieldValue] = useRecoilState<string>( - recordStoreFamilySelector({ - recordId, - fieldName: fieldName, - }), - ); - - const { setDraftValue, getDraftValueSelector } = - useRecordFieldInput<FieldEmailValue>(`${recordId}-${fieldName}`); - - const draftValue = useRecoilValue(getDraftValueSelector()); - - return { - fieldDefinition, - draftValue, - setDraftValue, - fieldValue, - setFieldValue, - hotkeyScope, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useEmailFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useEmailFieldDisplay.ts deleted file mode 100644 index cb7684a4f493..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useEmailFieldDisplay.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useContext } from 'react'; - -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - -import { FieldContext } from '../../contexts/FieldContext'; - -export const useEmailFieldDisplay = () => { - const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext); - - const fieldName = fieldDefinition.metadata.fieldName; - - const fieldValue = useRecordFieldValue<string>(recordId, fieldName); - - return { - fieldDefinition, - fieldValue, - hotkeyScope, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useLinkField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useLinkField.ts deleted file mode 100644 index f1b5ed7e6a30..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useLinkField.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { useContext } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; - -import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -import { FieldContext } from '../../contexts/FieldContext'; -import { usePersistField } from '../../hooks/usePersistField'; -import { FieldLinkValue } from '../../types/FieldMetadata'; -import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; -import { isFieldLink } from '../../types/guards/isFieldLink'; -import { isFieldLinkValue } from '../../types/guards/isFieldLinkValue'; - -export const useLinkField = () => { - const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext); - - assertFieldMetadata(FieldMetadataType.Link, isFieldLink, fieldDefinition); - - const fieldName = fieldDefinition.metadata.fieldName; - - const [fieldValue, setFieldValue] = useRecoilState<FieldLinkValue>( - recordStoreFamilySelector({ - recordId, - fieldName: fieldName, - }), - ); - - const { setDraftValue, getDraftValueSelector } = - useRecordFieldInput<FieldLinkValue>(`${recordId}-${fieldName}`); - - const draftValue = useRecoilValue(getDraftValueSelector()); - - const persistField = usePersistField(); - - const persistLinkField = (newValue: FieldLinkValue) => { - if (!isFieldLinkValue(newValue)) { - return; - } - - persistField(newValue); - }; - - return { - fieldDefinition, - fieldValue, - draftValue, - setDraftValue, - setFieldValue, - hotkeyScope, - persistLinkField, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useLinkFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useLinkFieldDisplay.ts deleted file mode 100644 index 570857ffae10..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useLinkFieldDisplay.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useContext } from 'react'; - -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - -import { FieldContext } from '../../contexts/FieldContext'; -import { FieldLinkValue } from '../../types/FieldMetadata'; - -export const useLinkFieldDisplay = () => { - const { recordId, fieldDefinition } = useContext(FieldContext); - - const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue<FieldLinkValue | undefined>( - recordId, - fieldName, - ); - - return { - fieldDefinition, - fieldValue, - }; -}; 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/hooks/usePhoneField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/usePhoneField.ts deleted file mode 100644 index 2e20254c82b7..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/usePhoneField.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useContext } from 'react'; -import { isPossiblePhoneNumber } from 'libphonenumber-js'; -import { useRecoilState, useRecoilValue } from 'recoil'; - -import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; -import { FieldPhoneValue } from '@/object-record/record-field/types/FieldMetadata'; -import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone'; -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -import { FieldContext } from '../../contexts/FieldContext'; -import { usePersistField } from '../../hooks/usePersistField'; -import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata'; -import { isFieldPhone } from '../../types/guards/isFieldPhone'; - -export const usePhoneField = () => { - const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext); - - try { - // TODO: temporary - remove when 'Phone' field in 'Person' object - // is migrated to use FieldMetadataType.Phone as type. - assertFieldMetadata( - FieldMetadataType.Text, - isFieldDisplayedAsPhone, - fieldDefinition, - ); - } catch { - assertFieldMetadata(FieldMetadataType.Phone, isFieldPhone, fieldDefinition); - } - - const fieldName = fieldDefinition.metadata.fieldName; - - const [fieldValue, setFieldValue] = useRecoilState<string>( - recordStoreFamilySelector({ - recordId, - fieldName: fieldName, - }), - ); - - const persistField = usePersistField(); - - const persistPhoneField = (newPhoneValue: string) => { - if (!isPossiblePhoneNumber(newPhoneValue) && newPhoneValue !== '') return; - - persistField(newPhoneValue); - }; - const { setDraftValue, getDraftValueSelector } = - useRecordFieldInput<FieldPhoneValue>(`${recordId}-${fieldName}`); - - const draftValue = useRecoilValue(getDraftValueSelector()); - - return { - fieldDefinition, - fieldValue, - setFieldValue, - draftValue, - setDraftValue, - hotkeyScope, - persistPhoneField, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/usePhoneFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/usePhoneFieldDisplay.ts deleted file mode 100644 index 3ba7d4f1b25c..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/usePhoneFieldDisplay.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useContext } from 'react'; - -import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; - -import { FieldContext } from '../../contexts/FieldContext'; - -export const usePhoneFieldDisplay = () => { - const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext); - - const fieldName = fieldDefinition.metadata.fieldName; - - const fieldValue = useRecordFieldValue<string>(recordId, fieldName); - - return { - fieldDefinition, - fieldValue, - hotkeyScope, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx index 811c96987a1d..89a4e054e217 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx @@ -7,6 +7,7 @@ import { CurrencyInput } from '@/ui/field/input/components/CurrencyInput'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { useCurrencyField } from '../../hooks/useCurrencyField'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { FieldInputEvent } from './DateTimeFieldInput'; type CurrencyFieldInputProps = { @@ -108,7 +109,7 @@ export const CurrencyFieldInput = ({ const handleSelect = (newValue: string) => { setDraftValue({ - amount: draftValue?.amount ?? '', + amount: isUndefinedOrNull(draftValue?.amount) ? '' : draftValue?.amount, currencyCode: newValue as CurrencyCode, }); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx deleted file mode 100644 index 29ed4b2f15c6..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailFieldInput.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { TextInput } from '@/ui/field/input/components/TextInput'; - -import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; -import { usePersistField } from '../../../hooks/usePersistField'; -import { useEmailField } from '../../hooks/useEmailField'; - -import { FieldInputEvent } from './DateTimeFieldInput'; - -export type EmailFieldInputProps = { - onClickOutside?: FieldInputEvent; - onEnter?: FieldInputEvent; - onEscape?: FieldInputEvent; - onTab?: FieldInputEvent; - onShiftTab?: FieldInputEvent; -}; - -export const EmailFieldInput = ({ - onEnter, - onEscape, - onClickOutside, - onTab, - onShiftTab, -}: EmailFieldInputProps) => { - const { fieldDefinition, draftValue, setDraftValue, hotkeyScope } = - useEmailField(); - - const persistField = usePersistField(); - - const handleEnter = (newText: string) => { - onEnter?.(() => persistField(newText)); - }; - - const handleEscape = (newText: string) => { - onEscape?.(() => persistField(newText)); - }; - - const handleClickOutside = ( - event: MouseEvent | TouchEvent, - newText: string, - ) => { - onClickOutside?.(() => persistField(newText)); - }; - - const handleTab = (newText: string) => { - onTab?.(() => persistField(newText)); - }; - - const handleShiftTab = (newText: string) => { - onShiftTab?.(() => persistField(newText)); - }; - - const handleChange = (newText: string) => { - setDraftValue(newText); - }; - - return ( - <FieldInputOverlay> - <TextInput - placeholder={fieldDefinition.metadata.placeHolder} - autoFocus - value={draftValue ?? ''} - onClickOutside={handleClickOutside} - onEnter={handleEnter} - onEscape={handleEscape} - onShiftTab={handleShiftTab} - onTab={handleTab} - hotkeyScope={hotkeyScope} - onChange={handleChange} - /> - </FieldInputOverlay> - ); -}; 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 02ce963192b0..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,16 @@ 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 ( <MultiItemFieldInput items={emails} @@ -36,6 +47,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => { onCancel={onCancel} placeholder="Email" fieldMetadataType={FieldMetadataType.Emails} + validateInput={validateInput} renderItem={({ value: email, index, @@ -46,7 +58,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => { <EmailsFieldMenuItem key={index} dropdownId={`${hotkeyScope}-emails-${index}`} - isPrimary={index === 0} + isPrimary={isPrimaryEmail(index)} email={email} onEdit={handleEdit} onSetAsPrimary={handleSetPrimary} diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx deleted file mode 100644 index 758c95fb11a3..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinkFieldInput.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { TextInput } from '@/ui/field/input/components/TextInput'; - -import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; -import { useLinkField } from '../../hooks/useLinkField'; - -import { FieldInputEvent } from './DateTimeFieldInput'; - -type LinkFieldInputProps = { - onClickOutside?: FieldInputEvent; - onEnter?: FieldInputEvent; - onEscape?: FieldInputEvent; - onTab?: FieldInputEvent; - onShiftTab?: FieldInputEvent; -}; - -export const LinkFieldInput = ({ - onEnter, - onEscape, - onClickOutside, - onTab, - onShiftTab, -}: LinkFieldInputProps) => { - const { draftValue, setDraftValue, hotkeyScope, persistLinkField } = - useLinkField(); - - const handleEnter = (newURL: string) => { - onEnter?.(() => - persistLinkField({ - url: newURL, - label: '', - }), - ); - }; - - const handleEscape = (newURL: string) => { - onEscape?.(() => - persistLinkField({ - url: newURL, - label: '', - }), - ); - }; - - const handleClickOutside = ( - event: MouseEvent | TouchEvent, - newURL: string, - ) => { - onClickOutside?.(() => - persistLinkField({ - url: newURL, - label: '', - }), - ); - }; - - const handleTab = (newURL: string) => { - onTab?.(() => - persistLinkField({ - url: newURL, - label: '', - }), - ); - }; - - const handleShiftTab = (newURL: string) => { - onShiftTab?.(() => - persistLinkField({ - url: newURL, - label: '', - }), - ); - }; - - const handleChange = (newURL: string) => { - setDraftValue({ - url: newURL, - label: '', - }); - }; - - return ( - <FieldInputOverlay> - <TextInput - value={draftValue?.url ?? ''} - autoFocus - placeholder="URL" - hotkeyScope={hotkeyScope} - onClickOutside={handleClickOutside} - onEnter={handleEnter} - onEscape={handleEscape} - onTab={handleTab} - onShiftTab={handleShiftTab} - onChange={handleChange} - /> - </FieldInputOverlay> - ); -}; 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 c205039450f0..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 @@ -42,6 +42,8 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => { }); }; + const isPrimaryLink = (index: number) => index === 0 && links?.length > 1; + return ( <MultiItemFieldInput items={links} @@ -49,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, @@ -61,7 +66,7 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => { <LinksFieldMenuItem key={index} dropdownId={`${hotkeyScope}-links-${index}`} - isPrimary={index === 0} + isPrimary={isPrimaryLink(index)} label={link.label} onEdit={handleEdit} onSetAsPrimary={handleSetPrimary} 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<T> = { 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 = <T,>({ 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 = <T,>({ }; 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 = <T,>({ placeholder={placeholder} value={inputValue} hotkeyScope={hotkeyScope} + hasError={!errorData.isValid} renderInput={ renderInput ? (props) => @@ -170,7 +190,7 @@ export const MultiItemFieldInput = <T,>({ }) : undefined } - onChange={(event) => setInputValue(event.target.value)} + onChange={(event) => handleOnChange(event.target.value)} onEnter={handleSubmitInput} rightComponent={ <LightIconButton diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx deleted file mode 100644 index 179455da6a0e..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhoneFieldInput.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { PhoneInput } from '@/ui/field/input/components/PhoneInput'; - -import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; -import { usePhoneField } from '../../hooks/usePhoneField'; - -import { FieldInputEvent } from './DateTimeFieldInput'; - -export type PhoneFieldInputProps = { - onClickOutside?: FieldInputEvent; - onEnter?: FieldInputEvent; - onEscape?: FieldInputEvent; - onTab?: FieldInputEvent; - onShiftTab?: FieldInputEvent; -}; - -export const PhoneFieldInput = ({ - onEnter, - onEscape, - onClickOutside, - onTab, - onShiftTab, -}: PhoneFieldInputProps) => { - const { - fieldDefinition, - draftValue, - setDraftValue, - hotkeyScope, - persistPhoneField, - } = usePhoneField(); - - const handleEnter = (newText: string) => { - onEnter?.(() => persistPhoneField(newText)); - }; - - const handleEscape = (newText: string) => { - onEscape?.(() => persistPhoneField(newText)); - }; - - const handleClickOutside = ( - event: MouseEvent | TouchEvent, - newText: string, - ) => { - onClickOutside?.(() => persistPhoneField(newText)); - }; - - const handleTab = (newText: string) => { - onTab?.(() => persistPhoneField(newText)); - }; - - const handleShiftTab = (newText: string) => { - onShiftTab?.(() => persistPhoneField(newText)); - }; - - const handleChange = (newText: string) => { - setDraftValue(newText); - }; - - return ( - <FieldInputOverlay> - <PhoneInput - placeholder={fieldDefinition.metadata.placeHolder} - autoFocus - value={draftValue ?? ''} - onClickOutside={handleClickOutside} - onEnter={handleEnter} - onEscape={handleEscape} - onShiftTab={handleShiftTab} - onTab={handleTab} - hotkeyScope={hotkeyScope} - onChange={handleChange} - /> - </FieldInputOverlay> - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhonesFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhonesFieldInput.tsx index 6667acf2031f..54fb7ab8ded9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhonesFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/PhonesFieldInput.tsx @@ -78,6 +78,8 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => { }); }; + const isPrimaryPhone = (index: number) => index === 0 && phones?.length > 1; + return ( <MultiItemFieldInput items={phones} @@ -108,7 +110,7 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => { <PhonesFieldMenuItem key={index} dropdownId={`${hotkeyScope}-phones-${index}`} - isPrimary={index === 0} + isPrimary={isPrimaryPhone(index)} phone={phone} onEdit={handleEdit} onSetAsPrimary={handleSetPrimary} diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx deleted file mode 100644 index b8154f0545de..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailFieldInput.stories.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useEffect } from 'react'; -import { Decorator, Meta, StoryObj } from '@storybook/react'; -import { expect, fn, userEvent, waitFor, within } from '@storybook/test'; - -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { FieldMetadataType } from '~/generated/graphql'; -import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; - -import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; -import { useEmailField } from '../../../hooks/useEmailField'; -import { EmailFieldInput, EmailFieldInputProps } from '../EmailFieldInput'; - -const EmailFieldValueSetterEffect = ({ value }: { value: string }) => { - const { setFieldValue } = useEmailField(); - - useEffect(() => { - setFieldValue(value); - }, [setFieldValue, value]); - - return <></>; -}; - -type EmailFieldInputWithContextProps = EmailFieldInputProps & { - value: string; - recordId?: string; -}; - -const EmailFieldInputWithContext = ({ - recordId, - value, - onEnter, - onEscape, - onClickOutside, - onTab, - onShiftTab, -}: EmailFieldInputWithContextProps) => { - const setHotKeyScope = useSetHotkeyScope(); - - useEffect(() => { - setHotKeyScope('hotkey-scope'); - }, [setHotKeyScope]); - - return ( - <div> - <FieldContextProvider - fieldDefinition={{ - fieldMetadataId: 'email', - label: 'Email', - type: FieldMetadataType.Email, - iconName: 'IconLink', - metadata: { - fieldName: 'email', - placeHolder: 'username@email.com', - objectMetadataNameSingular: 'person', - }, - }} - recordId={recordId} - > - <EmailFieldValueSetterEffect value={value} /> - <EmailFieldInput - onEnter={onEnter} - onEscape={onEscape} - onClickOutside={onClickOutside} - onTab={onTab} - onShiftTab={onShiftTab} - /> - </FieldContextProvider> - <div data-testid="data-field-input-click-outside-div" /> - </div> - ); -}; - -const enterJestFn = fn(); -const escapeJestfn = fn(); -const clickOutsideJestFn = fn(); -const tabJestFn = fn(); -const shiftTabJestFn = fn(); - -const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks === true) { - enterJestFn.mockClear(); - escapeJestfn.mockClear(); - clickOutsideJestFn.mockClear(); - tabJestFn.mockClear(); - shiftTabJestFn.mockClear(); - } - return <Story />; -}; - -const meta: Meta = { - title: 'UI/Data/Field/Input/EmailFieldInput', - component: EmailFieldInputWithContext, - args: { - value: 'username@email.com', - onEnter: enterJestFn, - onEscape: escapeJestfn, - onClickOutside: clickOutsideJestFn, - onTab: tabJestFn, - onShiftTab: shiftTabJestFn, - }, - argTypes: { - onEnter: { control: false }, - onEscape: { control: false }, - onClickOutside: { control: false }, - onTab: { control: false }, - onShiftTab: { control: false }, - }, - decorators: [clearMocksDecorator, SnackBarDecorator], - parameters: { - clearMocks: true, - }, -}; - -export default meta; - -type Story = StoryObj<typeof EmailFieldInputWithContext>; - -export const Default: Story = {}; - -export const Enter: Story = { - play: async () => { - expect(enterJestFn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{enter}'); - expect(enterJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const Escape: Story = { - play: async () => { - expect(escapeJestfn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{esc}'); - expect(escapeJestfn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const ClickOutside: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - expect(clickOutsideJestFn).toHaveBeenCalledTimes(0); - - const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div'); - - await waitFor(() => { - userEvent.click(emptyDiv); - expect(clickOutsideJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const Tab: Story = { - play: async () => { - expect(tabJestFn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{tab}'); - expect(tabJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const ShiftTab: Story = { - play: async () => { - expect(shiftTabJestFn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{shift>}{tab}'); - expect(shiftTabJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx deleted file mode 100644 index 6300d789bdf6..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhoneFieldInput.stories.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { useEffect } from 'react'; -import { Decorator, Meta, StoryObj } from '@storybook/react'; -import { expect, fn, userEvent, waitFor, within } from '@storybook/test'; - -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { FieldMetadataType } from '~/generated/graphql'; -import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; - -import { FieldContextProvider } from '../../../__stories__/FieldContextProvider'; -import { usePhoneField } from '../../../hooks/usePhoneField'; -import { PhoneFieldInput, PhoneFieldInputProps } from '../PhoneFieldInput'; - -const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => { - const { setFieldValue } = usePhoneField(); - - useEffect(() => { - setFieldValue(value); - }, [setFieldValue, value]); - - return <></>; -}; - -type PhoneFieldInputWithContextProps = PhoneFieldInputProps & { - value: string; - recordId?: string; -}; - -const PhoneFieldInputWithContext = ({ - recordId, - value, - onEnter, - onEscape, - onClickOutside, - onTab, - onShiftTab, -}: PhoneFieldInputWithContextProps) => { - const setHotKeyScope = useSetHotkeyScope(); - - useEffect(() => { - setHotKeyScope('hotkey-scope'); - }, [setHotKeyScope]); - - return ( - <div> - <FieldContextProvider - fieldDefinition={{ - fieldMetadataId: 'phone', - label: 'Phone', - type: FieldMetadataType.Phone, - iconName: 'IconPhone', - metadata: { - fieldName: 'phone', - placeHolder: 'Enter phone number', - objectMetadataNameSingular: 'person', - }, - }} - recordId={recordId} - > - <PhoneFieldValueSetterEffect value={value} /> - <PhoneFieldInput - onEnter={onEnter} - onEscape={onEscape} - onClickOutside={onClickOutside} - onTab={onTab} - onShiftTab={onShiftTab} - /> - </FieldContextProvider> - <div data-testid="data-field-input-click-outside-div" /> - </div> - ); -}; - -const enterJestFn = fn(); -const escapeJestfn = fn(); -const clickOutsideJestFn = fn(); -const tabJestFn = fn(); -const shiftTabJestFn = fn(); - -const clearMocksDecorator: Decorator = (Story, context) => { - if (context.parameters.clearMocks === true) { - enterJestFn.mockClear(); - escapeJestfn.mockClear(); - clickOutsideJestFn.mockClear(); - tabJestFn.mockClear(); - shiftTabJestFn.mockClear(); - } - return <Story />; -}; - -const meta: Meta = { - title: 'UI/Data/Field/Input/PhoneFieldInput', - component: PhoneFieldInputWithContext, - args: { - value: '+1-12-123-456', - isPositive: true, - onEnter: enterJestFn, - onEscape: escapeJestfn, - onClickOutside: clickOutsideJestFn, - onTab: tabJestFn, - onShiftTab: shiftTabJestFn, - }, - argTypes: { - onEnter: { control: false }, - onEscape: { control: false }, - onClickOutside: { control: false }, - onTab: { control: false }, - onShiftTab: { control: false }, - }, - decorators: [clearMocksDecorator, SnackBarDecorator], - parameters: { - clearMocks: true, - }, -}; - -export default meta; - -type Story = StoryObj<typeof PhoneFieldInputWithContext>; - -export const Default: Story = {}; - -export const Enter: Story = { - play: async () => { - expect(enterJestFn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{enter}'); - expect(enterJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const Escape: Story = { - play: async () => { - expect(escapeJestfn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{esc}'); - expect(escapeJestfn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const ClickOutside: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - expect(clickOutsideJestFn).toHaveBeenCalledTimes(0); - - const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div'); - - await waitFor(() => { - userEvent.click(emptyDiv); - expect(clickOutsideJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const Tab: Story = { - play: async () => { - expect(tabJestFn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{tab}'); - expect(tabJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; - -export const ShiftTab: Story = { - play: async () => { - expect(shiftTabJestFn).toHaveBeenCalledTimes(0); - - await waitFor(() => { - userEvent.keyboard('{shift>}{tab}'); - expect(shiftTabJestFn).toHaveBeenCalledTimes(1); - }); - }, -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/CurrencyCode.ts b/packages/twenty-front/src/modules/object-record/record-field/types/CurrencyCode.ts index 9e86b3c87939..fcc4c3c3267a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/CurrencyCode.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/CurrencyCode.ts @@ -1,20 +1,154 @@ export enum CurrencyCode { + AED = 'AED', + AFN = 'AFN', + ALL = 'ALL', + AMD = 'AMD', + ANG = 'ANG', + AOA = 'AOA', + ARS = 'ARS', + AUD = 'AUD', + AWG = 'AWG', + AZN = 'AZN', + BAM = 'BAM', + BBD = 'BBD', + BDT = 'BDT', + BGN = 'BGN', + BHD = 'BHD', + BIF = 'BIF', + BMD = 'BMD', + BND = 'BND', + BOB = 'BOB', + BRL = 'BRL', + BSD = 'BSD', + BTN = 'BTN', + BWP = 'BWP', + BYN = 'BYN', + BZD = 'BZD', CAD = 'CAD', + CDF = 'CDF', CHF = 'CHF', + CLP = 'CLP', CNY = 'CNY', + COP = 'COP', + CRC = 'CRC', + CUP = 'CUP', + CVE = 'CVE', CZK = 'CZK', + DJF = 'DJF', + DKK = 'DKK', + DOP = 'DOP', + DZD = 'DZD', + EGP = 'EGP', + ERN = 'ERN', + ETB = 'ETB', EUR = 'EUR', + FJD = 'FJD', + FKP = 'FKP', GBP = 'GBP', + GEL = 'GEL', + GHS = 'GHS', + GIP = 'GIP', + GMD = 'GMD', + GNF = 'GNF', + GTQ = 'GTQ', + GYD = 'GYD', HKD = 'HKD', + HNL = 'HNL', + HTG = 'HTG', + HUF = 'HUF', + IDR = 'IDR', + ILS = 'ILS', + INR = 'INR', + IQD = 'IQD', + IRR = 'IRR', + ISK = 'ISK', + JMD = 'JMD', + JOD = 'JOD', JPY = 'JPY', - USD = 'USD', - NOK = 'NOK', - SEK = 'SEK', - BHT = 'BHT', + KES = 'KES', + KGS = 'KGS', + KHR = 'KHR', + KMF = 'KMF', + KPW = 'KPW', + KRW = 'KRW', + KWD = 'KWD', + KYD = 'KYD', + KZT = 'KZT', + LAK = 'LAK', + LBP = 'LBP', + LKR = 'LKR', + LRD = 'LRD', + LSL = 'LSL', + LYD = 'LYD', MAD = 'MAD', + MDL = 'MDL', + MGA = 'MGA', + MKD = 'MKD', + MMK = 'MMK', + MNT = 'MNT', + MOP = 'MOP', + MRU = 'MRU', + MUR = 'MUR', + MVR = 'MVR', + MWK = 'MWK', + MXN = 'MXN', + MYR = 'MYR', + MZN = 'MZN', + NAD = 'NAD', + NGN = 'NGN', + NIO = 'NIO', + NOK = 'NOK', + NPR = 'NPR', + NZD = 'NZD', + OMR = 'OMR', + PAB = 'PAB', + PEN = 'PEN', + PGK = 'PGK', + PHP = 'PHP', + PKR = 'PKR', + PLN = 'PLN', + PYG = 'PYG', QAR = 'QAR', - AED = 'AED', - KRW = 'KRW', - BRL = 'BRL', - AUD = 'AUD', + RON = 'RON', + RSD = 'RSD', + RUB = 'RUB', + RWF = 'RWF', + SAR = 'SAR', + SBD = 'SBD', + SCR = 'SCR', + SDG = 'SDG', + SEK = 'SEK', + SGD = 'SGD', + SHP = 'SHP', + SLE = 'SLE', + SOS = 'SOS', + SRD = 'SRD', + SSP = 'SSP', + STN = 'STN', + SVC = 'SVC', + SYP = 'SYP', + SZL = 'SZL', + THB = 'THB', + TJS = 'TJS', + TMT = 'TMT', + TND = 'TND', + TOP = 'TOP', + TRY = 'TRY', + TTD = 'TTD', + TWD = 'TWD', + TZS = 'TZS', + UAH = 'UAH', + UGX = 'UGX', + USD = 'USD', + UYU = 'UYU', + UZS = 'UZS', + VES = 'VES', + VND = 'VND', + VUV = 'VUV', + WST = 'WST', + XCD = 'XCD', + YER = 'YER', + ZAR = 'ZAR', + ZMW = 'ZMW', + ZWG = 'ZWG', } diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts index 16f05f2418d2..1d8107c17953 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldDefinition.ts @@ -16,4 +16,7 @@ export type FieldDefinition<T extends FieldMetadata> = { infoTooltipContent?: string; defaultValue?: any; editButtonIcon?: IconComponent; + settings?: { + decimals?: number; + }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts index 9ab99492f651..6bd2ee66810b 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldInputDraftValue.ts @@ -6,15 +6,12 @@ import { FieldCurrencyValue, FieldDateTimeValue, FieldEmailsValue, - FieldEmailValue, FieldFullNameValue, FieldJsonValue, FieldLinksValue, - FieldLinkValue, FieldMultiSelectValue, FieldNumberValue, FieldPhonesValue, - FieldPhoneValue, FieldRatingValue, FieldRelationFromManyValue, FieldRelationToOneValue, @@ -27,13 +24,11 @@ import { export type FieldTextDraftValue = string; export type FieldNumberDraftValue = number; export type FieldDateTimeDraftValue = string; -export type FieldPhoneDraftValue = string; export type FieldPhonesDraftValue = { primaryPhoneNumber: string; primaryPhoneCountryCode: string; additionalPhones?: PhoneRecord[] | null; }; -export type FieldEmailDraftValue = string; export type FieldEmailsDraftValue = { primaryEmail: string; additionalEmails: string[] | null; @@ -42,7 +37,6 @@ export type FieldSelectDraftValue = string; export type FieldMultiSelectDraftValue = string[]; export type FieldRelationDraftValue = string; export type FieldRelationManyDraftValue = string[]; -export type FieldLinkDraftValue = { url: string; label: string }; export type FieldLinksDraftValue = { primaryLinkLabel: string; primaryLinkUrl: string; @@ -80,36 +74,30 @@ export type FieldInputDraftValue<FieldValue> = FieldValue extends FieldTextValue ? FieldNumberDraftValue : FieldValue extends FieldBooleanValue ? FieldBooleanValue - : FieldValue extends FieldPhoneValue - ? FieldPhoneDraftValue - : FieldValue extends FieldPhonesValue - ? FieldPhonesDraftValue - : FieldValue extends FieldEmailValue - ? FieldEmailDraftValue - : FieldValue extends FieldEmailsValue - ? FieldEmailsDraftValue - : FieldValue extends FieldLinkValue - ? FieldLinkDraftValue - : FieldValue extends FieldLinksValue - ? FieldLinksDraftValue - : FieldValue extends FieldCurrencyValue - ? FieldCurrencyDraftValue - : FieldValue extends FieldFullNameValue - ? FieldFullNameDraftValue - : FieldValue extends FieldRatingValue - ? FieldRatingValue - : FieldValue extends FieldSelectValue - ? FieldSelectDraftValue - : FieldValue extends FieldMultiSelectValue - ? FieldMultiSelectDraftValue - : FieldValue extends FieldRelationToOneValue - ? FieldRelationDraftValue - : FieldValue extends FieldRelationFromManyValue - ? FieldRelationManyDraftValue - : FieldValue extends FieldAddressValue - ? FieldAddressDraftValue - : FieldValue extends FieldJsonValue - ? FieldJsonDraftValue - : FieldValue extends FieldActorValue - ? FieldActorDraftValue - : never; + : FieldValue extends FieldPhonesValue + ? FieldPhonesDraftValue + : FieldValue extends FieldEmailsValue + ? FieldEmailsDraftValue + : FieldValue extends FieldLinksValue + ? FieldLinksDraftValue + : FieldValue extends FieldCurrencyValue + ? FieldCurrencyDraftValue + : FieldValue extends FieldFullNameValue + ? FieldFullNameDraftValue + : FieldValue extends FieldRatingValue + ? FieldRatingValue + : FieldValue extends FieldSelectValue + ? FieldSelectDraftValue + : FieldValue extends FieldMultiSelectValue + ? FieldMultiSelectDraftValue + : FieldValue extends FieldRelationToOneValue + ? FieldRelationDraftValue + : FieldValue extends FieldRelationFromManyValue + ? FieldRelationManyDraftValue + : FieldValue extends FieldAddressValue + ? FieldAddressDraftValue + : FieldValue extends FieldJsonValue + ? FieldJsonDraftValue + : FieldValue extends FieldActorValue + ? FieldActorDraftValue + : never; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts index 0349367f2cc6..434d168e7e11 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/FieldMetadata.ts @@ -27,12 +27,18 @@ export type FieldDateTimeMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: { + displayAsRelativeDate?: boolean; + }; }; export type FieldDateMetadata = { objectMetadataNameSingular?: string; placeHolder: string; fieldName: string; + settings?: { + displayAsRelativeDate?: boolean; + }; }; export type FieldNumberMetadata = { @@ -150,6 +156,11 @@ export type FieldPhonesMetadata = { fieldName: string; }; +export type FieldTsVectorMetadata = { + objectMetadataNameSingular?: string; + fieldName: string; +}; + export type FieldMetadata = | FieldBooleanMetadata | FieldCurrencyMetadata @@ -168,22 +179,19 @@ export type FieldMetadata = | FieldUuidMetadata | FieldAddressMetadata | FieldActorMetadata - | FieldArrayMetadata; - + | FieldArrayMetadata + | FieldTsVectorMetadata; export type FieldTextValue = string; -export type FieldUUidValue = string; +export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ? export type FieldDateTimeValue = string | null; export type FieldDateValue = string | null; export type FieldNumberValue = number | null; export type FieldBooleanValue = boolean; -export type FieldPhoneValue = string; -export type FieldEmailValue = string; export type FieldEmailsValue = { primaryEmail: string; additionalEmails: string[] | null; }; -export type FieldLinkValue = { url: string; label: string }; export type FieldLinksValue = { primaryLinkLabel: string; primaryLinkUrl: string; @@ -219,6 +227,8 @@ export type FieldRelationValue< export type Json = ZodHelperLiteral | { [key: string]: Json } | Json[]; export type FieldJsonValue = Record<string, Json> | Json[] | null; +export type FieldRichTextValue = Record<string, Json> | Json[] | null; + export type FieldActorValue = { source: string; workspaceMemberId?: string; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldEmail.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldEmail.ts deleted file mode 100644 index 676cca39bdfd..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldEmail.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -import { FieldDefinition } from '../FieldDefinition'; -import { FieldEmailMetadata, FieldMetadata } from '../FieldMetadata'; - -export const isFieldEmail = ( - field: Pick<FieldDefinition<FieldMetadata>, 'type'>, -): field is FieldDefinition<FieldEmailMetadata> => - field.type === FieldMetadataType.Email; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldEmailValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldEmailValue.ts deleted file mode 100644 index 2ce954057096..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldEmailValue.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { isString } from '@sniptt/guards'; - -import { FieldEmailValue } from '../FieldMetadata'; - -// TODO: add zod -export const isFieldEmailValue = ( - fieldValue: unknown, -): fieldValue is FieldEmailValue => isString(fieldValue); diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldLink.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldLink.ts deleted file mode 100644 index 6f98150d0149..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldLink.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -import { FieldDefinition } from '../FieldDefinition'; -import { FieldLinkMetadata, FieldMetadata } from '../FieldMetadata'; - -export const isFieldLink = ( - field: Pick<FieldDefinition<FieldMetadata>, 'type'>, -): field is FieldDefinition<FieldLinkMetadata> => - field.type === FieldMetadataType.Link; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldLinkValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldLinkValue.ts deleted file mode 100644 index b075dc746f13..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldLinkValue.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from 'zod'; - -import { FieldLinkValue } from '../FieldMetadata'; - -const linkSchema = z.object({ - url: z.string(), - label: z.string(), -}); - -export const isFieldLinkValue = ( - fieldValue: unknown, -): fieldValue is FieldLinkValue => linkSchema.safeParse(fieldValue).success; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldPhone.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldPhone.ts deleted file mode 100644 index 0de71e270863..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldPhone.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -import { FieldDefinition } from '../FieldDefinition'; -import { FieldMetadata, FieldPhoneMetadata } from '../FieldMetadata'; - -export const isFieldPhone = ( - field: Pick<FieldDefinition<FieldMetadata>, 'type'>, -): field is FieldDefinition<FieldPhoneMetadata> => - field.type === FieldMetadataType.Phone; diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldPhoneValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldPhoneValue.ts deleted file mode 100644 index 4bb79a9e611a..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldPhoneValue.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { isString } from '@sniptt/guards'; - -import { FieldPhoneValue } from '../FieldMetadata'; - -// TODO: add zod -export const isFieldPhoneValue = ( - fieldValue: unknown, -): fieldValue is FieldPhoneValue => isString(fieldValue); diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldTsVectorValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldTsVectorValue.ts new file mode 100644 index 000000000000..107db30a8fbd --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldTsVectorValue.ts @@ -0,0 +1,10 @@ +import { FieldMetadata, FieldTsVectorMetadata } from '../FieldMetadata'; + +import { FieldDefinition } from '../FieldDefinition'; + +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const isFieldTsVector = ( + field: Pick<FieldDefinition<FieldMetadata>, 'type'>, +): field is FieldDefinition<FieldTsVectorMetadata> => + field.type === FieldMetadataType.TsVector; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueEmpty.test.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueEmpty.test.ts index 47fe783322c2..07761aef8822 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueEmpty.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/__tests__/isFieldValueEmpty.test.ts @@ -2,7 +2,6 @@ import { booleanFieldDefinition, fieldMetadataId, fullNameFieldDefinition, - linkFieldDefinition, relationFieldDefinition, selectFieldDefinition, } from '@/object-record/record-field/__mocks__/fieldDefinitions'; @@ -101,19 +100,4 @@ describe('isFieldValueEmpty', () => { }), ).toBe(false); }); - - it('should return correct value for link field', () => { - expect( - isFieldValueEmpty({ - fieldDefinition: linkFieldDefinition, - fieldValue: { url: '', label: '' }, - }), - ).toBe(true); - expect( - isFieldValueEmpty({ - fieldDefinition: linkFieldDefinition, - fieldValue: { url: 'https://linkedin.com/user-slug', label: '' }, - }), - ).toBe(false); - }); }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index 725ca65cc1b8..298ef3c9a4a9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -9,6 +9,7 @@ import { isFieldRelation } from '@/object-record/record-field/types/guards/isFie import { computeEmptyDraftValue } from '@/object-record/record-field/utils/computeEmptyDraftValue'; import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty'; import { isDefined } from '~/utils/isDefined'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type computeDraftValueFromFieldValueParams<FieldValue> = { fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type'>; @@ -32,7 +33,9 @@ export const computeDraftValueFromFieldValue = <FieldValue>({ } return { - amount: fieldValue?.amountMicros ? fieldValue.amountMicros / 1000000 : '', + amount: isUndefinedOrNull(fieldValue?.amountMicros) + ? '' + : (fieldValue.amountMicros / 1000000).toString(), currencyCode: fieldValue?.currencyCode ?? '', } as unknown as FieldInputDraftValue<FieldValue>; } diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts index d41d5fd85100..06d16fdb8ffb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromString.ts @@ -5,11 +5,11 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata' import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; -import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail'; +import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; -import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; +import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; @@ -31,14 +31,10 @@ export const computeDraftValueFromString = <FieldValue>({ isFieldText(fieldDefinition) || isFieldDateTime(fieldDefinition) || isFieldNumber(fieldDefinition) || - isFieldEmail(fieldDefinition) || isFieldRelation(fieldDefinition) ) { return value as FieldInputDraftValue<FieldValue>; } - if (isFieldLink(fieldDefinition)) { - return { url: value, label: value } as FieldInputDraftValue<FieldValue>; - } if (isFieldCurrency(fieldDefinition)) { return { @@ -66,5 +62,17 @@ export const computeDraftValueFromString = <FieldValue>({ } as FieldInputDraftValue<FieldValue>; } + if (isFieldEmails(fieldDefinition)) { + return { + primaryEmail: value, + } as FieldInputDraftValue<FieldValue>; + } + + if (isFieldPhones(fieldDefinition)) { + return { + primaryPhoneNumber: value, + } as FieldInputDraftValue<FieldValue>; + } + throw new Error(`Record field type not supported : ${fieldDefinition.type}}`); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts index af950cf2041f..7433a30554e8 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeEmptyDraftValue.ts @@ -5,7 +5,6 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata' import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress'; import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency'; import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; -import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; @@ -27,7 +26,6 @@ export const computeEmptyDraftValue = <FieldValue>({ isFieldText(fieldDefinition) || isFieldDateTime(fieldDefinition) || isFieldNumber(fieldDefinition) || - isFieldEmail(fieldDefinition) || isFieldRelation(fieldDefinition) || isFieldRawJson(fieldDefinition) ) { diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/getFieldButtonIcon.tsx b/packages/twenty-front/src/modules/object-record/record-field/utils/getFieldButtonIcon.tsx index 5743a74cbb1a..afb99cda1334 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/getFieldButtonIcon.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/getFieldButtonIcon.tsx @@ -11,9 +11,6 @@ import { isFieldRelation } from '@/object-record/record-field/types/guards/isFie import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isFieldArray } from '@/object-record/record-field/types/guards/isFieldArray'; -import { isFieldEmail } from '../types/guards/isFieldEmail'; -import { isFieldLink } from '../types/guards/isFieldLink'; -import { isFieldPhone } from '../types/guards/isFieldPhone'; export const getFieldButtonIcon = ( fieldDefinition: @@ -24,9 +21,6 @@ export const getFieldButtonIcon = ( if (isUndefinedOrNull(fieldDefinition)) return undefined; if ( - isFieldLink(fieldDefinition) || - isFieldEmail(fieldDefinition) || - isFieldPhone(fieldDefinition) || isFieldDisplayedAsPhone(fieldDefinition) || isFieldMultiSelect(fieldDefinition) || (isFieldRelation(fieldDefinition) && diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldMetadataReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldMetadataReadOnly.ts new file mode 100644 index 000000000000..a24ed100352d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldMetadataReadOnly.ts @@ -0,0 +1,19 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; + +export const isFieldMetadataReadOnly = (fieldMetadata: FieldMetadata) => { + if ( + fieldMetadata.fieldName === 'noteTargets' || + fieldMetadata.fieldName === 'taskTargets' + ) { + return true; + } + + return ( + isWorkflowSubObjectMetadata(fieldMetadata.objectMetadataNameSingular) || + (fieldMetadata.objectMetadataNameSingular === + CoreObjectNameSingular.Workflow && + fieldMetadata.fieldName !== 'name') + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts index 0323ef19e099..93ee5eaa5458 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueEmpty.ts @@ -13,19 +13,15 @@ import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFie import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue'; import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate'; import { isFieldDateTime } from '@/object-record/record-field/types/guards/isFieldDateTime'; -import { isFieldEmail } from '@/object-record/record-field/types/guards/isFieldEmail'; import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails'; import { isFieldEmailsValue } from '@/object-record/record-field/types/guards/isFieldEmailsValue'; import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName'; import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue'; -import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink'; -import { isFieldLinkValue } from '@/object-record/record-field/types/guards/isFieldLinkValue'; import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks'; import { isFieldLinksValue } from '@/object-record/record-field/types/guards/isFieldLinksValue'; import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect'; import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue'; import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber'; -import { isFieldPhone } from '@/object-record/record-field/types/guards/isFieldPhone'; import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones'; import { isFieldPhonesValue } from '@/object-record/record-field/types/guards/isFieldPhonesValue'; import { isFieldPosition } from '@/object-record/record-field/types/guards/isFieldPosition'; @@ -36,6 +32,7 @@ import { isFieldRichText } from '@/object-record/record-field/types/guards/isFie import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue'; import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; +import { isFieldTsVector } from '@/object-record/record-field/types/guards/isFieldTsVectorValue'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; import { isDefined } from '~/utils/isDefined'; import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString'; @@ -60,12 +57,10 @@ export const isFieldValueEmpty = ({ isFieldDate(fieldDefinition) || isFieldNumber(fieldDefinition) || isFieldRating(fieldDefinition) || - isFieldEmail(fieldDefinition) || isFieldBoolean(fieldDefinition) || isFieldRelation(fieldDefinition) || isFieldRawJson(fieldDefinition) || isFieldRichText(fieldDefinition) || - isFieldPhone(fieldDefinition) || isFieldPosition(fieldDefinition) ) { return isValueEmpty(fieldValue); @@ -101,10 +96,6 @@ export const isFieldValueEmpty = ({ ); } - if (isFieldLink(fieldDefinition)) { - return !isFieldLinkValue(fieldValue) || isValueEmpty(fieldValue?.url); - } - if (isFieldAddress(fieldDefinition)) { return ( !isFieldAddressValue(fieldValue) || @@ -140,6 +131,10 @@ export const isFieldValueEmpty = ({ ); } + if (isFieldTsVector(fieldDefinition)) { + return false; + } + throw new Error( `Entity field type not supported in isFieldValueEmpty : ${fieldDefinition.type}}`, ); 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/applyEmptyFilters.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/applyEmptyFilters.ts new file mode 100644 index 000000000000..e004288ceba0 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/applyEmptyFilters.ts @@ -0,0 +1,327 @@ +import { + ActorFilter, + AddressFilter, + CurrencyFilter, + DateFilter, + EmailsFilter, + FloatFilter, + RecordGqlOperationFilter, + RelationFilter, + StringFilter, + URLFilter, + UUIDFilter, +} from '@/object-record/graphql/types/RecordGqlOperationFilter'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { isNonEmptyString } from '@sniptt/guards'; +import { Field } from '~/generated/graphql'; +import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; + +// TODO: fix this +export const applyEmptyFilters = ( + operand: ViewFilterOperand, + correspondingField: Pick<Field, 'id' | 'name'>, + objectRecordFilters: RecordGqlOperationFilter[], + definition: FilterDefinition, +) => { + let emptyRecordFilter: RecordGqlOperationFilter = {}; + + const compositeFieldName = definition.compositeFieldName; + + const isCompositeField = isNonEmptyString(compositeFieldName); + + switch (definition.type) { + case 'TEXT': + emptyRecordFilter = { + or: [ + { [correspondingField.name]: { ilike: '' } as StringFilter }, + { [correspondingField.name]: { is: 'NULL' } as StringFilter }, + ], + }; + break; + case 'PHONES': { + if (!isCompositeField) { + const phonesFilter = generateILikeFiltersForCompositeFields( + '', + correspondingField.name, + ['primaryPhoneNumber', 'primaryPhoneCountryCode'], + true, + ); + + emptyRecordFilter = { + and: phonesFilter, + }; + break; + } else { + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + [compositeFieldName]: { ilike: '' }, + } as StringFilter, + }, + { + [correspondingField.name]: { + [compositeFieldName]: { is: 'NULL' }, + } as StringFilter, + }, + ], + }; + break; + } + } + case 'CURRENCY': + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + amountMicros: { is: 'NULL' }, + } as CurrencyFilter, + }, + ], + }; + break; + case 'FULL_NAME': { + if (!isCompositeField) { + const fullNameFilters = generateILikeFiltersForCompositeFields( + '', + correspondingField.name, + ['firstName', 'lastName'], + true, + ); + + emptyRecordFilter = { + and: fullNameFilters, + }; + } else { + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + [compositeFieldName]: { ilike: '' }, + }, + }, + { + [correspondingField.name]: { + [compositeFieldName]: { is: 'NULL' }, + }, + }, + ], + }; + } + break; + } + case 'LINKS': { + if (!isCompositeField) { + const linksFilters = generateILikeFiltersForCompositeFields( + '', + correspondingField.name, + ['primaryLinkLabel', 'primaryLinkUrl'], + true, + ); + + emptyRecordFilter = { + and: linksFilters, + }; + } else { + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + [compositeFieldName]: { ilike: '' }, + } as URLFilter, + }, + { + [correspondingField.name]: { + [compositeFieldName]: { is: 'NULL' }, + } as URLFilter, + }, + ], + }; + } + break; + } + case 'ADDRESS': + if (!isCompositeField) { + emptyRecordFilter = { + and: [ + { + or: [ + { + [correspondingField.name]: { + addressStreet1: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressStreet1: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }, + { + or: [ + { + [correspondingField.name]: { + addressStreet2: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressStreet2: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }, + { + or: [ + { + [correspondingField.name]: { + addressCity: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressCity: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }, + { + or: [ + { + [correspondingField.name]: { + addressState: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressState: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }, + { + or: [ + { + [correspondingField.name]: { + addressCountry: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressCountry: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }, + { + or: [ + { + [correspondingField.name]: { + addressPostcode: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressPostcode: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }, + ], + }; + } else { + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + [compositeFieldName]: { ilike: '' }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + [compositeFieldName]: { is: 'NULL' }, + } as AddressFilter, + }, + ], + }; + } + break; + case 'NUMBER': + emptyRecordFilter = { + [correspondingField.name]: { is: 'NULL' } as FloatFilter, + }; + break; + case 'RATING': + emptyRecordFilter = { + [correspondingField.name]: { is: 'NULL' } as StringFilter, + }; + break; + case 'DATE': + case 'DATE_TIME': + emptyRecordFilter = { + [correspondingField.name]: { is: 'NULL' } as DateFilter, + }; + break; + case 'SELECT': + emptyRecordFilter = { + [correspondingField.name]: { is: 'NULL' } as UUIDFilter, + }; + break; + case 'RELATION': + emptyRecordFilter = { + [correspondingField.name + 'Id']: { is: 'NULL' } as RelationFilter, + }; + break; + case 'ACTOR': + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + name: { ilike: '' }, + } as ActorFilter, + }, + { + [correspondingField.name]: { + name: { is: 'NULL' }, + } as ActorFilter, + }, + ], + }; + break; + case 'EMAILS': + emptyRecordFilter = { + or: [ + { + [correspondingField.name]: { + primaryEmail: { ilike: '' }, + } as EmailsFilter, + }, + { + [correspondingField.name]: { + primaryEmail: { is: 'NULL' }, + } as EmailsFilter, + }, + ], + }; + break; + default: + throw new Error(`Unsupported empty filter type ${definition.type}`); + } + + switch (operand) { + case ViewFilterOperand.IsEmpty: + objectRecordFilters.push(emptyRecordFilter); + break; + case ViewFilterOperand.IsNotEmpty: + objectRecordFilters.push({ + not: emptyRecordFilter, + }); + break; + default: + throw new Error( + `Unknown operand ${operand} for ${definition.type} filter`, + ); + } +}; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts index f83c82d0e865..929956d77128 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts @@ -11,13 +11,13 @@ import { EmailsFilter, FloatFilter, FullNameFilter, + LeafObjectRecordFilter, LinksFilter, NotObjectRecordFilter, OrObjectRecordFilter, PhonesFilter, RecordGqlOperationFilter, StringFilter, - URLFilter, UUIDFilter, } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { isMatchingBooleanFilter } from '@/object-record/record-filter/utils/isMatchingBooleanFilter'; @@ -30,6 +30,12 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; import { isEmptyObject } from '~/utils/isEmptyObject'; +const isLeafFilter = ( + filter: RecordGqlOperationFilter, +): filter is LeafObjectRecordFilter => { + return !isAndFilter(filter) && !isOrFilter(filter) && !isNotFilter(filter); +}; + const isAndFilter = ( filter: RecordGqlOperationFilter, ): filter is AndObjectRecordFilter => 'and' in filter && !!filter.and; @@ -51,7 +57,7 @@ export const isRecordMatchingFilter = ({ filter: RecordGqlOperationFilter; objectMetadataItem: ObjectMetadataItem; }): boolean => { - if (Object.keys(filter).length === 0) { + if (Object.keys(filter).length === 0 && record.deletedAt === null) { return true; } @@ -121,6 +127,12 @@ export const isRecordMatchingFilter = ({ ); } + if (isLeafFilter(filter)) { + if (isDefined(record.deletedAt) && filter.deletedAt === undefined) { + return false; + } + } + return Object.entries(filter).every(([filterKey, filterValue]) => { if (!isDefined(filterValue)) { throw new Error( @@ -144,8 +156,6 @@ export const isRecordMatchingFilter = ({ } switch (objectMetadataField.type) { - case FieldMetadataType.Email: - case FieldMetadataType.Phone: case FieldMetadataType.Select: case FieldMetadataType.Rating: case FieldMetadataType.MultiSelect: @@ -155,22 +165,6 @@ export const isRecordMatchingFilter = ({ value: record[filterKey], }); } - case FieldMetadataType.Link: { - const urlFilter = filterValue as URLFilter; - - return ( - (urlFilter.url === undefined || - isMatchingStringFilter({ - stringFilter: urlFilter.url, - value: record[filterKey].url, - })) && - (urlFilter.label === undefined || - isMatchingStringFilter({ - stringFilter: urlFilter.label, - value: record[filterKey].label, - })) - ); - } case FieldMetadataType.FullName: { const fullNameFilter = filterValue as FullNameFilter; @@ -309,7 +303,9 @@ export const isRecordMatchingFilter = ({ ); } default: { - throw new Error('Not implemented yet'); + throw new Error( + `Not implemented yet for field type "${objectMetadataField.type}"`, + ); } } }); 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 3b0e9f8c2c72..ff0199344948 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,10 +10,8 @@ import { RecordGqlOperationFilter, RelationFilter, StringFilter, - URLFilter, UUIDFilter, } from '@/object-record/graphql/types/RecordGqlOperationFilter'; -import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { Field } from '~/generated/graphql'; @@ -25,261 +23,16 @@ import { convertLessThanRatingToArrayOfRatingValues, convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; -import { Filter } from '../../object-filter-dropdown/types/Filter'; - -export type ObjectDropdownFilter = Omit<Filter, 'definition'> & { - definition: { - type: Filter['definition']['type']; - }; -}; - -const applyEmptyFilters = ( - operand: ViewFilterOperand, - correspondingField: Pick<Field, 'id' | 'name'>, - objectRecordFilters: RecordGqlOperationFilter[], - filterType: FilterType, -) => { - let emptyRecordFilter: RecordGqlOperationFilter = {}; - - switch (filterType) { - case 'TEXT': - case 'EMAIL': - case 'PHONE': - emptyRecordFilter = { - or: [ - { [correspondingField.name]: { ilike: '' } as StringFilter }, - { [correspondingField.name]: { is: 'NULL' } as StringFilter }, - ], - }; - break; - case 'PHONES': { - const phonesFilter = generateILikeFiltersForCompositeFields( - '', - correspondingField.name, - ['primaryPhoneNumber', 'primaryPhoneCountryCode'], - true, - ); - - emptyRecordFilter = { - and: phonesFilter, - }; - break; - } - case 'CURRENCY': - emptyRecordFilter = { - or: [ - { - [correspondingField.name]: { - amountMicros: { is: 'NULL' }, - } as CurrencyFilter, - }, - ], - }; - break; - case 'FULL_NAME': { - const fullNameFilters = generateILikeFiltersForCompositeFields( - '', - correspondingField.name, - ['firstName', 'lastName'], - true, - ); - - emptyRecordFilter = { - and: fullNameFilters, - }; - break; - } - case 'LINK': - emptyRecordFilter = { - or: [ - { [correspondingField.name]: { url: { ilike: '' } } as URLFilter }, - { - [correspondingField.name]: { url: { is: 'NULL' } } as URLFilter, - }, - ], - }; - break; - case 'LINKS': { - const linksFilters = generateILikeFiltersForCompositeFields( - '', - correspondingField.name, - ['primaryLinkLabel', 'primaryLinkUrl'], - true, - ); - - emptyRecordFilter = { - and: linksFilters, - }; - break; - } - case 'ADDRESS': - emptyRecordFilter = { - and: [ - { - or: [ - { - [correspondingField.name]: { - addressStreet1: { ilike: '' }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressStreet1: { is: 'NULL' }, - } as AddressFilter, - }, - ], - }, - { - or: [ - { - [correspondingField.name]: { - addressStreet2: { ilike: '' }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressStreet2: { is: 'NULL' }, - } as AddressFilter, - }, - ], - }, - { - or: [ - { - [correspondingField.name]: { - addressCity: { ilike: '' }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressCity: { is: 'NULL' }, - } as AddressFilter, - }, - ], - }, - { - or: [ - { - [correspondingField.name]: { - addressState: { ilike: '' }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressState: { is: 'NULL' }, - } as AddressFilter, - }, - ], - }, - { - or: [ - { - [correspondingField.name]: { - addressCountry: { ilike: '' }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressCountry: { is: 'NULL' }, - } as AddressFilter, - }, - ], - }, - { - or: [ - { - [correspondingField.name]: { - addressPostcode: { ilike: '' }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressPostcode: { is: 'NULL' }, - } as AddressFilter, - }, - ], - }, - ], - }; - break; - case 'NUMBER': - emptyRecordFilter = { - [correspondingField.name]: { is: 'NULL' } as FloatFilter, - }; - break; - case 'RATING': - emptyRecordFilter = { - [correspondingField.name]: { is: 'NULL' } as StringFilter, - }; - break; - case 'DATE': - case 'DATE_TIME': - emptyRecordFilter = { - [correspondingField.name]: { is: 'NULL' } as DateFilter, - }; - break; - case 'SELECT': - emptyRecordFilter = { - [correspondingField.name]: { is: 'NULL' } as UUIDFilter, - }; - break; - case 'RELATION': - emptyRecordFilter = { - [correspondingField.name + 'Id']: { is: 'NULL' } as RelationFilter, - }; - break; - case 'ACTOR': - emptyRecordFilter = { - or: [ - { - [correspondingField.name]: { - name: { ilike: '' }, - } as ActorFilter, - }, - { - [correspondingField.name]: { - name: { is: 'NULL' }, - } as ActorFilter, - }, - ], - }; - break; - case 'EMAILS': - emptyRecordFilter = { - or: [ - { - [correspondingField.name]: { - primaryEmail: { ilike: '' }, - } as EmailsFilter, - }, - { - [correspondingField.name]: { - primaryEmail: { is: 'NULL' }, - } as EmailsFilter, - }, - ], - }; - break; - default: - throw new Error(`Unsupported empty filter type ${filterType}`); - } - - switch (operand) { - case ViewFilterOperand.IsEmpty: - objectRecordFilters.push(emptyRecordFilter); - break; - case ViewFilterOperand.IsNotEmpty: - objectRecordFilters.push({ - not: emptyRecordFilter, - }); - break; - default: - throw new Error(`Unknown operand ${operand} for ${filterType} filter`); - } -}; +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { applyEmptyFilters } from '@/object-record/record-filter/utils/applyEmptyFilters'; +import { resolveFilterValue } from '@/views/utils/view-filter-value/resolveFilterValue'; +import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns'; +import { z } from 'zod'; +// TODO: break this down into smaller functions and make the whole thing immutable +// Especially applyEmptyFilters export const turnObjectDropdownFilterIntoQueryFilter = ( - rawUIFilters: ObjectDropdownFilter[], + rawUIFilters: Filter[], fields: Pick<Field, 'id' | 'name'>[], ): RecordGqlOperationFilter | undefined => { const objectRecordFilters: RecordGqlOperationFilter[] = []; @@ -289,9 +42,16 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( (field) => field.id === rawUIFilter.fieldMetadataId, ); + const compositeFieldName = rawUIFilter.definition.compositeFieldName; + + const isCompositeFieldFiter = isNonEmptyString(compositeFieldName); + const isEmptyOperand = [ ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, ].includes(rawUIFilter.operand); if (!correspondingField) { @@ -305,8 +65,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } switch (rawUIFilter.definition.type) { - case 'EMAIL': - case 'PHONE': case 'TEXT': switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: @@ -331,7 +89,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -341,37 +99,132 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } break; case 'DATE': - case 'DATE_TIME': + case 'DATE_TIME': { + const resolvedFilterValue = resolveFilterValue(rawUIFilter); + const now = roundToNearestMinutes(new Date()); + const date = + resolvedFilterValue instanceof Date ? resolvedFilterValue : now; + switch (rawUIFilter.operand) { - case ViewFilterOperand.GreaterThan: + case ViewFilterOperand.IsAfter: { objectRecordFilters.push({ [correspondingField.name]: { - gte: rawUIFilter.value, + gt: date.toISOString(), } as DateFilter, }); break; - case ViewFilterOperand.LessThan: + } + case ViewFilterOperand.IsBefore: { objectRecordFilters.push({ [correspondingField.name]: { - lte: rawUIFilter.value, + lt: date.toISOString(), } as DateFilter, }); break; + } case ViewFilterOperand.IsEmpty: - case ViewFilterOperand.IsNotEmpty: + case ViewFilterOperand.IsNotEmpty: { applyEmptyFilters( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; + } + case ViewFilterOperand.IsRelative: { + const dateRange = z + .object({ start: z.date(), end: z.date() }) + .safeParse(resolvedFilterValue).data; + + const defaultDateRange = resolveFilterValue({ + value: 'PAST_1_DAY', + definition: { + type: 'DATE', + }, + operand: ViewFilterOperand.IsRelative, + }); + + if (!defaultDateRange) { + throw new Error('Failed to resolve default date range'); + } + + const { start, end } = dateRange ?? defaultDateRange; + + objectRecordFilters.push({ + and: [ + { + [correspondingField.name]: { + gte: start.toISOString(), + } as DateFilter, + }, + { + [correspondingField.name]: { + lte: end.toISOString(), + } as DateFilter, + }, + ], + }); + break; + } + case ViewFilterOperand.Is: { + const isValid = resolvedFilterValue instanceof Date; + const date = isValid ? resolvedFilterValue : now; + + objectRecordFilters.push({ + and: [ + { + [correspondingField.name]: { + lte: endOfDay(date).toISOString(), + } as DateFilter, + }, + { + [correspondingField.name]: { + gte: startOfDay(date).toISOString(), + } as DateFilter, + }, + ], + }); + break; + } + case ViewFilterOperand.IsInPast: + objectRecordFilters.push({ + [correspondingField.name]: { + lte: now.toISOString(), + } as DateFilter, + }); + break; + case ViewFilterOperand.IsInFuture: + objectRecordFilters.push({ + [correspondingField.name]: { + gte: now.toISOString(), + } as DateFilter, + }); + break; + case ViewFilterOperand.IsToday: { + objectRecordFilters.push({ + and: [ + { + [correspondingField.name]: { + lte: endOfDay(now).toISOString(), + } as DateFilter, + }, + { + [correspondingField.name]: { + gte: startOfDay(now).toISOString(), + } as DateFilter, + }, + ], + }); + break; + } default: throw new Error( - `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, // ); } break; + } case 'RATING': switch (rawUIFilter.operand) { case ViewFilterOperand.Is: @@ -405,7 +258,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -436,7 +289,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -491,7 +344,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -524,44 +377,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, - ); - break; - default: - throw new Error( - `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, - ); - } - 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, + rawUIFilter.definition, ); break; default: @@ -576,20 +392,43 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( correspondingField.name, ['primaryLinkLabel', 'primaryLinkUrl'], ); + switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: - objectRecordFilters.push({ - or: linksFilters, - }); + if (!isCompositeFieldFiter) { + objectRecordFilters.push({ + or: linksFilters, + }); + } else { + objectRecordFilters.push({ + [correspondingField.name]: { + [compositeFieldName]: { + ilike: `%${rawUIFilter.value}%`, + }, + }, + }); + } break; case ViewFilterOperand.DoesNotContain: - objectRecordFilters.push({ - and: linksFilters.map((filter) => { - return { - not: filter, - }; - }), - }); + if (!isCompositeFieldFiter) { + objectRecordFilters.push({ + and: linksFilters.map((filter) => { + return { + not: filter, + }; + }), + }); + } else { + objectRecordFilters.push({ + not: { + [correspondingField.name]: { + [compositeFieldName]: { + ilike: `%${rawUIFilter.value}%`, + }, + }, + }, + }); + } break; case ViewFilterOperand.IsEmpty: case ViewFilterOperand.IsNotEmpty: @@ -597,7 +436,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -615,18 +454,40 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( ); switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: - objectRecordFilters.push({ - or: fullNameFilters, - }); + if (!isCompositeFieldFiter) { + objectRecordFilters.push({ + or: fullNameFilters, + }); + } else { + objectRecordFilters.push({ + [correspondingField.name]: { + [compositeFieldName]: { + ilike: `%${rawUIFilter.value}%`, + }, + }, + }); + } break; case ViewFilterOperand.DoesNotContain: - objectRecordFilters.push({ - and: fullNameFilters.map((filter) => { - return { - not: filter, - }; - }), - }); + if (!isCompositeFieldFiter) { + objectRecordFilters.push({ + and: fullNameFilters.map((filter) => { + return { + not: filter, + }; + }), + }); + } else { + objectRecordFilters.push({ + not: { + [correspondingField.name]: { + [compositeFieldName]: { + ilike: `%${rawUIFilter.value}%`, + }, + }, + }, + }); + } break; case ViewFilterOperand.IsEmpty: case ViewFilterOperand.IsNotEmpty: @@ -634,7 +495,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -647,85 +508,107 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( case 'ADDRESS': switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: - objectRecordFilters.push({ - or: [ - { - [correspondingField.name]: { - addressStreet1: { - ilike: `%${rawUIFilter.value}%`, - }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressStreet2: { - ilike: `%${rawUIFilter.value}%`, - }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressCity: { - ilike: `%${rawUIFilter.value}%`, - }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressState: { - ilike: `%${rawUIFilter.value}%`, - }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressCountry: { - ilike: `%${rawUIFilter.value}%`, - }, - } as AddressFilter, - }, - { - [correspondingField.name]: { - addressPostcode: { - ilike: `%${rawUIFilter.value}%`, - }, - } as AddressFilter, - }, - ], - }); - break; - case ViewFilterOperand.DoesNotContain: - objectRecordFilters.push({ - and: [ - { - not: { + if (!isCompositeFieldFiter) { + objectRecordFilters.push({ + or: [ + { [correspondingField.name]: { addressStreet1: { ilike: `%${rawUIFilter.value}%`, }, } as AddressFilter, }, - }, - { - not: { + { [correspondingField.name]: { addressStreet2: { ilike: `%${rawUIFilter.value}%`, }, } as AddressFilter, }, - }, - { - not: { + { [correspondingField.name]: { addressCity: { ilike: `%${rawUIFilter.value}%`, }, } as AddressFilter, }, + { + [correspondingField.name]: { + addressState: { + ilike: `%${rawUIFilter.value}%`, + }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressCountry: { + ilike: `%${rawUIFilter.value}%`, + }, + } as AddressFilter, + }, + { + [correspondingField.name]: { + addressPostcode: { + ilike: `%${rawUIFilter.value}%`, + }, + } as AddressFilter, + }, + ], + }); + } else { + objectRecordFilters.push({ + [correspondingField.name]: { + [compositeFieldName]: { + ilike: `%${rawUIFilter.value}%`, + } as AddressFilter, }, - ], - }); + }); + } + break; + case ViewFilterOperand.DoesNotContain: + if (!isCompositeFieldFiter) { + objectRecordFilters.push({ + and: [ + { + not: { + [correspondingField.name]: { + addressStreet1: { + ilike: `%${rawUIFilter.value}%`, + }, + } as AddressFilter, + }, + }, + { + not: { + [correspondingField.name]: { + addressStreet2: { + ilike: `%${rawUIFilter.value}%`, + }, + } as AddressFilter, + }, + }, + { + not: { + [correspondingField.name]: { + addressCity: { + ilike: `%${rawUIFilter.value}%`, + }, + } as AddressFilter, + }, + }, + ], + }); + } else { + objectRecordFilters.push({ + not: { + [correspondingField.name]: { + [compositeFieldName]: { + ilike: `%${rawUIFilter.value}%`, + } as AddressFilter, + }, + }, + }); + } break; case ViewFilterOperand.IsEmpty: case ViewFilterOperand.IsNotEmpty: @@ -733,7 +616,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -748,7 +631,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; } @@ -793,8 +676,38 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } break; } - case 'ACTOR': + // TODO: fix this with a new composite field in ViewFilter entity + case 'ACTOR': { switch (rawUIFilter.operand) { + case ViewFilterOperand.Is: { + const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[]; + + objectRecordFilters.push({ + [correspondingField.name]: { + source: { + in: parsedRecordIds, + } as RelationFilter, + }, + }); + + break; + } + case ViewFilterOperand.IsNot: { + const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[]; + + if (parsedRecordIds.length > 0) { + objectRecordFilters.push({ + not: { + [correspondingField.name]: { + source: { + in: parsedRecordIds, + } as RelationFilter, + }, + }, + }); + } + break; + } case ViewFilterOperand.Contains: objectRecordFilters.push({ or: [ @@ -829,15 +742,17 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; + default: throw new Error( - `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, + `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.label} filter`, ); } break; + } case 'EMAILS': switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: @@ -874,7 +789,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: @@ -910,7 +825,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( rawUIFilter.operand, correspondingField, objectRecordFilters, - rawUIFilter.definition.type, + rawUIFilter.definition, ); break; default: diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx index 8bca151080a2..b34fc8b7ac98 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx @@ -4,9 +4,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; -import { RecordBoardActionBar } from '@/object-record/record-board/action-bar/components/RecordBoardActionBar'; import { RecordBoard } from '@/object-record/record-board/components/RecordBoard'; -import { RecordBoardContextMenu } from '@/object-record/record-board/context-menu/components/RecordBoardContextMenu'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; @@ -51,8 +49,6 @@ export const RecordIndexBoardContainer = ({ }} > <RecordBoard recordBoardId={recordBoardId} /> - <RecordBoardActionBar recordBoardId={recordBoardId} /> - <RecordBoardContextMenu recordBoardId={recordBoardId} /> </RecordBoardContext.Provider> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardDataLoaderEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardDataLoaderEffect.tsx index 19f11f45a55e..2bf2f827d85f 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardDataLoaderEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardDataLoaderEffect.tsx @@ -1,10 +1,11 @@ import { useEffect } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar'; +import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; -import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; @@ -61,7 +62,22 @@ export const RecordIndexBoardDataLoaderEffect = ({ setFieldDefinitions(recordIndexFieldDefinitions); }, [recordIndexFieldDefinitions, setFieldDefinitions]); - const { resetRecordSelection } = useRecordBoardSelection(recordBoardId); + const navigate = useNavigate(); + const location = useLocation(); + const setNavigationMemorizedUrl = useSetRecoilState( + navigationMemorizedUrlState, + ); + + const navigateToSelectSettings = useCallback(() => { + setNavigationMemorizedUrl(location.pathname + location.search); + navigate(`/settings/objects/${getObjectSlug(objectMetadataItem)}`); + }, [ + navigate, + objectMetadataItem, + location.pathname, + location.search, + setNavigationMemorizedUrl, + ]); useEffect(() => { setObjectSingularName(objectNameSingular); @@ -96,16 +112,32 @@ export const RecordIndexBoardDataLoaderEffect = ({ const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector()); - const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({ - objectMetadataItem, - selectedRecordIds, - callback: resetRecordSelection, - }); + const setContextStoreTargetedRecordIds = useSetRecoilState( + contextStoreTargetedRecordIdsState, + ); + + const setContextStoreCurrentObjectMetadataItem = useSetRecoilState( + contextStoreCurrentObjectMetadataIdState, + ); + + useEffect(() => { + setContextStoreTargetedRecordIds(selectedRecordIds); + }, [selectedRecordIds, setContextStoreTargetedRecordIds]); useEffect(() => { - setActionBarEntries?.(); - setContextMenuEntries?.(); - }, [setActionBarEntries, setContextMenuEntries]); + setContextStoreTargetedRecordIds(selectedRecordIds); + setContextStoreCurrentObjectMetadataItem(objectMetadataItem?.id); + + return () => { + setContextStoreTargetedRecordIds([]); + setContextStoreCurrentObjectMetadataItem(null); + }; + }, [ + objectMetadataItem?.id, + selectedRecordIds, + setContextStoreCurrentObjectMetadataItem, + setContextStoreTargetedRecordIds, + ]); return <></>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 347ea1e213b4..e2f77be13069 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -23,6 +23,13 @@ import { RecordIndexRootPropsContext } from '@/object-record/record-index/contex import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider'; + +import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter'; +import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar'; +import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals'; +import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown'; +import { ActionMenuEffect } from '@/action-menu/components/ActionMenuEffect'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ViewBar } from '@/views/components/ViewBar'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewField } from '@/views/types/ViewField'; @@ -270,6 +277,15 @@ export const RecordIndexContainer = () => { /> </StyledContainerWithPadding> )} + <ActionMenuComponentInstanceContext.Provider + value={{ instanceId: recordIndexId }} + > + <ActionMenuEffect /> + <RecordActionMenuEntriesSetter /> + <ActionMenuBar /> + <ActionMenuDropdown /> + <ActionMenuConfirmationModals /> + </ActionMenuComponentInstanceContext.Provider> </RecordFieldValueSelectorContextProvider> </ViewComponentInstanceContext.Provider> </StyledContainer> diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx index bb8c0197a940..f167ad13f19d 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx @@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; @@ -30,8 +31,11 @@ export const RecordIndexPageHeader = () => { const recordIndexViewType = useRecoilValue(recordIndexViewTypeState); - const isTable = - recordIndexViewType === ViewType.Table && !objectMetadataItem?.isRemote; + const shouldDisplayAddButton = objectMetadataItem + ? !isObjectMetadataReadOnly(objectMetadataItem) + : false; + + const isTable = recordIndexViewType === ViewType.Table; const pageHeaderTitle = objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural); @@ -43,11 +47,12 @@ export const RecordIndexPageHeader = () => { return ( <PageHeader title={pageHeaderTitle} Icon={Icon}> <PageHotkeysEffect onAddButtonClick={handleAddButtonClick} /> - {isTable ? ( - <PageAddButton onClick={handleAddButtonClick} /> - ) : ( - <RecordIndexPageKanbanAddButton /> - )} + {shouldDisplayAddButton && + (isTable ? ( + <PageAddButton onClick={handleAddButtonClick} /> + ) : ( + <RecordIndexPageKanbanAddButton /> + ))} </PageHeader> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx index 85a60dbc671d..e67a5a1882b4 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx @@ -1,23 +1,21 @@ +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; +import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; -import { useRecordIndexPageKanbanAddButton } from '@/object-record/record-index/hooks/useRecordIndexPageKanbanAddButton'; -import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; -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 { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { IconButton } from '@/ui/input/button/components/IconButton'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import styled from '@emotion/styled'; -import { useCallback, useContext, useState } from 'react'; +import { useCallback, useContext } from 'react'; import { useRecoilValue } from 'recoil'; -import { IconPlus, isDefined } from 'twenty-ui'; +import { IconPlus } from 'twenty-ui'; const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)` width: 100%; @@ -29,82 +27,59 @@ const StyledDropDownMenu = styled(DropdownMenu)` export const RecordIndexPageKanbanAddButton = () => { const dropdownId = `record-index-page-add-button-dropdown`; - const [isSelectingCompany, setIsSelectingCompany] = useState(false); - const [selectedColumnDefinition, setSelectedColumnDefinition] = - useState<RecordGroupDefinition>(); - const { recordIndexId, objectNamePlural } = useContext( + const { recordIndexId, objectNameSingular } = useContext( RecordIndexRootPropsContext, ); + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); - const { columnIdsState } = useRecordBoardStates(recordIndexId); - const columnIds = useRecoilValue(columnIdsState); + const recordIndexKanbanFieldMetadataId = useRecoilValue( + recordIndexKanbanFieldMetadataIdState, + ); - const { - setHotkeyScopeAndMemorizePreviousScope, - goBackToPreviousHotkeyScope, - } = usePreviousHotkeyScope(); - const { resetSearchFilter } = useEntitySelectSearch({ - relationPickerScopeId: 'relation-picker', - }); + const selectFieldMetadataItem = objectMetadataItem.fields.find( + (field) => field.id === recordIndexKanbanFieldMetadataId, + ); + const isOpportunity = + objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity; - const { closeDropdown } = useDropdown(dropdownId); + const { columnIdsState, visibleFieldDefinitionsState } = + useRecordBoardStates(recordIndexId); + const columnIds = useRecoilValue(columnIdsState); + const visibleFieldDefinitions = useRecoilValue( + visibleFieldDefinitionsState(), + ); + const labelIdentifierField = visibleFieldDefinitions.find( + (field) => field.isLabelIdentifier, + ); - const { - selectFieldMetadataItem, - isOpportunity, - createOpportunity, - createRecordWithoutCompany, - } = useRecordIndexPageKanbanAddButton({ - objectNamePlural, - }); + const { closeDropdown } = useDropdown(dropdownId); + const { isOpportunitiesCompanyFieldDisabled } = + useIsOpportunitiesCompanyFieldDisabled(); + const { handleAddNewCardClick } = useAddNewCard(); const handleItemClick = useCallback( (columnDefinition: RecordGroupDefinition) => { - if (isOpportunity) { - setIsSelectingCompany(true); - setSelectedColumnDefinition(columnDefinition); - setHotkeyScopeAndMemorizePreviousScope( - RelationPickerHotkeyScope.RelationPicker, - ); - } else { - createRecordWithoutCompany(columnDefinition); - closeDropdown(); - } - }, - [ - isOpportunity, - createRecordWithoutCompany, - setHotkeyScopeAndMemorizePreviousScope, - closeDropdown, - ], - ); - - const handleEntitySelect = useCallback( - (company?: EntityForSelect) => { - setIsSelectingCompany(false); - goBackToPreviousHotkeyScope(); - resetSearchFilter(); - if (isDefined(company) && isDefined(selectedColumnDefinition)) { - createOpportunity(company, selectedColumnDefinition); - } + const isOpportunityEnabled = + isOpportunity && !isOpportunitiesCompanyFieldDisabled; + handleAddNewCardClick( + labelIdentifierField?.label ?? '', + '', + 'first', + isOpportunityEnabled, + columnDefinition.id, + ); closeDropdown(); }, [ - createOpportunity, - goBackToPreviousHotkeyScope, - resetSearchFilter, - selectedColumnDefinition, + isOpportunity, + handleAddNewCardClick, closeDropdown, + labelIdentifierField, + isOpportunitiesCompanyFieldDisabled, ], ); - const handleCancel = useCallback(() => { - resetSearchFilter(); - goBackToPreviousHotkeyScope(); - setIsSelectingCompany(false); - }, [goBackToPreviousHotkeyScope, resetSearchFilter]); - if (!selectFieldMetadataItem) { return null; } @@ -126,27 +101,16 @@ export const RecordIndexPageKanbanAddButton = () => { dropdownId={dropdownId} dropdownComponents={ <StyledDropDownMenu> - {isOpportunity && isSelectingCompany ? ( - <SingleEntitySelect - disableBackgroundBlur - onCancel={handleCancel} - onEntitySelected={handleEntitySelect} - relationObjectNameSingular={CoreObjectNameSingular.Company} - relationPickerScopeId="relation-picker" - selectedRelationRecordIds={[]} - /> - ) : ( - <StyledDropdownMenuItemsContainer> - {columnIds.map((columnId) => ( - <RecordIndexPageKanbanAddMenuItem - key={columnId} - columnId={columnId} - recordIndexId={recordIndexId} - onItemClick={handleItemClick} - /> - ))} - </StyledDropdownMenuItemsContainer> - )} + <StyledDropdownMenuItemsContainer> + {columnIds.map((columnId) => ( + <RecordIndexPageKanbanAddMenuItem + key={columnId} + columnId={columnId} + recordIndexId={recordIndexId} + onItemClick={handleItemClick} + /> + ))} + </StyledDropdownMenuItemsContainer> </StyledDropDownMenu> } dropdownHotkeyScope={{ scope: dropdownId }} diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx index 959d09b8ca39..8e84fa83325d 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx @@ -1,9 +1,9 @@ -import { AvatarChip, AvatarChipVariant } from 'twenty-ui'; - +import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; import { useRecordChipData } from '@/object-record/hooks/useRecordChipData'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useContext } from 'react'; +import { AvatarChip, AvatarChipVariant } from 'twenty-ui'; export type RecordIdentifierChipProps = { objectNameSingular: string; @@ -17,7 +17,6 @@ export const RecordIdentifierChip = ({ variant, }: RecordIdentifierChipProps) => { const { onIndexIdentifierClick } = useContext(RecordIndexRootPropsContext); - const { recordChipData } = useRecordChipData({ objectNameSingular, record, @@ -27,6 +26,8 @@ export const RecordIdentifierChip = ({ onIndexIdentifierClick(record.id); }; + const { Icon: LeftIcon, IconColor: LeftIconColor } = + useGetStandardObjectIcon(objectNameSingular); return ( <AvatarChip placeholderColorSeed={record.id} @@ -35,6 +36,8 @@ export const RecordIdentifierChip = ({ avatarUrl={recordChipData.avatarUrl ?? ''} onClick={handleAvatarChipClick} variant={variant} + LeftIcon={LeftIcon} + LeftIconColor={LeftIconColor} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx index 50bb639f5237..f1dec5e3e7e6 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx @@ -2,9 +2,7 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext'; import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; -import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; -import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu'; import { useContext } from 'react'; type RecordIndexTableContainerProps = { @@ -37,9 +35,7 @@ export const RecordIndexTableContainer = ({ viewBarId={viewBarId} updateRecordMutation={updateEntity} /> - <RecordTableActionBar recordTableId={recordTableId} /> <RecordIndexRemoveSortingModal recordTableId={recordTableId} /> - <RecordTableContextMenu recordTableId={recordTableId} /> </> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx index 428537f69467..ce5dc7279ded 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx @@ -1,16 +1,14 @@ import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar'; import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter'; import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort'; -import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView'; -import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState'; type RecordIndexTableContainerEffectProps = { objectNameSingular: string; @@ -26,7 +24,6 @@ export const RecordIndexTableContainerEffect = ({ const { setAvailableTableColumns, setOnEntityCountChange, - resetTableRowSelection, selectedRowIdsSelector, setOnToggleColumnFilter, setOnToggleColumnSort, @@ -34,6 +31,14 @@ export const RecordIndexTableContainerEffect = ({ recordTableId, }); + const setContextStoreTargetedRecordIds = useSetRecoilState( + contextStoreTargetedRecordIdsState, + ); + + const setContextStoreCurrentObjectMetadataItem = useSetRecoilState( + contextStoreCurrentObjectMetadataIdState, + ); + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); @@ -48,34 +53,8 @@ export const RecordIndexTableContainerEffect = ({ setAvailableTableColumns(columnDefinitions); }, [columnDefinitions, setAvailableTableColumns]); - const { tableRowIdsState, hasUserSelectedAllRowsState } = - useRecordTableStates(recordTableId); - - // TODO: verify this instance id works - const entityCountInCurrentView = useRecoilComponentValueV2( - entityCountInCurrentViewComponentState, - recordTableId, - ); - const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState); - const tableRowIds = useRecoilValue(tableRowIdsState); - const selectedRowIds = useRecoilValue(selectedRowIdsSelector()); - const numSelected = - hasUserSelectedAllRows && entityCountInCurrentView - ? selectedRowIds.length === tableRowIds.length - ? entityCountInCurrentView - : entityCountInCurrentView - - (tableRowIds.length - selectedRowIds.length) // unselected row Ids - : selectedRowIds.length; - - const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({ - objectMetadataItem, - selectedRecordIds: selectedRowIds, - callback: resetTableRowSelection, - totalNumberOfRecordsSelected: numSelected, - }); - const handleToggleColumnFilter = useHandleToggleColumnFilter({ objectNameSingular, viewBarId, @@ -100,16 +79,26 @@ export const RecordIndexTableContainerEffect = ({ ); }, [setOnToggleColumnSort, handleToggleColumnSort]); - useEffect(() => { - setActionBarEntries?.(); - setContextMenuEntries?.(); - }, [setActionBarEntries, setContextMenuEntries]); - useEffect(() => { setOnEntityCountChange( () => (entityCount: number) => setRecordCountInCurrentView(entityCount), ); }, [setRecordCountInCurrentView, setOnEntityCountChange]); + useEffect(() => { + setContextStoreTargetedRecordIds(selectedRowIds); + setContextStoreCurrentObjectMetadataItem(objectMetadataItem?.id); + + return () => { + setContextStoreTargetedRecordIds([]); + setContextStoreCurrentObjectMetadataItem(null); + }; + }, [ + objectMetadataItem?.id, + selectedRowIds, + setContextStoreCurrentObjectMetadataItem, + setContextStoreTargetedRecordIds, + ]); + return <></>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts index 41d4fc49d99c..f15fe3ea9661 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleColumnFilter.ts @@ -5,7 +5,8 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; -import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters'; import { isDefined } from '~/utils/isDefined'; @@ -42,7 +43,15 @@ export const useHandleToggleColumnFilter = ({ correspondingColumnDefinition?.type, ); - const availableOperandsForFilter = getOperandsForFilterType(filterType); + const filterDefinition = { + label: correspondingColumnDefinition.label, + iconName: correspondingColumnDefinition.iconName, + fieldMetadataId, + type: filterType, + } satisfies FilterDefinition; + + const availableOperandsForFilter = + getOperandsForFilterDefinition(filterDefinition); const defaultOperand = availableOperandsForFilter[0]; @@ -51,12 +60,7 @@ export const useHandleToggleColumnFilter = ({ fieldMetadataId, operand: defaultOperand, displayValue: '', - definition: { - label: correspondingColumnDefinition.label, - iconName: correspondingColumnDefinition.iconName, - fieldMetadataId, - type: filterType, - }, + definition: filterDefinition, 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/hooks/useRecordIndexPageKanbanAddButton.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordIndexPageKanbanAddButton.ts deleted file mode 100644 index 81e188996edc..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordIndexPageKanbanAddButton.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; -import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; -import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; -import { useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-ui'; - -type useRecordIndexPageKanbanAddButtonProps = { - objectNamePlural: string; -}; - -export const useRecordIndexPageKanbanAddButton = ({ - objectNamePlural, -}: useRecordIndexPageKanbanAddButtonProps) => { - const { objectNameSingular } = useObjectNameSingularFromPlural({ - objectNamePlural, - }); - const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); - - const recordIndexKanbanFieldMetadataId = useRecoilValue( - recordIndexKanbanFieldMetadataIdState, - ); - const { createOneRecord } = useCreateOneRecord({ objectNameSingular }); - - const selectFieldMetadataItem = objectMetadataItem.fields.find( - (field) => field.id === recordIndexKanbanFieldMetadataId, - ); - const isOpportunity = - objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity; - - const createOpportunity = ( - company: EntityForSelect, - columnDefinition: RecordGroupDefinition, - ) => { - if (isDefined(selectFieldMetadataItem)) { - createOneRecord({ - name: company.name, - companyId: company.id, - position: 'first', - [selectFieldMetadataItem.name]: columnDefinition?.value, - }); - } - }; - - const createRecordWithoutCompany = ( - columnDefinition: RecordGroupDefinition, - ) => { - if (isDefined(selectFieldMetadataItem)) { - createOneRecord({ - [selectFieldMetadataItem.name]: columnDefinition?.value, - position: 'first', - }); - } - }; - - return { - selectFieldMetadataItem, - isOpportunity, - createOpportunity, - createRecordWithoutCompany, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/constants/ExportTableDataDefaultPageSize.ts b/packages/twenty-front/src/modules/object-record/record-index/options/constants/ExportTableDataDefaultPageSize.ts new file mode 100644 index 000000000000..1a58deeb28ba --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/options/constants/ExportTableDataDefaultPageSize.ts @@ -0,0 +1 @@ +export const EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE = 200; 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 bac46e65238e..aa9f392782f0 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,17 +1,18 @@ -import { act, renderHook, waitFor } from '@testing-library/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 { ReactNode } from 'react'; -import { BrowserRouter as Router } from 'react-router-dom'; -import { RecoilRoot, useRecoilValue } from 'recoil'; +import { useRecoilValue } from 'recoil'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const defaultResponseData = { pageInfo: { @@ -22,11 +23,15 @@ const defaultResponseData = { }, totalCount: 1, }; + const mockPerson = { __typename: 'Person', updatedAt: '2021-08-03T19:20:06.000Z', - myCustomObjectId: '123', - whatsapp: '123', + whatsapp: { + primaryPhoneNumber: '+1', + primaryPhoneCountryCode: '234-567-890', + additionalPhones: [], + }, linkedinLink: { primaryLinkUrl: 'https://www.linkedin.com', primaryLinkLabel: 'linkedin', @@ -36,7 +41,10 @@ const mockPerson = { firstName: 'firstName', lastName: 'lastName', }, - email: 'email', + emails: { + primaryEmail: 'email', + additionalEmails: [], + }, position: 'position', createdBy: { source: 'source', @@ -52,13 +60,19 @@ const mockPerson = { }, performanceRating: 1, createdAt: '2021-08-03T19:20:06.000Z', - phone: 'phone', + phones: { + primaryPhoneNumber: '+1', + primaryPhoneCountryCode: '234-567-890', + additionalPhones: [], + }, id: '123', city: 'city', companyId: '1', intro: 'intro', - workPrefereance: 'workPrefereance', + deletedAt: null, + workPreference: 'workPreference', }; + const mocks: MockedResponse[] = [ { request: { @@ -77,41 +91,7 @@ const mocks: MockedResponse[] = [ ) { edges { node { - __typename - updatedAt - myCustomObjectId - whatsapp - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - name { - firstName - lastName - } - email - position - createdBy { - source - workspaceMemberId - name - } - avatarUrl - jobTitle - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - performanceRating - createdAt - phone - id - city - companyId - intro - workPrefereance + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -147,21 +127,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - <SnackBarManagerScopeInternalContext.Provider - value={{ - scopeId: 'snack-bar-manager', - }} - > - <Router> - <RecoilRoot> - <MockedProvider addTypename={false} mocks={mocks}> - {children} - </MockedProvider> - </RecoilRoot> - </Router> - </SnackBarManagerScopeInternalContext.Provider> -); +const WrapperWithResponse = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const graphqlEmptyResponse = [ { @@ -177,21 +145,9 @@ const graphqlEmptyResponse = [ }, ]; -const WrapperWithEmptyResponse = ({ children }: { children: ReactNode }) => ( - <SnackBarManagerScopeInternalContext.Provider - value={{ - scopeId: 'snack-bar-manager', - }} - > - <Router> - <RecoilRoot> - <MockedProvider addTypename={false} mocks={graphqlEmptyResponse}> - {children} - </MockedProvider> - </RecoilRoot> - </Router> - </SnackBarManagerScopeInternalContext.Provider> -); +const WrapperWithEmptyResponse = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: graphqlEmptyResponse, +}); describe('useTableData', () => { const recordIndexId = 'people'; @@ -205,6 +161,7 @@ describe('useTableData', () => { useTableData({ recordIndexId, objectNameSingular, + pageSize: 30, callback, delayMs: 0, viewType: ViewType.Kanban, @@ -229,10 +186,11 @@ describe('useTableData', () => { recordIndexId, objectNameSingular, callback, + pageSize: 30, delayMs: 0, }), - { wrapper: Wrapper }, + { wrapper: WrapperWithResponse }, ); await act(async () => { @@ -262,7 +220,7 @@ describe('useTableData', () => { delayMs: 0, viewType: ViewType.Kanban, }), - setKanbanFieldName: useRecordBoard(recordIndexId), + useRecordBoardHook: useRecordBoard(recordIndexId), kanbanFieldName: useRecoilValue(kanbanFieldNameState), kanbanData: useRecordIndexOptionsForBoard({ objectNameSingular, @@ -272,13 +230,21 @@ describe('useTableData', () => { }; }, { - wrapper: Wrapper, + wrapper: WrapperWithResponse, }, ); + const personObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + const updatedAtFieldMetadataItem = personObjectMetadataItem?.fields.find( + (field) => field.name === 'updatedAt', + ); + await act(async () => { - result.current.setKanbanFieldName.setKanbanFieldMetadataName( - result.current.kanbanData.hiddenBoardFields[0].metadata.fieldName, + result.current.useRecordBoardHook.setKanbanFieldMetadataName( + updatedAtFieldMetadataItem?.name, ); }); @@ -293,7 +259,7 @@ describe('useTableData', () => { { defaultValue: 'now', editButtonIcon: undefined, - fieldMetadataId: '102963b7-3e77-4293-a1e6-1ab59a02b663', + fieldMetadataId: updatedAtFieldMetadataItem?.id, iconName: 'IconCalendarClock', isFilterable: true, isLabelIdentifier: false, @@ -312,8 +278,14 @@ describe('useTableData', () => { relationObjectMetadataNameSingular: '', relationType: undefined, targetFieldMetadataName: '', + settings: { + displayAsRelativeDate: true, + }, + }, + position: expect.any(Number), + settings: { + displayAsRelativeDate: true, }, - position: 0, showLabel: undefined, size: 100, type: 'DATE_TIME', @@ -351,7 +323,7 @@ describe('useTableData', () => { }; }, { - wrapper: Wrapper, + wrapper: WrapperWithResponse, }, ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts index 2c8e8f6d5270..345e11453892 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts @@ -1,53 +1,27 @@ import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; -import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds'; import { UseTableDataOptions } from '@/object-record/record-index/options/hooks/useTableData'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; -import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; -import { useRecoilValue } from 'recoil'; -type UseDeleteTableDataOptions = Omit<UseTableDataOptions, 'callback'>; +type UseDeleteTableDataOptions = Pick< + UseTableDataOptions, + 'objectNameSingular' | 'recordIndexId' +>; export const useDeleteTableData = ({ objectNameSingular, recordIndexId, }: UseDeleteTableDataOptions) => { - const { fetchAllRecordIds } = useFetchAllRecordIds({ - objectNameSingular, + const { resetTableRowSelection } = useRecordTable({ + recordTableId: recordIndexId, }); - const { resetTableRowSelection, hasUserSelectedAllRowsState } = - useRecordTable({ - recordTableId: recordIndexId, - }); - - const tableRowIds = useRecoilValue( - tableRowIdsComponentState({ - scopeId: getScopeIdFromComponentId(recordIndexId), - }), - ); - const { deleteManyRecords } = useDeleteManyRecords({ objectNameSingular, }); const { favorites, deleteFavorite } = useFavorites(); - const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState); - const deleteRecords = async (recordIdsToDelete: string[]) => { - if (hasUserSelectedAllRows) { - const allRecordIds = await fetchAllRecordIds(); - - const unselectedRecordIds = tableRowIds.filter( - (recordId) => !recordIdsToDelete.includes(recordId), - ); - - recordIdsToDelete = allRecordIds.filter( - (recordId) => !unselectedRecordIds.includes(recordId), - ); - } - resetTableRowSelection(); for (const recordIdToDelete of recordIdsToDelete) { diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts index 8a535604dee8..532b8e0aa59b 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useExportTableData.ts @@ -2,6 +2,7 @@ import { json2csv } from 'json-2-csv'; import { useMemo } from 'react'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize'; import { useProcessRecordsForCSVExport } from '@/object-record/record-index/options/hooks/useProcessRecordsForCSVExport'; import { useTableData, @@ -142,7 +143,7 @@ export const useExportTableData = ({ filename, maximumRequests = 100, objectNameSingular, - pageSize = 30, + pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE, recordIndexId, viewType, }: UseExportTableDataOptions) => { diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts index 1e6255276919..98294115c5d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts @@ -9,6 +9,7 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isDefined } from '~/utils/isDefined'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize'; import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard'; import { ViewType } from '@/views/types/ViewType'; import { useFindManyParams } from '../../hooks/useLoadRecordIndexTable'; @@ -43,7 +44,7 @@ export const useTableData = ({ delayMs, maximumRequests = 100, objectNameSingular, - pageSize = 30, + pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE, recordIndexId, callback, viewType = ViewType.Table, diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx index e2c8b2bcfa03..55f3ccbf751e 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx @@ -24,15 +24,13 @@ import { type RecordInlineCellProps = { readonly?: boolean; loading?: boolean; - isCentered?: boolean; }; export const RecordInlineCell = ({ readonly, loading, - isCentered, }: RecordInlineCellProps) => { - const { fieldDefinition, recordId } = useContext(FieldContext); + const { fieldDefinition, recordId, isCentered } = useContext(FieldContext); const buttonIcon = useGetButtonIcon(); const isFieldInputOnly = useIsFieldInputOnly(); @@ -90,7 +88,7 @@ export const RecordInlineCell = ({ label: fieldDefinition.label, labelWidth: fieldDefinition.labelWidth, showLabel: fieldDefinition.showLabel, - isCentered: isCentered, + isCentered, editModeContent: ( <FieldInput recordFieldInputdId={getRecordFieldInputId( diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx index c77c19fa88c4..740039656f2d 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellContainer.tsx @@ -4,8 +4,8 @@ import { ReactElement, useContext } from 'react'; import { AppTooltip, IconComponent, - TooltipDelay, OverflowingTextWithTooltip, + TooltipDelay, } from 'twenty-ui'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -42,6 +42,7 @@ const StyledValueContainer = styled.div` display: flex; flex-grow: 1; min-width: 0; + position: relative; `; const StyledLabelContainer = styled.div<{ width?: number }>` @@ -55,11 +56,9 @@ const StyledInlineCellBaseContainer = styled.div` box-sizing: border-box; width: 100%; display: flex; - + height: 24px; gap: ${({ theme }) => theme.spacing(1)}; - user-select: none; - justify-content: center; `; @@ -81,7 +80,6 @@ export type RecordInlineCellContainerProps = { isDisplayModeFixHeight?: boolean; disableHoverEffect?: boolean; loading?: boolean; - isCentered?: boolean; }; export const RecordInlineCellContainer = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditMode.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditMode.tsx index 7d6c6e4d50ed..a6a4bec4559d 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditMode.tsx @@ -1,17 +1,16 @@ +import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext'; import styled from '@emotion/styled'; import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; import { useContext } from 'react'; import { createPortal } from 'react-dom'; -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; - const StyledInlineCellEditModeContainer = styled.div` align-items: center; display: flex; + width: 100%; + position: absolute; height: 24px; - - margin-left: -${({ theme }) => theme.spacing(1)}; `; const StyledInlineCellInput = styled.div` @@ -34,21 +33,21 @@ type RecordInlineCellEditModeProps = { export const RecordInlineCellEditMode = ({ children, }: RecordInlineCellEditModeProps) => { - const { isCentered } = useContext(FieldContext); + const { isCentered } = useContext(RecordInlineCellContext); const { refs, floatingStyles } = useFloating({ - placement: isCentered ? undefined : 'right-start', + placement: isCentered ? 'bottom' : 'bottom-start', middleware: [ flip(), offset( isCentered ? { - mainAxis: -32, - crossAxis: 160, + mainAxis: -26, + crossAxis: 0, } : { + mainAxis: -28, crossAxis: -4, - mainAxis: -4, }, ), ], 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} > <StyledSkeletonDiv> - <Skeleton width={154} height={16} /> + <Skeleton + width={154} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </StyledSkeletonDiv> </SkeletonTheme> ); 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 }) => ( <StyledSkeletonDiv key={id}> - <Skeleton width={92} height={16} /> - <Skeleton width={154} height={16} /> + <Skeleton + width={92} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> + <Skeleton + width={154} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </StyledSkeletonDiv> ))} </SkeletonTheme> 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 49b27cdfe3eb..3a747b0913a1 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 @@ -1,23 +1,34 @@ import { useRecoilValue } from 'recoil'; +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 { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import styled from '@emotion/styled'; + +const StyledRightDrawerRecord = styled.div` + height: ${({ theme }) => + useIsMobile() ? `calc(100% - ${theme.spacing(16)})` : '100%'}; +`; export const RightDrawerRecord = () => { const viewableRecordNameSingular = useRecoilValue( viewableRecordNameSingularState, ); + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); const viewableRecordId = useRecoilValue(viewableRecordIdState); - if (!viewableRecordNameSingular) { + if (!viewableRecordNameSingular && !isNewViewableRecordLoading) { throw new Error(`Object name is not defined`); } - if (!viewableRecordId) { + if (!viewableRecordId && !isNewViewableRecordLoading) { throw new Error(`Record id is not defined`); } @@ -27,14 +38,19 @@ export const RightDrawerRecord = () => { ); return ( - <RecordFieldValueSelectorContextProvider> - <RecordValueSetterEffect recordId={objectRecordId} /> - <RecordShowContainer - objectNameSingular={objectNameSingular} - objectRecordId={objectRecordId} - loading={false} - isInRightDrawer={true} - /> - </RecordFieldValueSelectorContextProvider> + <StyledRightDrawerRecord> + <RecordFieldValueSelectorContextProvider> + {!isNewViewableRecordLoading && ( + <RecordValueSetterEffect recordId={objectRecordId} /> + )} + <RecordShowContainer + objectNameSingular={objectNameSingular} + objectRecordId={objectRecordId} + loading={false} + isInRightDrawer={true} + isNewRightDrawerItemLoading={isNewViewableRecordLoading} + /> + </RecordFieldValueSelectorContextProvider> + </StyledRightDrawerRecord> ); }; 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<boolean>({ + 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 4829831086e2..9b1e10601ab4 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 @@ -5,6 +5,7 @@ import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/A import { Note } from '@/activities/types/Note'; import { Task } from '@/activities/types/Task'; import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord'; +import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -25,12 +26,14 @@ import { RecordDetailRelationSection } from '@/object-record/record-show/record- import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; 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, @@ -45,6 +48,7 @@ type RecordShowContainerProps = { objectRecordId: string; loading: boolean; isInRightDrawer?: boolean; + isNewRightDrawerItemLoading?: boolean; }; export const RecordShowContainer = ({ @@ -52,6 +56,7 @@ export const RecordShowContainer = ({ objectRecordId, loading, isInRightDrawer = false, + isNewRightDrawerItemLoading = false, }: RecordShowContainerProps) => { const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, @@ -68,7 +73,7 @@ export const RecordShowContainer = ({ recordLoadingFamilyState(objectRecordId), ); - const [recordFromStore] = useRecoilState<any>( + const [recordFromStore] = useRecoilState<ObjectRecord | null>( recordStoreFamilyState(objectRecordId), ); @@ -78,7 +83,6 @@ export const RecordShowContainer = ({ recordId: objectRecordId, }), ); - const [uploadImage] = useUploadImageMutation(); const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular }); @@ -156,55 +160,58 @@ export const RecordShowContainer = ({ objectNameSingular !== CoreObjectNameSingular.Task && fieldMetadataItem.name !== 'taskTargets', ); - + const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular); const isReadOnly = objectMetadataItem.isRemote; const isMobile = useIsMobile() || isInRightDrawer; const isPrefetchLoading = useIsPrefetchLoading(); - const summaryCard = isDefined(recordFromStore) ? ( - <ShowPageSummaryCard - isMobile={isMobile} - id={objectRecordId} - logoOrAvatar={recordIdentifier?.avatarUrl ?? ''} - avatarPlaceholder={recordIdentifier?.name ?? ''} - date={recordFromStore.createdAt ?? ''} - loading={isPrefetchLoading || loading || recordLoading} - title={ - <FieldContext.Provider - value={{ - recordId: objectRecordId, - recoilScopeId: - objectRecordId + labelIdentifierFieldMetadataItem?.id, - isLabelIdentifier: false, - fieldDefinition: { - type: - labelIdentifierFieldMetadataItem?.type || - FieldMetadataType.Text, - iconName: '', - fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '', - label: labelIdentifierFieldMetadataItem?.label || '', - metadata: { - fieldName: labelIdentifierFieldMetadataItem?.name || '', - objectMetadataNameSingular: objectNameSingular, + const summaryCard = + !isNewRightDrawerItemLoading && isDefined(recordFromStore) ? ( + <ShowPageSummaryCard + isMobile={isMobile} + id={objectRecordId} + logoOrAvatar={recordIdentifier?.avatarUrl ?? ''} + icon={Icon} + iconColor={IconColor} + avatarPlaceholder={recordIdentifier?.name ?? ''} + date={recordFromStore.createdAt ?? ''} + loading={isPrefetchLoading || loading || recordLoading} + title={ + <FieldContext.Provider + value={{ + recordId: objectRecordId, + recoilScopeId: + objectRecordId + labelIdentifierFieldMetadataItem?.id, + isLabelIdentifier: false, + fieldDefinition: { + type: + labelIdentifierFieldMetadataItem?.type || + FieldMetadataType.Text, + iconName: '', + fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '', + label: labelIdentifierFieldMetadataItem?.label || '', + metadata: { + fieldName: labelIdentifierFieldMetadataItem?.name || '', + objectMetadataNameSingular: objectNameSingular, + }, + defaultValue: labelIdentifierFieldMetadataItem?.defaultValue, }, - defaultValue: labelIdentifierFieldMetadataItem?.defaultValue, - }, - useUpdateRecord: useUpdateOneObjectRecordMutation, - hotkeyScope: InlineCellHotkeyScope.InlineCell, - isCentered: true, - }} - > - <RecordInlineCell readonly={isReadOnly} isCentered={true} /> - </FieldContext.Provider> - } - avatarType={recordIdentifier?.avatarType ?? 'rounded'} - onUploadPicture={ - objectNameSingular === 'person' ? onUploadPicture : undefined - } - /> - ) : ( - <></> - ); + useUpdateRecord: useUpdateOneObjectRecordMutation, + hotkeyScope: InlineCellHotkeyScope.InlineCell, + isCentered: !isMobile, + }} + > + <RecordInlineCell readonly={isReadOnly} /> + </FieldContext.Provider> + } + avatarType={recordIdentifier?.avatarType ?? 'rounded'} + onUploadPicture={ + objectNameSingular === 'person' ? onUploadPicture : undefined + } + /> + ) : ( + <ShowPageSummaryCardSkeletonLoader /> + ); const fieldsBox = ( <> diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts index 7bdaf0b4284b..fb73b8ce9a52 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts @@ -23,10 +23,10 @@ export const useRecordShowPage = ( objectRecordId: paramObjectRecordId, } = useParams(); - const objectNameSingular = propsObjectNameSingular || paramObjectNameSingular; - const objectRecordId = propsObjectRecordId || paramObjectRecordId; + const objectNameSingular = propsObjectNameSingular ?? paramObjectNameSingular; + const objectRecordId = propsObjectRecordId ?? paramObjectRecordId; - if (!objectNameSingular || !objectRecordId) { + if (!isDefined(objectNameSingular) || !isDefined(objectRecordId)) { throw new Error('Object name or Record id is not defined'); } diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState.tsx deleted file mode 100644 index a47aa9de0393..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { useIcons } from 'twenty-ui'; - -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -type RecordDetailRelationRecordsListEmptyStateProps = { - relationObjectMetadataItem: ObjectMetadataItem; -}; - -const StyledRelationRecordsListEmptyState = styled.div` - color: ${({ theme }) => theme.font.color.light}; - align-items: center; - justify-content: center; - gap: ${({ theme }) => theme.spacing(2)}; - display: flex; - height: ${({ theme }) => theme.spacing(10)}; - text-transform: capitalize; -`; - -export const RecordDetailRelationRecordsListEmptyState = ({ - relationObjectMetadataItem, -}: RecordDetailRelationRecordsListEmptyStateProps) => { - const theme = useTheme(); - - const { getIcon } = useIcons(); - const Icon = getIcon(relationObjectMetadataItem.icon); - - return ( - <StyledRelationRecordsListEmptyState> - <Icon size={theme.icon.size.sm} /> - <div>No {relationObjectMetadataItem.labelSingular}</div> - </StyledRelationRecordsListEmptyState> - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx index 36a3d7a1a2fe..eefeb3540878 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx @@ -24,6 +24,7 @@ import { } from '@/object-record/record-field/contexts/FieldContext'; import { usePersistField } from '@/object-record/record-field/hooks/usePersistField'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; @@ -180,6 +181,8 @@ export const RecordDetailRelationRecordsListItem = ({ [isExpanded], ); + const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata); + return ( <> <RecordValueSetterEffect recordId={relationRecord.id} /> @@ -195,37 +198,39 @@ export const RecordDetailRelationRecordsListItem = ({ accent="tertiary" /> </StyledClickableZone> - <DropdownScope dropdownScopeId={dropdownScopeId}> - <Dropdown - dropdownId={dropdownScopeId} - dropdownPlacement="right-start" - clickableComponent={ - <LightIconButton - className="displayOnHover" - Icon={IconDotsVertical} - accent="tertiary" - /> - } - dropdownComponents={ - <DropdownMenuItemsContainer> - <MenuItem - LeftIcon={IconUnlink} - text="Detach" - onClick={handleDetach} + {canEdit && ( + <DropdownScope dropdownScopeId={dropdownScopeId}> + <Dropdown + dropdownId={dropdownScopeId} + dropdownPlacement="right-start" + clickableComponent={ + <LightIconButton + className="displayOnHover" + Icon={IconDotsVertical} + accent="tertiary" /> - {!isAccountOwnerRelation && ( + } + dropdownComponents={ + <DropdownMenuItemsContainer> <MenuItem - LeftIcon={IconTrash} - text="Delete" - accent="danger" - onClick={handleDelete} + LeftIcon={IconUnlink} + text="Detach" + onClick={handleDetach} /> - )} - </DropdownMenuItemsContainer> - } - dropdownHotkeyScope={{ scope: dropdownScopeId }} - /> - </DropdownScope> + {!isAccountOwnerRelation && ( + <MenuItem + LeftIcon={IconTrash} + text="Delete" + accent="danger" + onClick={handleDelete} + /> + )} + </DropdownMenuItemsContainer> + } + dropdownHotkeyScope={{ scope: dropdownScopeId }} + /> + </DropdownScope> + )} </StyledListItem> <AnimatedEaseInOut isOpen={isExpanded}> <PropertyBox> diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx index 54e21a486f19..81e0c37c76e7 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx @@ -12,9 +12,8 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi import { RelationFromManyFieldInputMultiRecordsEffect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly'; import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList'; -import { RecordDetailRelationRecordsListEmptyState } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState'; -import { RecordDetailRelationSectionSkeletonLoader } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionSkeletonLoader'; import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection'; import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -26,12 +25,15 @@ import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRela import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; +import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; +import { View } from '@/views/types/View'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { RelationDefinitionType } from '~/generated-metadata/graphql'; @@ -71,7 +73,7 @@ export const RecordDetailRelationSection = ({ // TODO: use new relation type const isToOneObject = relationType === RelationDefinitionType.ManyToOne; - const isToManyObjects = RelationDefinitionType.OneToMany; + const isToManyObjects = relationType === RelationDefinitionType.OneToMany; const relationRecords: ObjectRecord[] = fieldValue && isToOneObject @@ -121,32 +123,31 @@ export const RecordDetailRelationSection = ({ scopeId: dropdownId, }); + const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews); + + const indexView = views.find( + (view) => + view.key === 'INDEX' && + view.objectMetadataId === relationObjectMetadataItem.id, + ); + const filterQueryParams: FilterQueryParams = { filter: { [relationFieldMetadataItem?.name || '']: { [ViewFilterOperand.Is]: [recordId], }, }, + view: indexView?.id, }; const filterLinkHref = `/objects/${ relationObjectMetadataItem.namePlural }?${qs.stringify(filterQueryParams)}`; const showContent = () => { - if (loading) { - return ( - <RecordDetailRelationSectionSkeletonLoader - numSkeletons={fieldName === 'people' ? 2 : 1} - /> - ); - } - - return relationRecords.length ? ( - <RecordDetailRelationRecordsList relationRecords={relationRecords} /> - ) : ( - <RecordDetailRelationRecordsListEmptyState - relationObjectMetadataItem={relationObjectMetadataItem} - /> + return ( + relationRecords.length > 0 && ( + <RecordDetailRelationRecordsList relationRecords={relationRecords} /> + ) ); }; @@ -158,6 +159,10 @@ export const RecordDetailRelationSection = ({ recordId, }); + const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata); + + if (loading) return null; + return ( <RecordDetailSection> <RecordDetailSectionHeader @@ -166,55 +171,61 @@ export const RecordDetailRelationSection = ({ isToManyObjects ? { to: filterLinkHref, - label: `All (${relationRecords.length})`, + label: + relationRecords.length > 0 + ? `All (${relationRecords.length})` + : '', } : undefined } hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile} + areRecordsAvailable={relationRecords.length > 0} rightAdornment={ - <DropdownScope dropdownScopeId={dropdownId}> - <StyledAddDropdown - dropdownId={dropdownId} - dropdownPlacement="right-start" - onClose={handleCloseRelationPickerDropdown} - clickableComponent={ - <LightIconButton - className="displayOnHover" - Icon={isToOneObject ? IconPencil : IconPlus} - accent="tertiary" - /> - } - dropdownComponents={ - <RelationPickerScope relationPickerScopeId={dropdownId}> - {isToOneObject ? ( - <SingleEntitySelectMenuItemsWithSearch - EmptyIcon={IconForbid} - onEntitySelected={handleRelationPickerEntitySelected} - selectedRelationRecordIds={relationRecordIds} - relationObjectNameSingular={ - relationObjectMetadataNameSingular - } - relationPickerScopeId={dropdownId} - onCreate={createNewRecordAndOpenRightDrawer} - /> - ) : ( - <> - <ObjectMetadataItemsRelationPickerEffect /> - <RelationFromManyFieldInputMultiRecordsEffect /> - <MultiRecordSelect + canEdit && ( + <DropdownScope dropdownScopeId={dropdownId}> + <StyledAddDropdown + dropdownId={dropdownId} + dropdownPlacement="right-start" + onClose={handleCloseRelationPickerDropdown} + clickableComponent={ + <LightIconButton + className="displayOnHover" + Icon={isToOneObject ? IconPencil : IconPlus} + accent="tertiary" + /> + } + dropdownComponents={ + <RelationPickerScope relationPickerScopeId={dropdownId}> + {isToOneObject ? ( + <SingleEntitySelectMenuItemsWithSearch + EmptyIcon={IconForbid} + onEntitySelected={handleRelationPickerEntitySelected} + selectedRelationRecordIds={relationRecordIds} + relationObjectNameSingular={ + relationObjectMetadataNameSingular + } + relationPickerScopeId={dropdownId} onCreate={createNewRecordAndOpenRightDrawer} - onChange={updateRelation} - onSubmit={closeDropdown} /> - </> - )} - </RelationPickerScope> - } - dropdownHotkeyScope={{ - scope: dropdownId, - }} - /> - </DropdownScope> + ) : ( + <> + <ObjectMetadataItemsRelationPickerEffect /> + <RelationFromManyFieldInputMultiRecordsEffect /> + <MultiRecordSelect + onCreate={createNewRecordAndOpenRightDrawer} + onChange={updateRelation} + onSubmit={closeDropdown} + /> + </> + )} + </RelationPickerScope> + } + dropdownHotkeyScope={{ + scope: dropdownId, + }} + /> + </DropdownScope> + ) } /> {showContent()} diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionSkeletonLoader.tsx deleted file mode 100644 index 12ba33e3b210..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionSkeletonLoader.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; - -const StyledSkeletonDiv = styled.div` - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(4)}; - height: 40px; -`; - -export const RecordDetailRelationSectionSkeletonLoader = ({ - numSkeletons = 1, -}: { - numSkeletons?: number; -}) => { - const theme = useTheme(); - return ( - <SkeletonTheme - baseColor={theme.background.tertiary} - highlightColor={theme.background.transparent.lighter} - borderRadius={4} - > - <StyledSkeletonDiv> - {Array.from({ length: numSkeletons }).map((_, index) => ( - <Skeleton key={index} width={129} height={16} /> - ))} - </StyledSkeletonDiv> - </SkeletonTheme> - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader.tsx index b9087184b92a..158a80e6721e 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader.tsx @@ -1,12 +1,16 @@ +import styled from '@emotion/styled'; import { useState } from 'react'; import { Link } from 'react-router-dom'; -import styled from '@emotion/styled'; -const StyledHeader = styled.header<{ isDropdownOpen?: boolean }>` +const StyledHeader = styled.header<{ + isDropdownOpen?: boolean; + areRecordsAvailable?: boolean; +}>` align-items: center; display: flex; height: 24px; - margin-bottom: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme, areRecordsAvailable }) => + areRecordsAvailable && theme.spacing(2)}; `; const StyledTitle = styled.div` @@ -34,6 +38,7 @@ type RecordDetailSectionHeaderProps = { link?: { to: string; label: string }; rightAdornment?: React.ReactNode; hideRightAdornmentOnMouseLeave?: boolean; + areRecordsAvailable?: boolean; }; export const RecordDetailSectionHeader = ({ @@ -41,11 +46,13 @@ export const RecordDetailSectionHeader = ({ link, rightAdornment, hideRightAdornmentOnMouseLeave = true, + areRecordsAvailable = false, }: RecordDetailSectionHeaderProps) => { const [isHovered, setIsHovered] = useState(false); return ( <StyledHeader + areRecordsAvailable={areRecordsAvailable} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > @@ -53,7 +60,9 @@ export const RecordDetailSectionHeader = ({ <StyledTitleLabel>{title}</StyledTitleLabel> {link && <StyledLink to={link.to}>{link.label}</StyledLink>} </StyledTitle> - {hideRightAdornmentOnMouseLeave && !isHovered ? null : rightAdornment} + {hideRightAdornmentOnMouseLeave && !isHovered && areRecordsAvailable + ? null + : rightAdornment} </StyledHeader> ); }; 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<typeof RecordDetailRelationSection> = { title: 'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailRelationSection', diff --git a/packages/twenty-front/src/modules/object-record/record-table/action-bar/components/RecordTableActionBar.tsx b/packages/twenty-front/src/modules/object-record/record-table/action-bar/components/RecordTableActionBar.tsx deleted file mode 100644 index 0b2c810bc15c..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/action-bar/components/RecordTableActionBar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; -import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState'; - -export const RecordTableActionBar = ({ - recordTableId, -}: { - recordTableId: string; -}) => { - const { - selectedRowIdsSelector, - tableRowIdsState, - hasUserSelectedAllRowsState, - } = useRecordTableStates(recordTableId); - - // TODO: verify this instance id works - const entityCountInCurrentView = useRecoilComponentValueV2( - entityCountInCurrentViewComponentState, - recordTableId, - ); - - const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState); - const tableRowIds = useRecoilValue(tableRowIdsState); - const selectedRowIds = useRecoilValue(selectedRowIdsSelector()); - - const totalNumberOfSelectedRecords = - hasUserSelectedAllRows && entityCountInCurrentView - ? selectedRowIds.length === tableRowIds.length - ? entityCountInCurrentView - : entityCountInCurrentView - - (tableRowIds.length - selectedRowIds.length) // unselected row Ids - : selectedRowIds.length; - - if (!selectedRowIds.length) { - return null; - } - - return ( - <ActionBar - selectedIds={selectedRowIds} - totalNumberOfSelectedRecords={totalNumberOfSelectedRecords} - /> - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index 7f76f10a55a0..f195b2b68649 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -13,10 +13,8 @@ import { useRecoilValue } from 'recoil'; const StyledTable = styled.table` border-radius: ${({ theme }) => theme.border.radius.sm}; border-spacing: 0; - margin-right: ${({ theme }) => theme.table.horizontalCellMargin}; table-layout: fixed; - - width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2); + width: 100%; `; type RecordTableProps = { @@ -72,7 +70,9 @@ export const RecordTable = ({ <RecordTableEmptyState /> ) : ( <StyledTable className="entity-table-cell"> - <RecordTableHeader /> + <RecordTableHeader + objectMetadataNameSingular={objectNameSingular} + /> <RecordTableBody /> </StyledTable> )} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx index 6d5c5b7e0e75..8e827ae3e4e6 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx @@ -12,7 +12,7 @@ import { OpenTableCellArgs, useOpenRecordTableCellV2, } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; -import { useTriggerContextMenu } from '@/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu'; +import { useTriggerActionMenuDropdown } from '@/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown'; import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord'; import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; @@ -75,12 +75,15 @@ export const RecordTableContextProvider = ({ moveSoftFocusToCell(cellPosition); }; - const { triggerContextMenu } = useTriggerContextMenu({ + const { triggerActionMenuDropdown } = useTriggerActionMenuDropdown({ recordTableId, }); - const handleContextMenu = (event: React.MouseEvent, recordId: string) => { - triggerContextMenu(event, recordId); + const handleActionMenuDropdown = ( + event: React.MouseEvent, + recordId: string, + ) => { + triggerActionMenuDropdown(event, recordId); }; const { handleContainerMouseEnter } = useHandleContainerMouseEnter({ @@ -99,7 +102,7 @@ export const RecordTableContextProvider = ({ onMoveFocus: handleMoveFocus, onCloseTableCell: handleCloseTableCell, onMoveSoftFocusToCell: handleMoveSoftFocusToCell, - onContextMenu: handleContextMenu, + onActionMenuDropdownOpened: handleActionMenuDropdown, onCellMouseEnter: handleContainerMouseEnter, visibleTableColumns, recordTableId, diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx index 8b94a325f36a..0dff4b429dca 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx @@ -46,7 +46,7 @@ export const RecordTableInternalEffect = ({ useListenClickOutsideByClassName({ classNames: ['entity-table-cell'], - excludeClassNames: ['action-bar', 'context-menu'], + excludeClassNames: ['bottom-bar', 'context-menu'], callback: () => { resetTableRowSelection(); }, diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx index 268608e183cc..b7a46e64829d 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx @@ -81,7 +81,9 @@ export const RecordTableWithWrappers = ({ /> <DragSelect dragSelectable={tableBodyRef} - onDragSelectionStart={resetTableRowSelection} + onDragSelectionStart={() => { + resetTableRowSelection(); + }} onDragSelectionChange={setRowSelected} /> </StyledTableInternalContainer> 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 44002bd598ab..b03187ceae68 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 @@ -5,7 +5,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; + import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { RecordFieldValueSelectorContextProvider, @@ -21,10 +21,9 @@ 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/generatedMockObjectMetadataItems'; import { mockPerformance } from './mock'; -const objectMetadataItems = getObjectMetadataItemsMock(); - const RelationFieldValueSetterEffect = () => { const setEntity = useSetRecoilState( recordStoreFamilyState(mockPerformance.recordId), @@ -48,7 +47,7 @@ const RelationFieldValueSetterEffect = () => { mockPerformance.relationFieldValue, ); - setObjectMetadataItems(objectMetadataItems); + setObjectMetadataItems(generatedMockObjectMetadataItems); }, [setEntity, setRelationEntity, setRecordValue, setObjectMetadataItems]); return null; @@ -71,7 +70,7 @@ const meta: Meta = { onMoveFocus: () => {}, onCloseTableCell: () => {}, onMoveSoftFocusToCell: () => {}, - onContextMenu: () => {}, + onActionMenuDropdownOpened: () => {}, onCellMouseEnter: () => {}, visibleTableColumns: mockPerformance.visibleTableColumns as any, objectNameSingular: diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts index 52c11a2044d9..ce0253ddab59 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts @@ -662,7 +662,10 @@ export const mockPerformance = { }, id: '20202020-2d40-4e49-8df4-9c6a049191df', email: 'lorie.vladim@google.com', - phone: '+33788901235', + phones: { + primaryPhoneCountryCode: '+33', + primaryPhoneNumber: '788901235', + }, linkedinLink: { __typename: 'Link', primaryLinkLabel: '', diff --git a/packages/twenty-front/src/modules/object-record/record-table/context-menu/components/RecordTableContextMenu.tsx b/packages/twenty-front/src/modules/object-record/record-table/context-menu/components/RecordTableContextMenu.tsx deleted file mode 100644 index d9f712ef82e4..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/context-menu/components/RecordTableContextMenu.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; -import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu'; - -export const RecordTableContextMenu = ({ - recordTableId, -}: { - recordTableId: string; -}) => { - const { selectedRowIdsSelector } = useRecordTableStates(recordTableId); - - const selectedRowIds = useRecoilValue(selectedRowIdsSelector()); - - if (!selectedRowIds.length) { - return null; - } - - return <ContextMenu />; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts index cf8a8afed861..ba2ea9f7a92d 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts @@ -24,7 +24,10 @@ export type RecordTableContextProps = { onMoveFocus: (direction: MoveFocusDirection) => void; onCloseTableCell: () => void; onMoveSoftFocusToCell: (cellPosition: TableCellPosition) => void; - onContextMenu: (event: React.MouseEvent, recordId: string) => void; + onActionMenuDropdownOpened: ( + event: React.MouseEvent, + recordId: string, + ) => void; onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void; visibleTableColumns: ColumnDefinition<FieldMetadata>[]; recordTableId: string; diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx index c25a3cf11990..7ea1deb12616 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx @@ -1,4 +1,3 @@ -import { useObjectIsRemote } from '@/object-metadata/hooks/useObjectIsRemote'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll'; @@ -18,7 +17,7 @@ export const RecordTableEmptyState = () => { const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 }); const noRecordAtAll = totalCount === 0; - const isRemote = useObjectIsRemote(objectMetadataItem); + const isRemote = objectMetadataItem.isRemote; const isSoftDeleteActive = useRecoilValue(isSoftDeleteActiveState); diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx index 80c5ecaefeb1..c5f120a6de63 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx @@ -8,7 +8,10 @@ import { AnimatedPlaceholderEmptyTitle, } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; +import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { Button } from '@/ui/input/button/components/Button'; +import { useContext } from 'react'; import { IconComponent } from 'twenty-ui'; type RecordTableEmptyStateDisplayProps = { @@ -28,6 +31,9 @@ export const RecordTableEmptyStateDisplay = ({ subTitle, title, }: RecordTableEmptyStateDisplayProps) => { + const { objectMetadataItem } = useContext(RecordTableContext); + const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem); + return ( <AnimatedPlaceholderEmptyContainer> <AnimatedPlaceholder type={animatedPlaceholderType} /> @@ -37,12 +43,14 @@ export const RecordTableEmptyStateDisplay = ({ {subTitle} </AnimatedPlaceholderEmptySubTitle> </AnimatedPlaceholderEmptyTextContainer> - <Button - Icon={Icon} - title={buttonTitle} - variant={'secondary'} - onClick={onClick} - /> + {!isReadOnly && ( + <Button + Icon={Icon} + title={buttonTitle} + variant={'secondary'} + onClick={onClick} + /> + )} </AnimatedPlaceholderEmptyContainer> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts index 1b6263739111..58779a443da2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts @@ -1,7 +1,9 @@ import { useRecoilCallback } from 'recoil'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; +import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; export const useResetTableRowSelection = (recordTableId?: string) => { const { @@ -20,7 +22,19 @@ export const useResetTableRowSelection = (recordTableId?: string) => { } set(hasUserSelectedAllRowsState, false); + + const isActionMenuDropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + `action-menu-dropdown-${recordTableId}`, + ); + + set(isActionMenuDropdownOpenState, false); }, - [tableRowIdsState, isRowSelectedFamilyState, hasUserSelectedAllRowsState], + [ + tableRowIdsState, + hasUserSelectedAllRowsState, + recordTableId, + isRowSelectedFamilyState, + ], ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx index 1e977ecaec4e..52ae0772d769 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx @@ -71,10 +71,10 @@ export const RecordTableCellBaseContainer = ({ } }; - const { onContextMenu } = useContext(RecordTableContext); + const { onActionMenuDropdownOpened } = useContext(RecordTableContext); - const handleContextMenu = (event: React.MouseEvent) => { - onContextMenu(event, recordId); + const handleActionMenuDropdown = (event: React.MouseEvent) => { + onActionMenuDropdownOpened(event, recordId); }; const { hotkeyScope } = useContext(FieldContext); @@ -87,7 +87,7 @@ export const RecordTableCellBaseContainer = ({ onMouseLeave={handleContainerMouseLeave} onMouseMove={handleContainerMouseMove} onClick={handleContainerClick} - onContextMenu={handleContextMenu} + onContextMenu={handleActionMenuDropdown} backgroundColorTransparentSecondary={ theme.background.transparent.secondary } diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx index 645e2bc4ab6e..459211537244 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx @@ -1,13 +1,12 @@ import styled from '@emotion/styled'; import { useCallback, useContext } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd'; import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected'; import { Checkbox } from '@/ui/input/components/Checkbox'; -import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; const StyledContainer = styled.div` align-items: center; @@ -24,14 +23,12 @@ export const RecordTableCellCheckbox = () => { const { recordId } = useContext(RecordTableRowContext); const { isRowSelectedFamilyState } = useRecordTableStates(); - const setActionBarOpenState = useSetRecoilState(actionBarOpenState); const { setCurrentRowSelected } = useSetCurrentRowSelected(); const currentRowSelected = useRecoilValue(isRowSelectedFamilyState(recordId)); const handleClick = useCallback(() => { setCurrentRowSelected(!currentRowSelected); - setActionBarOpenState(true); - }, [currentRowSelected, setActionBarOpenState, setCurrentRowSelected]); + }, [currentRowSelected, setCurrentRowSelected]); return ( <RecordTableTd isSelected={isSelected} hasRightBorder={false}> diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx index 16e59db8d559..0e083492f095 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellEditMode.tsx @@ -1,7 +1,6 @@ -import { ReactElement } from 'react'; -import { createPortal } from 'react-dom'; import styled from '@emotion/styled'; import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; +import { ReactElement } from 'react'; const StyledEditableCellEditModeContainer = styled.div<RecordTableCellEditModeProps>` position: absolute; @@ -44,12 +43,9 @@ export const RecordTableCellEditMode = ({ ref={refs.setReference} data-testid="editable-cell-edit-mode-container" > - {createPortal( - <StyledTableCellInput ref={refs.setFloating} style={floatingStyles}> - {children} - </StyledTableCellInput>, - document.body, - )} + <StyledTableCellInput ref={refs.setFloating} style={floatingStyles}> + {children} + </StyledTableCellInput> </StyledEditableCellEditModeContainer> ); }; 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} > - <Skeleton width={width} height={16} /> + <Skeleton + width={width} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} + /> </SkeletonTheme> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx index 49f51197db11..5aca062fbac2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx @@ -43,8 +43,8 @@ const StyledTd = styled.td<{ ${({ freezeFirstColumns }) => freezeFirstColumns ? `@media (max-width: ${MOBILE_VIEWPORT}px) { - width: 35px; - max-width: 35px; + width: 32px; + max-width: 32px; }` : ''} `; 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 1712e01c2033..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 @@ -5,12 +5,12 @@ import { createState } from 'twenty-ui'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; 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/generatedMockObjectMetadataItems'; const draftValue = 'updated Name'; @@ -55,8 +55,6 @@ const updateOneRecordMock = jest.fn(); createOneRecord: createOneRecordMock, }); -const objectMetadataItems = getObjectMetadataItemsMock(); - const Wrapper = ({ children, pendingRecordIdMockedValue, @@ -68,7 +66,7 @@ const Wrapper = ({ }) => ( <RecoilRoot initializeState={(snapshot) => { - snapshot.set(objectMetadataItemsState, objectMetadataItems); + snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue); snapshot.set(draftValueState, draftValueMockedValue); }} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts new file mode 100644 index 000000000000..0870b7bb4d5a --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts @@ -0,0 +1,67 @@ +import { useRecoilCallback } from 'recoil'; + +import { actionMenuDropdownPositionComponentState } from '@/action-menu/states/actionMenuDropdownPositionComponentState'; +import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; +import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; +import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; +import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState'; +import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; + +export const useTriggerActionMenuDropdown = ({ + recordTableId, +}: { + recordTableId: string; +}) => { + const triggerActionMenuDropdown = useRecoilCallback( + ({ set, snapshot }) => + (event: React.MouseEvent, recordId: string) => { + event.preventDefault(); + + const tableScopeId = getScopeIdFromComponentId(recordTableId); + + set( + extractComponentState( + actionMenuDropdownPositionComponentState, + `action-menu-dropdown-${recordTableId}`, + ), + { + x: event.clientX, + y: event.clientY, + }, + ); + + const isRowSelectedFamilyState = extractComponentFamilyState( + isRowSelectedComponentFamilyState, + tableScopeId, + ); + + const isRowSelected = getSnapshotValue( + snapshot, + isRowSelectedFamilyState(recordId), + ); + + if (isRowSelected !== true) { + set(isRowSelectedFamilyState(recordId), true); + } + + const isActionMenuDropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + `action-menu-dropdown-${recordTableId}`, + ); + + const isActionBarOpenState = isBottomBarOpenedComponentState.atomFamily( + { + instanceId: `action-bar-${recordTableId}`, + }, + ); + + set(isActionBarOpenState, false); + set(isActionMenuDropdownOpenState, true); + }, + [recordTableId], + ); + + return { triggerActionMenuDropdown }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts deleted file mode 100644 index b9c04a8b7769..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; -import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; -import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; -import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; -import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState'; - -export const useTriggerContextMenu = ({ - recordTableId, -}: { - recordTableId: string; -}) => { - const triggerContextMenu = useRecoilCallback( - ({ set, snapshot }) => - (event: React.MouseEvent, recordId: string) => { - event.preventDefault(); - - const tableScopeId = getScopeIdFromComponentId(recordTableId); - - set(contextMenuPositionState, { - x: event.clientX, - y: event.clientY, - }); - set(contextMenuIsOpenState, true); - - const isRowSelectedFamilyState = extractComponentFamilyState( - isRowSelectedComponentFamilyState, - tableScopeId, - ); - - const isRowSelected = getSnapshotValue( - snapshot, - isRowSelectedFamilyState(recordId), - ); - - if (isRowSelected !== true) { - set(isRowSelectedFamilyState(recordId), true); - } - }, - [recordTableId], - ); - - return { triggerContextMenu }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx index a21a0578ab0d..556e1e9845d5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx @@ -50,7 +50,7 @@ const StyledTableHead = styled.thead<{ clip-path: inset(0px -4px 0px 0px); } @media (max-width: ${MOBILE_VIEWPORT}px) { - width: 35px; + width: 30px; max-width: 35px; } } @@ -73,7 +73,11 @@ const StyledTableHead = styled.thead<{ } `; -export const RecordTableHeader = () => { +export const RecordTableHeader = ({ + objectMetadataNameSingular, +}: { + objectMetadataNameSingular: string; +}) => { const { visibleTableColumnsSelector } = useRecordTableStates(); const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector()); @@ -84,7 +88,11 @@ export const RecordTableHeader = () => { <RecordTableHeaderDragDropColumn /> <RecordTableHeaderCheckboxColumn /> {visibleTableColumns.map((column) => ( - <RecordTableHeaderCell key={column.fieldMetadataId} column={column} /> + <RecordTableHeaderCell + key={column.fieldMetadataId} + column={column} + objectMetadataNameSingular={objectMetadataNameSingular} + /> ))} <RecordTableHeaderLastColumn /> </tr> diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx index 4084b95dd9b2..ff3fc98580ee 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx @@ -3,6 +3,8 @@ import { useCallback, useMemo, useState } from 'react'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { IconPlus } from 'twenty-ui'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; @@ -91,11 +93,17 @@ const StyledHeaderIcon = styled.div` export const RecordTableHeaderCell = ({ column, + objectMetadataNameSingular, }: { column: ColumnDefinition<FieldMetadata>; + objectMetadataNameSingular: string; }) => { const { resizeFieldOffsetState, tableColumnsState } = useRecordTableStates(); + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular: objectMetadataNameSingular, + }); + const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState( resizeFieldOffsetState, ); @@ -190,6 +198,8 @@ export const RecordTableHeaderCell = ({ createNewTableRecord(); }; + const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem); + return ( <StyledColumnHeaderCell key={column.fieldMetadataId} @@ -205,16 +215,18 @@ export const RecordTableHeaderCell = ({ > <StyledColumnHeadContainer> <RecordTableColumnHeadWithDropdown column={column} /> - {(useIsMobile() || iconVisibility) && !!column.isLabelIdentifier && ( - <StyledHeaderIcon> - <LightIconButton - Icon={IconPlus} - size="small" - accent="tertiary" - onClick={handlePlusButtonClick} - /> - </StyledHeaderIcon> - )} + {(useIsMobile() || iconVisibility) && + !!column.isLabelIdentifier && + !isReadOnly && ( + <StyledHeaderIcon> + <LightIconButton + Icon={IconPlus} + size="small" + accent="tertiary" + onClick={handlePlusButtonClick} + /> + </StyledHeaderIcon> + )} </StyledColumnHeadContainer> {!disableColumnResize && ( <StyledResizeHandler diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx index 0a548ae5f8de..8a87fd63c560 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx @@ -19,7 +19,6 @@ const StyledPlusIconHeaderCell = styled.th<{ &:hover { background: ${theme.background.transparent.light}; }; - padding-left: ${theme.spacing(3)}; `; }}; border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; @@ -28,12 +27,12 @@ const StyledPlusIconHeaderCell = styled.th<{ border-left: none !important; color: ${({ theme }) => theme.font.color.tertiary}; min-width: 32px; + width: 32px; border-right: none !important; ${({ isTableWiderThanScreen, theme }) => isTableWiderThanScreen ? ` - width: 32px; background-color: ${theme.background.primary}; ` : ''}; @@ -45,7 +44,7 @@ const StyledPlusIconContainer = styled.div` display: flex; height: 32px; justify-content: center; - width: 32px; + width: 100%; `; const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID = diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx index f325edecc22f..266defb44511 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx @@ -23,6 +23,7 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; import { IconPlus, isDefined } from 'twenty-ui'; import { useDebouncedCallback } from 'use-debounce'; + export const StyledSelectableItem = styled(SelectableItem)` height: 100%; width: 100%; @@ -144,19 +145,19 @@ export const MultiRecordSelect = ({ )} </> )} - {isDefined(onCreate) && ( - <> - {objectRecordsIdsMultiSelect.length > 0 && ( - <DropdownMenuSeparator /> - )} + </DropdownMenuItemsContainer> + {isDefined(onCreate) && ( + <> + <DropdownMenuSeparator /> + <DropdownMenuItemsContainer> <CreateNewButton onClick={debouncedOnCreate} LeftIcon={IconPlus} text="Add New" /> - </> - )} - </DropdownMenuItemsContainer> + </DropdownMenuItemsContainer> + </> + )} </DropdownMenu> </> ); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx index 95cdddcda144..ead53f498fdc 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx @@ -27,6 +27,7 @@ describe('useLimitPerMetadataItem', () => { nameSingular: 'nameSingular', updatedAt: 'updatedAt', fields: [], + indexMetadatas: [], }, ]; 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 9bd679cc47ee..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 @@ -2,9 +2,9 @@ import { act, renderHook } from '@testing-library/react'; import { RecoilRoot, useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; 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/generatedMockObjectMetadataItems'; const scopeId = 'scopeId'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( @@ -13,8 +13,6 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => ( </RelationPickerScopeInternalContext.Provider> ); -const objectMetadataItemsMock = getObjectMetadataItemsMock(); - const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6'; const personId = 'ab091fd9-1b81-4dfd-bfdb-564ffee032a2'; @@ -70,7 +68,7 @@ describe('useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray' }, ); act(() => { - result.current.setObjectMetadata(objectMetadataItemsMock); + result.current.setObjectMetadata(generatedMockObjectMetadataItems); }); expect( diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx index 54fb2aeaa323..73d1715f5503 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx @@ -46,6 +46,7 @@ const objectData: ObjectMetadataItem[] = [ isActive: true, }, ], + indexMetadatas: [], }, ]; diff --git a/packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx b/packages/twenty-front/src/modules/object-record/select/components/MultipleSelectDropdown.tsx similarity index 55% rename from packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx rename to packages/twenty-front/src/modules/object-record/select/components/MultipleSelectDropdown.tsx index 3914a41633fe..fba130110ddc 100644 --- a/packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/select/components/MultipleSelectDropdown.tsx @@ -1,9 +1,10 @@ +import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; -import { Avatar } from 'twenty-ui'; +import { AvatarChip } from 'twenty-ui'; -import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; +import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; @@ -14,26 +15,36 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -export const MultipleRecordSelectDropdown = ({ +const StyledAvatarChip = styled(AvatarChip)` + &.avatar-icon-container { + color: ${({ theme }) => theme.font.color.secondary}; + gap: ${({ theme }) => theme.spacing(2)}; + padding-left: 0px; + padding-right: 0px; + font-size: ${({ theme }) => theme.font.size.md}; + } +`; + +export const MultipleSelectDropdown = ({ selectableListId, hotkeyScope, - recordsToSelect, - loadingRecords, - filteredSelectedRecords, + itemsToSelect, + loadingItems, + filteredSelectedItems, onChange, searchFilter, }: { selectableListId: string; hotkeyScope: string; - recordsToSelect: SelectableRecord[]; - filteredSelectedRecords: SelectableRecord[]; - selectedRecords: SelectableRecord[]; + itemsToSelect: SelectableItem[]; + filteredSelectedItems: SelectableItem[]; + selectedItems: SelectableItem[]; searchFilter: string; onChange: ( - changedRecordToSelect: SelectableRecord, + changedItemToSelect: SelectableItem, newSelectedValue: boolean, ) => void; - loadingRecords: boolean; + loadingItems: boolean; }) => { const { closeDropdown } = useDropdown(); const { selectedItemIdState } = useSelectableListStates({ @@ -44,32 +55,32 @@ export const MultipleRecordSelectDropdown = ({ const selectedItemId = useRecoilValue(selectedItemIdState); - const handleRecordSelectChange = ( - recordToSelect: SelectableRecord, + const handleItemSelectChange = ( + itemToSelect: SelectableItem, newSelectedValue: boolean, ) => { onChange( { - ...recordToSelect, + ...itemToSelect, isSelected: newSelectedValue, }, newSelectedValue, ); }; - const [recordsInDropdown, setRecordInDropdown] = useState([ - ...(filteredSelectedRecords ?? []), - ...(recordsToSelect ?? []), + const [itemsInDropdown, setItemInDropdown] = useState([ + ...(filteredSelectedItems ?? []), + ...(itemsToSelect ?? []), ]); useEffect(() => { - if (!loadingRecords) { - setRecordInDropdown([ - ...(filteredSelectedRecords ?? []), - ...(recordsToSelect ?? []), + if (!loadingItems) { + setItemInDropdown([ + ...(filteredSelectedItems ?? []), + ...(itemsToSelect ?? []), ]); } - }, [recordsToSelect, filteredSelectedRecords, loadingRecords]); + }, [itemsToSelect, filteredSelectedItems, loadingItems]); useScopedHotkeys( [Key.Escape], @@ -82,12 +93,12 @@ export const MultipleRecordSelectDropdown = ({ ); const showNoResult = - recordsToSelect?.length === 0 && + itemsToSelect?.length === 0 && searchFilter !== '' && - filteredSelectedRecords?.length === 0 && - !loadingRecords; + filteredSelectedItems?.length === 0 && + !loadingItems; - const selectableItemIds = recordsInDropdown.map((record) => record.id); + const selectableItemIds = itemsInDropdown.map((item) => item.id); return ( <SelectableList @@ -95,45 +106,46 @@ export const MultipleRecordSelectDropdown = ({ selectableItemIdArray={selectableItemIds} hotkeyScope={hotkeyScope} onEnter={(itemId) => { - const record = recordsInDropdown.findIndex( + const item = itemsInDropdown.findIndex( (entity) => entity.id === itemId, ); - const recordIsSelectedInDropwdown = filteredSelectedRecords.find( + const itemIsSelectedInDropwdown = filteredSelectedItems.find( (entity) => entity.id === itemId, ); - handleRecordSelectChange( - recordsInDropdown[record], - !recordIsSelectedInDropwdown, + handleItemSelectChange( + itemsInDropdown[item], + !itemIsSelectedInDropwdown, ); resetSelectedItem(); }} > <DropdownMenuItemsContainer hasMaxHeight> - {recordsInDropdown?.map((record) => { + {itemsInDropdown?.map((item) => { return ( <MenuItemMultiSelectAvatar - key={record.id} - selected={record.isSelected} - isKeySelected={record.id === selectedItemId} + key={item.id} + selected={item.isSelected} + isKeySelected={item.id === selectedItemId} onSelectChange={(newCheckedValue) => { resetSelectedItem(); - handleRecordSelectChange(record, newCheckedValue); + handleItemSelectChange(item, newCheckedValue); }} avatar={ - <Avatar - avatarUrl={record.avatarUrl} - placeholderColorSeed={record.id} - placeholder={record.name} - size="md" - type={record.avatarType ?? 'rounded'} + <StyledAvatarChip + className="avatar-icon-container" + name={item.name} + avatarUrl={item.avatarUrl} + LeftIcon={item.AvatarIcon} + avatarType={item.avatarType} + isIconInverted={item.isIconInverted} + placeholderColorSeed={item.id} /> } - text={record.name} /> ); })} {showNoResult && <MenuItem text="No result" />} - {loadingRecords && <DropdownMenuSkeletonItem />} + {loadingItems && <DropdownMenuSkeletonItem />} </DropdownMenuItemsContainer> </SelectableList> ); diff --git a/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts b/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts index 905d4fd8f138..73bacf2e7cf6 100644 --- a/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts +++ b/packages/twenty-front/src/modules/object-record/select/hooks/useRecordsForSelect.ts @@ -5,7 +5,7 @@ import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapTo import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { SelectableRecord } from '@/object-record/select/types/SelectableRecord'; +import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { getObjectFilterFields } from '@/object-record/select/utils/getObjectFilterFields'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables'; @@ -109,19 +109,19 @@ export const useRecordsForSelect = ({ .map((record) => ({ ...record, isSelected: true, - })) as SelectableRecord[], + })) as SelectableItem[], filteredSelectedRecords: filteredSelectedRecordsData .map(mapToObjectRecordIdentifier) .map((record) => ({ ...record, isSelected: true, - })) as SelectableRecord[], + })) as SelectableItem[], recordsToSelect: recordsToSelectData .map(mapToObjectRecordIdentifier) .map((record) => ({ ...record, isSelected: false, - })) as SelectableRecord[], + })) as SelectableItem[], loading: recordsToSelectLoading || filteredSelectedRecordsLoading || diff --git a/packages/twenty-front/src/modules/object-record/select/types/SelectableItem.ts b/packages/twenty-front/src/modules/object-record/select/types/SelectableItem.ts new file mode 100644 index 000000000000..57122ebe702b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/select/types/SelectableItem.ts @@ -0,0 +1,11 @@ +import { AvatarType, IconComponent } from 'twenty-ui'; + +export type SelectableItem<T = object> = T & { + id: string; + name: string; + avatarUrl?: string; + avatarType?: AvatarType; + AvatarIcon?: IconComponent; + isSelected: boolean; + isIconInverted?: boolean; +}; diff --git a/packages/twenty-front/src/modules/object-record/select/types/SelectableRecord.ts b/packages/twenty-front/src/modules/object-record/select/types/SelectableRecord.ts deleted file mode 100644 index ff08ac70b8e3..000000000000 --- a/packages/twenty-front/src/modules/object-record/select/types/SelectableRecord.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AvatarType } from 'twenty-ui'; - -export type SelectableRecord = { - id: string; - name: string; - avatarUrl?: string; - avatarType?: AvatarType; - record: any; - isSelected: boolean; -}; 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 a9680898821f..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,57 +25,269 @@ const companyMocks = [ ) { createCompanies(data: $data, upsert: $upsert) { __typename - id - visaSponsorship + 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 + } + } + } + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } + createdAt createdBy { source workspaceMemberId name } + 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 secondaryLinks } - position - annualRecurringRevenue { - amountMicros - currencyCode - } - employees linkedinLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - workPolicy - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } 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 + 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 } - myCustomField - createdAt - accountOwnerId - tagline - idealCustomerProfile } } `, @@ -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 }) => ( - <RecoilRoot> - <MockedProvider mocks={companyMocks} addTypename={false}> - <SnackBarManagerScopeInternalContext.Provider - value={{ scopeId: 'snack-bar-manager' }} - > - {children} - </SnackBarManagerScopeInternalContext.Provider> - </MockedProvider> - </RecoilRoot> -); +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/object-record/spreadsheet-import/constants/CompositeFieldImportLabels.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/constants/CompositeFieldImportLabels.ts index 35a697bc116d..030601f241bc 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/constants/CompositeFieldImportLabels.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/constants/CompositeFieldImportLabels.ts @@ -1,8 +1,10 @@ import { FieldAddressValue, FieldCurrencyValue, + FieldEmailsValue, FieldFullNameValue, FieldLinksValue, + FieldPhonesValue, } from '@/object-record/record-field/types/FieldMetadata'; import { CompositeFieldLabels } from '@/object-record/spreadsheet-import/types/CompositeFieldLabels'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -30,6 +32,13 @@ export const COMPOSITE_FIELD_IMPORT_LABELS = { primaryLinkUrlLabel: 'Link URL', primaryLinkLabelLabel: 'Link Label', } satisfies Partial<CompositeFieldLabels<FieldLinksValue>>, + [FieldMetadataType.Emails]: { + primaryEmailLabel: 'Email', + } satisfies Partial<CompositeFieldLabels<FieldEmailsValue>>, + [FieldMetadataType.Phones]: { + primaryPhoneCountryCodeLabel: 'Phone country code', + primaryPhoneNumberLabel: 'Phone number', + } satisfies Partial<CompositeFieldLabels<FieldPhonesValue>>, [FieldMetadataType.Actor]: { sourceLabel: 'Source', }, diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport.ts index 7e4edf83926a..260a223f1791 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport.ts @@ -15,6 +15,7 @@ export const useBuildAvailableFieldsForImport = () => { ) => { const availableFieldsForImport: AvailableFieldForImport[] = []; + // Todo: refactor this to avoid this else if syntax with duplicated code for (const fieldMetadataItem of fieldMetadataItems) { if (fieldMetadataItem.type === FieldMetadataType.FullName) { const { firstNameLabel, lastNameLabel } = @@ -155,6 +156,42 @@ export const useBuildAvailableFieldsForImport = () => { fieldMetadataItem.label, ), }); + } else if (fieldMetadataItem.type === FieldMetadataType.Emails) { + Object.entries( + COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.Emails], + ).forEach(([_, fieldLabel]) => { + availableFieldsForImport.push({ + icon: getIcon(fieldMetadataItem.icon), + label: `${fieldLabel} (${fieldMetadataItem.label})`, + key: `${fieldLabel} (${fieldMetadataItem.name})`, + fieldType: { + type: 'input', + }, + fieldValidationDefinitions: + getSpreadSheetFieldValidationDefinitions( + fieldMetadataItem.type, + `${fieldLabel} (${fieldMetadataItem.label})`, + ), + }); + }); + } else if (fieldMetadataItem.type === FieldMetadataType.Phones) { + Object.entries( + COMPOSITE_FIELD_IMPORT_LABELS[FieldMetadataType.Phones], + ).forEach(([_, fieldLabel]) => { + availableFieldsForImport.push({ + icon: getIcon(fieldMetadataItem.icon), + label: `${fieldLabel} (${fieldMetadataItem.label})`, + key: `${fieldLabel} (${fieldMetadataItem.name})`, + fieldType: { + type: 'input', + }, + fieldValidationDefinitions: + getSpreadSheetFieldValidationDefinitions( + fieldMetadataItem.type, + `${fieldLabel} (${fieldMetadataItem.label})`, + ), + }); + }); } else { availableFieldsForImport.push({ icon: getIcon(fieldMetadataItem.icon), diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/buildRecordFromImportedStructuredRow.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/buildRecordFromImportedStructuredRow.ts index 5ddbe06096b5..68e641656660 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/buildRecordFromImportedStructuredRow.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/buildRecordFromImportedStructuredRow.ts @@ -1,7 +1,9 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldAddressValue, + FieldEmailsValue, FieldLinksValue, + FieldPhonesValue, } from '@/object-record/record-field/types/FieldMetadata'; import { COMPOSITE_FIELD_IMPORT_LABELS } from '@/object-record/spreadsheet-import/constants/CompositeFieldImportLabels'; import { ImportedStructuredRow } from '@/spreadsheet-import/types'; @@ -31,6 +33,8 @@ export const buildRecordFromImportedStructuredRow = ( CURRENCY: { amountMicrosLabel, currencyCodeLabel }, FULL_NAME: { firstNameLabel, lastNameLabel }, LINKS: { primaryLinkLabelLabel, primaryLinkUrlLabel }, + EMAILS: { primaryEmailLabel }, + PHONES: { primaryPhoneNumberLabel, primaryPhoneCountryCodeLabel }, } = COMPOSITE_FIELD_IMPORT_LABELS; for (const field of fields) { @@ -129,14 +133,48 @@ export const buildRecordFromImportedStructuredRow = ( } break; } - case FieldMetadataType.Link: - if (importedFieldValue !== undefined) { + case FieldMetadataType.Phones: { + if ( + isDefined( + importedStructuredRow[ + `${primaryPhoneCountryCodeLabel} (${field.name})` + ] || + importedStructuredRow[ + `${primaryPhoneNumberLabel} (${field.name})` + ], + ) + ) { recordToBuild[field.name] = { - label: field.name, - url: importedFieldValue || null, - }; + primaryPhoneCountryCode: castToString( + importedStructuredRow[ + `${primaryPhoneCountryCodeLabel} (${field.name})` + ], + ), + primaryPhoneNumber: castToString( + importedStructuredRow[ + `${primaryPhoneNumberLabel} (${field.name})` + ], + ), + additionalPhones: null, + } satisfies FieldPhonesValue; } break; + } + case FieldMetadataType.Emails: { + if ( + isDefined( + importedStructuredRow[`${primaryEmailLabel} (${field.name})`], + ) + ) { + recordToBuild[field.name] = { + primaryEmail: castToString( + importedStructuredRow[`${primaryEmailLabel} (${field.name})`], + ), + additionalEmails: null, + } satisfies FieldEmailsValue; + } + break; + } case FieldMetadataType.Relation: if ( isDefined(importedFieldValue) && diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/getSpreadSheetFieldValidationDefinitions.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/getSpreadSheetFieldValidationDefinitions.ts index ad08de8936f5..b32f814a7300 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/getSpreadSheetFieldValidationDefinitions.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/util/getSpreadSheetFieldValidationDefinitions.ts @@ -1,5 +1,3 @@ -import { isValidPhoneNumber } from 'libphonenumber-js'; - import { FieldValidationDefinition } from '@/spreadsheet-import/types'; import { isDefined } from 'twenty-ui'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -41,15 +39,6 @@ export const getSpreadSheetFieldValidationDefinitions = ( level: 'error', }, ]; - case FieldMetadataType.Phone: - return [ - { - rule: 'function', - isValid: (value: string) => isValidPhoneNumber(value), - errorMessage: fieldName + ' is not valid', - level: 'error', - }, - ]; case FieldMetadataType.Relation: return [ { diff --git a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts index a0a69349fc35..b936f09833df 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts @@ -8,20 +8,12 @@ export const generateEmptyFieldValue = ( fieldMetadataItem: Pick<FieldMetadataItem, 'type' | 'relationDefinition'>, ) => { switch (fieldMetadataItem.type) { - case FieldMetadataType.Email: - case FieldMetadataType.Phone: case FieldMetadataType.Text: { return ''; } case FieldMetadataType.Emails: { return { primaryEmail: '', additionalEmails: null }; } - case FieldMetadataType.Link: { - return { - label: '', - url: '', - }; - } case FieldMetadataType.Links: { return { primaryLinkUrl: '', primaryLinkLabel: '', secondaryLinks: null }; } diff --git a/packages/twenty-front/src/modules/object-record/utils/generateSearchRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/utils/generateSearchRecordsQuery.ts new file mode 100644 index 000000000000..070aaf020833 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/generateSearchRecordsQuery.ts @@ -0,0 +1,40 @@ +import gql from 'graphql-tag'; + +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; +import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; +import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField'; +import { capitalize } from '~/utils/string/capitalize'; + +export type QueryCursorDirection = 'before' | 'after'; + +export const generateSearchRecordsQuery = ({ + objectMetadataItem, + objectMetadataItems, + recordGqlFields, + computeReferences, +}: { + objectMetadataItem: ObjectMetadataItem; + objectMetadataItems: ObjectMetadataItem[]; // TODO - what is this used for? + recordGqlFields?: RecordGqlOperationGqlRecordFields; + computeReferences?: boolean; +}) => gql` + query Search${capitalize(objectMetadataItem.namePlural)}($search: String, $limit: Int) { + ${getSearchRecordsQueryResponseField(objectMetadataItem.namePlural)}(searchInput: $search, limit: $limit){ + edges { + node ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + recordGqlFields, + computeReferences, + })} + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } +} +`; diff --git a/packages/twenty-front/src/modules/object-record/utils/getSearchRecordsQueryResponseField.ts b/packages/twenty-front/src/modules/object-record/utils/getSearchRecordsQueryResponseField.ts new file mode 100644 index 000000000000..fa6b7daf5b8a --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/getSearchRecordsQueryResponseField.ts @@ -0,0 +1,4 @@ +import { capitalize } from '~/utils/string/capitalize'; + +export const getSearchRecordsQueryResponseField = (objectNamePlural: string) => + `search${capitalize(objectNamePlural)}`; diff --git a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts index cc3fb4ba46fa..c760351487a7 100644 --- a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts @@ -50,6 +50,8 @@ export const sanitizeRecordInput = ({ return undefined; } + // Todo: we should check that the fieldValue is a valid value + // (e.g. a string for a string field, following the right composite structure for composite fields) return [fieldName, fieldValue]; }) .filter(isDefined), diff --git a/packages/twenty-front/src/modules/opportunities/Opportunity.ts b/packages/twenty-front/src/modules/opportunities/Opportunity.ts new file mode 100644 index 000000000000..fe4212d5698d --- /dev/null +++ b/packages/twenty-front/src/modules/opportunities/Opportunity.ts @@ -0,0 +1,8 @@ +export type Opportunity = { + __typename: 'Opportunity'; + id: string; + createdAt: string; + updatedAt?: string; + deletedAt?: string | null; + name: string | null; +}; 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 23b7409799ca..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 @@ -1,14 +1,14 @@ -import { ReactNode } from 'react'; import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; +import { ReactNode } from 'react'; import { RecoilRoot, useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; 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/generatedMockObjectMetadataItems'; import { query, responseData, @@ -75,11 +75,9 @@ describe('useFilteredSearchEntityQuery', () => { locale: 'en', }); - const mockObjectMetadataItems = getObjectMetadataItemsMock(); - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(mockObjectMetadataItems); + setMetadataItems(generatedMockObjectMetadataItems); return useFilteredSearchEntityQuery({ orderByField: 'name', diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx index 16b1255ffeec..9556441e6267 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx @@ -8,7 +8,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails'; import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral'; -import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard'; +import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountCalendarChannelsTabListComponentId'; import { TabList } from '@/ui/layout/tab/components/TabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; @@ -45,6 +45,7 @@ export const SettingsAccountsCalendarChannelsContainer = () => { in: accounts.map((account) => account.id), }, }, + skip: !accounts.length, }); const tabs = [ @@ -55,7 +56,7 @@ export const SettingsAccountsCalendarChannelsContainer = () => { ]; if (!calendarChannels.length) { - return <SettingsAccountsListEmptyStateCard />; + return <SettingsNewAccountSection />; } return ( diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx index d2d15d02fdae..2c5e1102d3b3 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx @@ -6,8 +6,8 @@ import { MessageChannel } from '@/accounts/types/MessageChannel'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard'; import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails'; +import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId'; import { TabList } from '@/ui/layout/tab/components/TabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; @@ -55,7 +55,7 @@ export const SettingsAccountsMessageChannelsContainer = () => { ]; if (!messageChannels.length) { - return <SettingsAccountsListEmptyStateCard />; + return <SettingsNewAccountSection />; } return ( diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx index 5ca4cb832d68..5da069e0b2ca 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx @@ -1,5 +1,10 @@ import styled from '@emotion/styled'; -import { H2Title, IconCalendarEvent, IconMailCog } from 'twenty-ui'; +import { + H2Title, + IconCalendarEvent, + IconMailCog, + MOBILE_VIEWPORT, +} from 'twenty-ui'; import { SettingsCard } from '@/settings/components/SettingsCard'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; @@ -12,6 +17,10 @@ const StyledCardsContainer = styled.div` display: flex; gap: ${({ theme }) => theme.spacing(4)}; margin-top: ${({ theme }) => theme.spacing(6)}; + + @media (max-width: ${MOBILE_VIEWPORT}pxF) { + flex-direction: column; + } `; export const SettingsAccountsSettingsSection = () => { 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<typeof SettingsAccountsCalendarChannelDetails> = { 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<typeof SettingsAccountsCalendarChannelsGeneral> = { 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<typeof SettingsAccountsMessageChannelDetails> = { 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/accounts/hooks/useTriggerGoogleApisOAuth.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerGoogleApisOAuth.ts index 0c6ec18d28bc..921ebdea12ee 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerGoogleApisOAuth.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerGoogleApisOAuth.ts @@ -12,11 +12,17 @@ export const useTriggerGoogleApisOAuth = () => { const [generateTransientToken] = useGenerateTransientTokenMutation(); const triggerGoogleApisOAuth = useCallback( - async ( - redirectLocation?: AppPath, - messageVisibility?: MessageChannelVisibility, - calendarVisibility?: CalendarChannelVisibility, - ) => { + async ({ + redirectLocation, + messageVisibility, + calendarVisibility, + loginHint, + }: { + redirectLocation?: AppPath | string; + messageVisibility?: MessageChannelVisibility; + calendarVisibility?: CalendarChannelVisibility; + loginHint?: string; + } = {}) => { const authServerUrl = REACT_APP_SERVER_BASE_URL; const transientToken = await generateTransientToken(); @@ -38,6 +44,8 @@ export const useTriggerGoogleApisOAuth = () => { ? `&messageVisibility=${messageVisibility}` : ''; + params += loginHint ? `&loginHint=${loginHint}` : ''; + window.location.href = `${authServerUrl}/auth/google-apis?${params}`; }, [generateTransientToken], diff --git a/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx b/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx index 7383b6e902ee..972bac3d77a4 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsCard.tsx @@ -27,6 +27,7 @@ const StyledCard = styled(Card)<{ width: 100%; & :hover { background-color: ${({ theme }) => theme.background.quaternary}; + cursor: pointer; } `; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx index 050ca38f9e9b..16835921f6da 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx @@ -21,7 +21,7 @@ export const SettingsNavigationDrawerItem = ({ Icon, label, indentationLevel, - matchSubPages = false, + matchSubPages = true, path, soon, subItemState, diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx index 04f11020758a..e5f6dca4ee7d 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx @@ -13,13 +13,16 @@ import { IconMail, IconRocket, IconSettings, + IconTool, IconUserCircle, IconUsers, + MAIN_COLORS, } from 'twenty-ui'; import { useAuth } from '@/auth/hooks/useAuth'; import { billingState } from '@/client-config/states/billingState'; import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem'; +import { useExpandedHeightAnimation } from '@/settings/hooks/useExpandedHeightAnimation'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { @@ -29,19 +32,45 @@ import { import { NavigationDrawerItemGroup } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemGroup'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import styled from '@emotion/styled'; +import { AnimatePresence, motion } from 'framer-motion'; import { matchPath, resolvePath, useLocation } from 'react-router-dom'; type SettingsNavigationItem = { label: string; path: SettingsPath; Icon: IconComponent; - matchSubPages?: boolean; indentationLevel?: NavigationDrawerItemIndentationLevel; + matchSubPages?: boolean; }; +const StyledIconContainer = styled.div` + border-right: 1px solid ${MAIN_COLORS.yellow}; + position: absolute; + left: ${({ theme }) => theme.spacing(-5)}; + margin-top: ${({ theme }) => theme.spacing(2)}; + height: 75%; +`; + +const StyledDeveloperSection = styled.div` + display: flex; + width: 100%; + gap: ${({ theme }) => theme.spacing(1)}; + position: relative; +`; + +const StyledIconTool = styled(IconTool)` + margin-right: ${({ theme }) => theme.spacing(0.5)}; +`; + export const SettingsNavigationDrawerItems = () => { + const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); + const { contentRef, motionAnimationVariants } = useExpandedHeightAnimation( + isAdvancedModeEnabled, + ); const { signOut } = useAuth(); const billing = useRecoilValue(billingState); @@ -61,14 +90,12 @@ export const SettingsNavigationDrawerItems = () => { label: 'Emails', path: SettingsPath.AccountsEmails, Icon: IconMail, - matchSubPages: true, indentationLevel: 2, }, { label: 'Calendars', path: SettingsPath.AccountsCalendars, Icon: IconCalendarEvent, - matchSubPages: true, indentationLevel: 2, }, ]; @@ -80,7 +107,7 @@ export const SettingsNavigationDrawerItems = () => { return matchPath( { path: pathName, - end: !accountSubSetting.matchSubPages, + end: accountSubSetting.matchSubPages === false, }, currentPathName, ); @@ -105,6 +132,7 @@ export const SettingsNavigationDrawerItems = () => { label="Accounts" path={SettingsPath.Accounts} Icon={IconAt} + matchSubPages={false} /> {accountSubSettings.map((navigationItem, index) => ( <SettingsNavigationDrawerItem @@ -145,20 +173,7 @@ export const SettingsNavigationDrawerItems = () => { label="Data model" path={SettingsPath.Objects} Icon={IconHierarchy2} - matchSubPages /> - <SettingsNavigationDrawerItem - label="Developers" - path={SettingsPath.Developers} - Icon={IconCode} - /> - {isFunctionSettingsEnabled && ( - <SettingsNavigationDrawerItem - label="Functions" - path={SettingsPath.ServerlessFunctions} - Icon={IconFunction} - /> - )} <SettingsNavigationDrawerItem label="Integrations" path={SettingsPath.Integrations} @@ -172,6 +187,38 @@ export const SettingsNavigationDrawerItems = () => { /> )} </NavigationDrawerSection> + <AnimatePresence> + {isAdvancedModeEnabled && ( + <motion.div + ref={contentRef} + initial="initial" + animate="animate" + exit="exit" + variants={motionAnimationVariants} + > + <StyledDeveloperSection> + <StyledIconContainer> + <StyledIconTool size={12} color={MAIN_COLORS.yellow} /> + </StyledIconContainer> + <NavigationDrawerSection> + <NavigationDrawerSectionTitle label="Developers" /> + <SettingsNavigationDrawerItem + label="API & Webhooks" + path={SettingsPath.Developers} + Icon={IconCode} + /> + {isFunctionSettingsEnabled && ( + <SettingsNavigationDrawerItem + label="Functions" + path={SettingsPath.ServerlessFunctions} + Icon={IconFunction} + /> + )} + </NavigationDrawerSection> + </StyledDeveloperSection> + </motion.div> + )} + </AnimatePresence> <NavigationDrawerSection> <NavigationDrawerSectionTitle label="Other" /> <SettingsNavigationDrawerItem diff --git a/packages/twenty-front/src/modules/settings/components/SettingsSkeletonLoader.tsx b/packages/twenty-front/src/modules/settings/components/SettingsSkeletonLoader.tsx new file mode 100644 index 000000000000..812a5ca67b82 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/SettingsSkeletonLoader.tsx @@ -0,0 +1,52 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; +import { PageBody } from '@/ui/layout/page/PageBody'; +import { PageHeader } from '@/ui/layout/page/PageHeader'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const StyledTitleLoaderContainer = styled.div` + margin: ${({ theme }) => theme.spacing(8, 8, 2)}; +`; + +export const SettingsSkeletonLoader = () => { + const theme = useTheme(); + return ( + <StyledContainer> + <PageHeader + title={ + <SkeletonTheme + baseColor={theme.background.tertiary} + highlightColor={theme.background.transparent.lighter} + borderRadius={4} + > + <Skeleton + height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} + width={120} + />{' '} + </SkeletonTheme> + } + /> + <PageBody> + <StyledTitleLoaderContainer> + <SkeletonTheme + baseColor={theme.background.tertiary} + highlightColor={theme.background.transparent.lighter} + borderRadius={4} + > + <Skeleton + height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} + width={200} + /> + </SkeletonTheme> + </StyledTitleLoaderContainer> + </PageBody> + </StyledContainer> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/constants/AdvancedSettingsAnimationDurations.ts b/packages/twenty-front/src/modules/settings/constants/AdvancedSettingsAnimationDurations.ts new file mode 100644 index 000000000000..2f40f4d08e60 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/constants/AdvancedSettingsAnimationDurations.ts @@ -0,0 +1,4 @@ +export const ADVANCED_SETTINGS_ANIMATION_DURATION = { + opacity: 0.2, + size: 0.4, +}; diff --git a/packages/twenty-front/src/modules/settings/constants/ExpandedWidthAnimationVariants.ts b/packages/twenty-front/src/modules/settings/constants/ExpandedWidthAnimationVariants.ts new file mode 100644 index 000000000000..b6f14423e0a3 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/constants/ExpandedWidthAnimationVariants.ts @@ -0,0 +1,31 @@ +import { ADVANCED_SETTINGS_ANIMATION_DURATION } from '@/settings/constants/AdvancedSettingsAnimationDurations'; + +export const EXPANDED_WIDTH_ANIMATION_VARIANTS = { + initial: { + opacity: 0, + width: 0, + overflow: 'hidden', + transition: { + opacity: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.opacity }, + width: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.size }, + }, + }, + animate: { + opacity: 1, + width: '100%', + overflow: 'hidden', + transition: { + opacity: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.opacity }, + width: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.size }, + }, + }, + exit: { + opacity: 0, + width: 0, + overflow: 'hidden', + transition: { + opacity: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.opacity }, + width: { duration: ADVANCED_SETTINGS_ANIMATION_DURATION.size }, + }, + }, +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx index 0521676c7cf0..ce41c5f56301 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx @@ -1,3 +1,4 @@ +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { Button } from '@/ui/input/button/components/Button'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; @@ -6,20 +7,22 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { IconChevronDown } from 'twenty-ui'; - -type SettingsDataModelNewFieldBreadcrumbDropDownProps = { - isConfigureStep: boolean; - onBreadcrumbClick: (isConfigureStep: boolean) => void; -}; +import { + useLocation, + useNavigate, + useParams, + useSearchParams, +} from 'react-router-dom'; +import { IconChevronDown, isDefined } from 'twenty-ui'; const StyledContainer = styled.div` align-items: center; - color: ${({ theme }) => theme.font.color.secondary}; - cursor: pointer; + color: ${({ theme }) => theme.font.color.tertiary}; + cursor: default; display: flex; font-size: ${({ theme }) => theme.font.size.md}; `; + const StyledButtonContainer = styled.div` position: relative; width: 100%; @@ -33,10 +36,24 @@ const StyledDownChevron = styled(IconChevronDown)` transform: translateY(-50%); `; -const StyledMenuItem = styled(MenuItem)<{ selected?: boolean }>` +const StyledMenuItemWrapper = styled.div<{ disabled?: boolean }>` + cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; + width: 100%; +`; + +const StyledMenuItem = styled(MenuItem)<{ + selected?: boolean; + disabled?: boolean; +}>` background: ${({ theme, selected }) => selected ? theme.background.quaternary : 'transparent'}; - cursor: pointer; + opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; + pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')}; + + &:hover { + background: ${({ theme, disabled }) => + disabled ? 'transparent' : theme.background.tertiary}; + } `; const StyledSpan = styled.span` @@ -48,19 +65,30 @@ const StyledButton = styled(Button)` padding-right: ${({ theme }) => theme.spacing(6)}; `; -export const SettingsDataModelNewFieldBreadcrumbDropDown = ({ - isConfigureStep, - onBreadcrumbClick, -}: SettingsDataModelNewFieldBreadcrumbDropDownProps) => { +export const SettingsDataModelNewFieldBreadcrumbDropDown = () => { const dropdownId = `settings-object-new-field-breadcrumb-dropdown`; - const { closeDropdown } = useDropdown(dropdownId); + const navigate = useNavigate(); + const location = useLocation(); + const { objectSlug = '' } = useParams(); + const [searchParams] = useSearchParams(); + const theme = useTheme(); - const handleClick = (step: boolean) => { - onBreadcrumbClick(step); + const fieldType = searchParams.get('fieldType') as SettingsFieldType; + const isConfigureStep = location.pathname.includes('/configure'); + + const handleClick = (step: 'select' | 'configure') => { + if (step === 'configure' && isDefined(fieldType)) { + navigate( + `/settings/objects/${objectSlug}/new-field/configure?fieldType=${fieldType}`, + ); + } else { + navigate( + `/settings/objects/${objectSlug}/new-field/select${fieldType ? `?fieldType=${fieldType}` : ''}`, + ); + } closeDropdown(); }; - const theme = useTheme(); return ( <StyledContainer> @@ -81,16 +109,21 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = ({ dropdownComponents={ <DropdownMenu> <DropdownMenuItemsContainer> - <StyledMenuItem - text="1. Type" - onClick={() => handleClick(false)} - selected={!isConfigureStep} - /> - <StyledMenuItem - text="2. Configure" - onClick={() => handleClick(true)} - selected={isConfigureStep} - /> + <StyledMenuItemWrapper> + <StyledMenuItem + text="1. Type" + onClick={() => handleClick('select')} + selected={!isConfigureStep} + /> + </StyledMenuItemWrapper> + <StyledMenuItemWrapper disabled={!isDefined(fieldType)}> + <StyledMenuItem + text="2. Configure" + onClick={() => handleClick('configure')} + selected={isConfigureStep} + disabled={!isDefined(fieldType)} + /> + </StyledMenuItemWrapper> </DropdownMenuItemsContainer> </DropdownMenu> } diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx index 34e514abe83f..bf02470ca68c 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelPreviewFormCard.tsx @@ -1,6 +1,7 @@ -import { ReactNode } from 'react'; import styled from '@emotion/styled'; +import { ReactNode } from 'react'; +import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle'; import { Card } from '@/ui/layout/card/components/Card'; import { CardContent } from '@/ui/layout/card/components/CardContent'; @@ -14,14 +15,6 @@ const StyledPreviewContainer = styled(CardContent)` background-color: ${({ theme }) => theme.background.transparent.lighter}; `; -const StyledTitle = styled.h3` - color: ${({ theme }) => theme.font.color.extraLight}; - font-size: ${({ theme }) => theme.font.size.sm}; - font-weight: ${({ theme }) => theme.font.weight.medium}; - margin: 0; - margin-bottom: ${({ theme }) => theme.spacing(4)}; -`; - const StyledFormContainer = styled(CardContent)` padding: 0; `; @@ -33,7 +26,7 @@ export const SettingsDataModelPreviewFormCard = ({ }: SettingsDataModelPreviewFormCardProps) => ( <Card className={className} fullWidth> <StyledPreviewContainer divider={!!form}> - <StyledTitle>Preview</StyledTitle> + <StyledFormCardTitle>Preview</StyledFormCardTitle> {preview} </StyledPreviewContainer> {!!form && <StyledFormContainer>{form}</StyledFormContainer>} diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsCompositeFieldTypeConfigs.ts b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsCompositeFieldTypeConfigs.ts new file mode 100644 index 000000000000..b2fb02c29f62 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsCompositeFieldTypeConfigs.ts @@ -0,0 +1,177 @@ +import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; +import { + FieldActorValue, + FieldAddressValue, + FieldCurrencyValue, + FieldEmailsValue, + FieldFullNameValue, + FieldLinksValue, + FieldPhonesValue, +} from '@/object-record/record-field/types/FieldMetadata'; +import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs'; +import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType'; +import { + IllustrationIconCurrency, + IllustrationIconLink, + IllustrationIconMail, + IllustrationIconMap, + IllustrationIconPhone, + IllustrationIconSetting, + IllustrationIconUser, +} from 'twenty-ui'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export type SettingsCompositeFieldTypeConfig<T> = SettingsFieldTypeConfig<T> & { + subFields: (keyof T)[]; + filterableSubFields: (keyof T)[]; + labelBySubField: Record<keyof T, string>; + exampleValue: T; +}; + +type SettingsCompositeFieldTypeConfigArray = Record< + CompositeFieldType, + SettingsCompositeFieldTypeConfig<any> +>; + +export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = { + [FieldMetadataType.Currency]: { + label: 'Currency', + Icon: IllustrationIconCurrency, + subFields: ['amountMicros'], + filterableSubFields: ['amountMicros'], + labelBySubField: { + amountMicros: 'Amount', + currencyCode: 'Currency', + }, + exampleValue: { + amountMicros: 2000000000, + currencyCode: CurrencyCode.USD, + }, + category: 'Basic', + } as const satisfies SettingsCompositeFieldTypeConfig<FieldCurrencyValue>, + [FieldMetadataType.Emails]: { + label: 'Emails', + Icon: IllustrationIconMail, + subFields: ['primaryEmail', 'additionalEmails'], + filterableSubFields: ['primaryEmail'], + labelBySubField: { + primaryEmail: 'Primary Email', + additionalEmails: 'Additional Emails', + }, + exampleValue: { + primaryEmail: 'john@twenty.com', + additionalEmails: [ + 'tim@twenty.com', + 'timapple@twenty.com', + 'johnappletim@twenty.com', + ], + }, + category: 'Basic', + } as const satisfies SettingsCompositeFieldTypeConfig<FieldEmailsValue>, + [FieldMetadataType.Links]: { + label: 'Links', + Icon: IllustrationIconLink, + exampleValue: { + primaryLinkUrl: 'twenty.com', + primaryLinkLabel: '', + secondaryLinks: [{ url: 'twenty.com', label: 'Twenty' }], + }, + category: 'Basic', + subFields: ['primaryLinkUrl', 'primaryLinkLabel', 'secondaryLinks'], + filterableSubFields: ['primaryLinkUrl', 'primaryLinkLabel'], + labelBySubField: { + primaryLinkUrl: 'Link URL', + primaryLinkLabel: 'Link Label', + secondaryLinks: 'Secondary Links', + }, + } as const satisfies SettingsCompositeFieldTypeConfig<FieldLinksValue>, + [FieldMetadataType.Phones]: { + label: 'Phones', + Icon: IllustrationIconPhone, + exampleValue: { + primaryPhoneNumber: '234-567-890', + primaryPhoneCountryCode: '+1', + additionalPhones: [{ number: '234-567-890', countryCode: '+1' }], + }, + subFields: [ + 'primaryPhoneNumber', + 'primaryPhoneCountryCode', + 'additionalPhones', + ], + filterableSubFields: ['primaryPhoneNumber', 'primaryPhoneCountryCode'], + labelBySubField: { + primaryPhoneNumber: 'Primary Phone Number', + primaryPhoneCountryCode: 'Primary Phone Country Code', + additionalPhones: 'Additional Phones', + }, + category: 'Basic', + } as const satisfies SettingsCompositeFieldTypeConfig<FieldPhonesValue>, + [FieldMetadataType.FullName]: { + label: 'Full Name', + Icon: IllustrationIconUser, + exampleValue: { firstName: 'John', lastName: 'Doe' }, + category: 'Advanced', + subFields: ['firstName', 'lastName'], + filterableSubFields: ['firstName', 'lastName'], + labelBySubField: { + firstName: 'First Name', + lastName: 'Last Name', + }, + } as const satisfies SettingsCompositeFieldTypeConfig<FieldFullNameValue>, + [FieldMetadataType.Address]: { + label: 'Address', + Icon: IllustrationIconMap, + subFields: [ + 'addressStreet1', + 'addressStreet2', + 'addressCity', + 'addressState', + 'addressCountry', + 'addressPostcode', + 'addressLat', + 'addressLng', + ], + filterableSubFields: [ + 'addressStreet1', + 'addressStreet2', + 'addressCity', + 'addressState', + 'addressCountry', + 'addressPostcode', + ], + labelBySubField: { + addressStreet1: 'Address 1', + addressStreet2: 'Address 2', + addressCity: 'City', + addressState: 'State', + addressCountry: 'Country', + addressPostcode: 'Post Code', + addressLat: 'Latitude', + addressLng: 'Longitude', + }, + exampleValue: { + addressStreet1: '456 Oak Street', + addressStreet2: 'Unit 3B', + addressCity: 'Springfield', + addressState: 'California', + addressCountry: 'United States', + addressPostcode: '90210', + addressLat: 34.0522, + addressLng: -118.2437, + }, + category: 'Basic', + } as const satisfies SettingsCompositeFieldTypeConfig<FieldAddressValue>, + [FieldMetadataType.Actor]: { + label: 'Actor', + Icon: IllustrationIconSetting, + category: 'Basic', + subFields: ['source'], + filterableSubFields: ['source'], + labelBySubField: { + source: 'Source', + name: 'Name', + workspaceMemberId: 'Workspace Member ID', + }, + exampleValue: { source: 'source', name: 'name', workspaceMemberId: 'id' }, + } as const satisfies SettingsCompositeFieldTypeConfig<FieldActorValue>, +} as const satisfies SettingsCompositeFieldTypeConfigArray; diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldCurrencyCodes.ts b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldCurrencyCodes.ts index 5e013b50ca26..681da4b3b611 100644 --- a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldCurrencyCodes.ts +++ b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldCurrencyCodes.ts @@ -1,18 +1,56 @@ import { + IconCoins, IconComponent, + IconCurrencyAfghani, + IconCurrencyBahraini, IconCurrencyBaht, + IconCurrencyForint, + IconCurrencyDinar, IconCurrencyDirham, IconCurrencyDollar, + IconCurrencyDollarAustralian, + IconCurrencyDollarBrunei, + IconCurrencyDollarCanadian, + IconCurrencyDollarGuyanese, + IconCurrencyDollarSingapore, + IconCurrencyDong, + IconCurrencyDram, IconCurrencyEuro, + IconCurrencyFlorin, IconCurrencyFrank, + IconCurrencyGuarani, + IconCurrencyHryvnia, + IconCurrencyIranianRial, + IconCurrencyKip, IconCurrencyKroneCzech, + IconCurrencyKroneDanish, IconCurrencyKroneSwedish, + IconCurrencyLari, + IconCurrencyLeu, + IconCurrencyLira, + IconCurrencyLyd, + IconCurrencyManat, + IconCurrencyNaira, + IconCurrencyPaanga, + IconCurrencyPeso, IconCurrencyPound, + IconCurrencyQuetzal, IconCurrencyReal, + IconCurrencyRenminbi, IconCurrencyRiyal, + IconCurrencyRubel, + IconCurrencyRufiyaa, + IconCurrencyRupee, + IconCurrencyRupeeNepalese, + IconCurrencyTaka, + IconCurrencyTenge, + IconCurrencyTugrik, + IconCurrencySom, + IconCurrencyShekel, IconCurrencyWon, IconCurrencyYen, IconCurrencyYuan, + IconCurrencyZloty, } from 'twenty-ui'; import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; @@ -21,76 +59,156 @@ export const SETTINGS_FIELD_CURRENCY_CODES: Record< CurrencyCode, { label: string; Icon: IconComponent } > = { - USD: { - label: 'United States dollar', - Icon: IconCurrencyDollar, - }, - EUR: { - label: 'Euro', - Icon: IconCurrencyEuro, - }, - JPY: { - label: 'Japanese yen', - Icon: IconCurrencyYen, - }, - GBP: { - label: 'British pound', - Icon: IconCurrencyPound, - }, - CAD: { - label: 'Canadian dollar', - Icon: IconCurrencyDollar, - }, - CHF: { - label: 'Swiss franc', - Icon: IconCurrencyFrank, - }, - CNY: { - label: 'Chinese yuan', - Icon: IconCurrencyYuan, - }, - CZK: { - label: 'Czech koruna', - Icon: IconCurrencyKroneCzech, - }, - HKD: { - label: 'Hong Kong dollar', - Icon: IconCurrencyDollar, - }, - NOK: { - label: 'Norwegian krone', - Icon: IconCurrencyKroneSwedish, - }, - SEK: { - label: 'Swedish krona', - Icon: IconCurrencyKroneSwedish, - }, - BHT: { - label: 'Thai Baht', - Icon: IconCurrencyBaht, - }, - MAD: { - label: 'Moroccan dirham', - Icon: IconCurrencyDirham, - }, - QAR: { - label: 'Qatari riyal', - Icon: IconCurrencyRiyal, - }, - AED: { - label: 'UAE dirham', - Icon: IconCurrencyDirham, - }, - KRW: { - label: 'South Korean won', - Icon: IconCurrencyWon, - }, - BRL: { - label: 'Brazilian real', - Icon: IconCurrencyReal, - }, - AUD: { - label: 'Australian dollar', - Icon: IconCurrencyDollar, - }, + AED: { label: 'UAE dirham', Icon: IconCurrencyDirham }, + AFN: { label: 'Afghan afghani', Icon: IconCurrencyAfghani }, + ALL: { label: 'Albanian lek', Icon: IconCurrencyLeu }, + AMD: { label: 'Armenian dram', Icon: IconCurrencyDram }, + ANG: { label: 'Netherlands Antillean guilder', Icon: IconCurrencyFlorin }, + AOA: { label: 'Angolan kwanza', Icon: IconCoins }, + ARS: { label: 'Argentine peso', Icon: IconCoins }, + AUD: { label: 'Australian dollar', Icon: IconCurrencyDollarAustralian }, + AWG: { label: 'Aruban florin', Icon: IconCurrencyFlorin }, + AZN: { label: 'Azerbaijani manat', Icon: IconCurrencyManat }, + BAM: { label: 'Bosnia and Herzegovina mark', Icon: IconCoins }, + BBD: { label: 'Barbados dollar', Icon: IconCurrencyDollar }, + BDT: { label: 'Bangladeshi taka', Icon: IconCurrencyTaka }, + BGN: { label: 'Bulgarian lev', Icon: IconCoins }, + BHD: { label: 'Bahraini dinar', Icon: IconCurrencyBahraini }, + BIF: { label: 'Burundian franc', Icon: IconCoins }, + BMD: { label: 'Bermudian dollar', Icon: IconCurrencyDollar }, + BND: { label: 'Brunei dollar', Icon: IconCurrencyDollarBrunei }, + BOB: { label: 'Boliviano', Icon: IconCoins }, + BRL: { label: 'Brazilian real', Icon: IconCurrencyReal }, + BSD: { label: 'Bahamian dollar', Icon: IconCurrencyDollar }, + BTN: { label: 'Bhutanese ngultrum', Icon: IconCurrencyDollar }, + BWP: { label: 'Botswana pula', Icon: IconCoins }, + BYN: { label: 'Belarusian ruble', Icon: IconCoins }, + BZD: { label: 'Belize dollar', Icon: IconCurrencyDollar }, + CAD: { label: 'Canadian dollar', Icon: IconCurrencyDollarCanadian }, + CDF: { label: 'Congolese franc', Icon: IconCoins }, + CHF: { label: 'Swiss franc', Icon: IconCurrencyFrank }, + CLP: { label: 'Chilean peso', Icon: IconCoins }, + CNY: { label: 'Chinese yuan', Icon: IconCurrencyYuan }, + COP: { label: 'Colombian peso', Icon: IconCoins }, + CRC: { label: 'Costa Rican colon', Icon: IconCoins }, + CUP: { label: 'Cuban peso', Icon: IconCoins }, + CVE: { label: 'Cape Verdean escudo', Icon: IconCoins }, + CZK: { label: 'Czech koruna', Icon: IconCurrencyKroneCzech }, + DJF: { label: 'Djiboutian franc', Icon: IconCoins }, + DKK: { label: 'Danish krone', Icon: IconCurrencyKroneDanish }, + DOP: { label: 'Dominican peso', Icon: IconCoins }, + DZD: { label: 'Algerian Dinar', Icon: IconCoins }, + EGP: { label: 'Egyptian pound', Icon: IconCoins }, + ERN: { label: 'Eritrean nakfa', Icon: IconCoins }, + ETB: { label: 'Ethiopian birr', Icon: IconCoins }, + EUR: { label: 'Euro', Icon: IconCurrencyEuro }, + FJD: { label: 'Fiji dollar', Icon: IconCurrencyDollar }, + FKP: { label: 'Falkland Islands pound', Icon: IconCoins }, + GBP: { label: 'British pound', Icon: IconCurrencyPound }, + GEL: { label: 'Georgian lari', Icon: IconCurrencyLari }, + GHS: { label: 'Ghanaian cedi', Icon: IconCoins }, + GIP: { label: 'Gibraltar pound', Icon: IconCoins }, + GMD: { label: 'Gambian dalasi', Icon: IconCoins }, + GNF: { label: 'Guinean franc', Icon: IconCoins }, + GTQ: { label: 'Guatemalan quetzal', Icon: IconCurrencyQuetzal }, + GYD: { label: 'Guyanese dollar', Icon: IconCurrencyDollarGuyanese }, + HKD: { label: 'Hong Kong dollar', Icon: IconCurrencyRenminbi }, + HNL: { label: 'Honduran lempira', Icon: IconCurrencyLeu }, + HTG: { label: 'Haitian gourde', Icon: IconCoins }, + HUF: { label: 'Hungarian forint', Icon: IconCurrencyForint }, + IDR: { label: 'Indonesian rupiah', Icon: IconCoins }, + ILS: { label: 'Israeli shekel', Icon: IconCurrencyShekel }, + INR: { label: 'Indian rupee', Icon: IconCurrencyRupee }, + IQD: { label: 'Iraqi dinar', Icon: IconCoins }, + IRR: { label: 'Iranian rial', Icon: IconCurrencyIranianRial }, + ISK: { label: 'Icelandic krรณna', Icon: IconCoins }, + JMD: { label: 'Jamaican dollar', Icon: IconCurrencyDollar }, + JOD: { label: 'Jordanian dinar', Icon: IconCoins }, + JPY: { label: 'Japanese yen', Icon: IconCurrencyYen }, + KES: { label: 'Kenyan shilling', Icon: IconCoins }, + KGS: { label: 'Kyrgyzstani som', Icon: IconCurrencySom }, + KHR: { label: 'Cambodian riel', Icon: IconCoins }, + KMF: { label: 'Comoro franc', Icon: IconCoins }, + KPW: { label: 'North Korean won', Icon: IconCurrencyWon }, + KRW: { label: 'South Korean won', Icon: IconCurrencyWon }, + KWD: { label: 'Kuwaiti dinar', Icon: IconCurrencyDinar }, + KYD: { label: 'Cayman Islands dollar', Icon: IconCurrencyDollar }, + KZT: { label: 'Kazakhstani tenge', Icon: IconCurrencyTenge }, + LAK: { label: 'Lao kip', Icon: IconCurrencyKip }, + LBP: { label: 'Lebanese pound', Icon: IconCoins }, + LKR: { label: 'Sri Lankan rupee', Icon: IconCoins }, + LRD: { label: 'Liberian dollar', Icon: IconCurrencyDollar }, + LSL: { label: 'Lesotho loti', Icon: IconCurrencyLeu }, + LYD: { label: 'Libyan dinar', Icon: IconCurrencyLyd }, + MAD: { label: 'Moroccan dirham', Icon: IconCurrencyDirham }, + MDL: { label: 'Moldovan leu', Icon: IconCurrencyLeu }, + MGA: { label: 'Malagasy ariary', Icon: IconCoins }, + MKD: { label: 'Macedonian denar', Icon: IconCoins }, + MMK: { label: 'Myanmar kyat', Icon: IconCoins }, + MNT: { label: 'Mongolian tรถgrรถg', Icon: IconCurrencyTugrik }, + MOP: { label: 'Macanese pataca', Icon: IconCurrencyRenminbi }, + MRU: { label: 'Mauritanian ouguiya', Icon: IconCoins }, + MUR: { label: 'Mauritian rupee', Icon: IconCoins }, + MVR: { label: 'Maldivian rufiyaa', Icon: IconCurrencyRufiyaa }, + MWK: { label: 'Malawian kwacha', Icon: IconCoins }, + MXN: { label: 'Mexican peso', Icon: IconCoins }, + MYR: { label: 'Malaysian ringgit', Icon: IconCoins }, + MZN: { label: 'Mozambican metical', Icon: IconCoins }, + NAD: { label: 'Namibian dollar', Icon: IconCurrencyDollar }, + NGN: { label: 'Nigerian naira', Icon: IconCurrencyNaira }, + NIO: { label: 'Nicaraguan cรณrdoba', Icon: IconCoins }, + NOK: { label: 'Norwegian krone', Icon: IconCurrencyKroneSwedish }, + NPR: { label: 'Nepalese rupee', Icon: IconCurrencyRupeeNepalese }, + NZD: { label: 'New Zealand dollar', Icon: IconCurrencyDollar }, + OMR: { label: 'Omani rial', Icon: IconCoins }, + PAB: { label: 'Panamanian balboa', Icon: IconCoins }, + PEN: { label: 'Peruvian sol', Icon: IconCoins }, + PGK: { label: 'Papua New Guinean kina', Icon: IconCoins }, + PHP: { label: 'Philippine peso', Icon: IconCurrencyPeso }, + PKR: { label: 'Pakistani rupee', Icon: IconCoins }, + PLN: { label: 'Polish zล‚oty', Icon: IconCurrencyZloty }, + PYG: { label: 'Paraguayan guaranรญ', Icon: IconCurrencyGuarani }, + QAR: { label: 'Qatari riyal', Icon: IconCurrencyRiyal }, + RON: { label: 'Romanian leu', Icon: IconCurrencyLeu }, + RSD: { label: 'Serbian dinar', Icon: IconCoins }, + RUB: { label: 'Russian ruble', Icon: IconCurrencyRubel }, + RWF: { label: 'Rwandan franc', Icon: IconCoins }, + SAR: { label: 'Saudi riyal', Icon: IconCoins }, + SBD: { label: 'Solomon Islands dollar', Icon: IconCurrencyDollar }, + SCR: { label: 'Seychelles rupee', Icon: IconCoins }, + SDG: { label: 'Sudanese pound', Icon: IconCoins }, + SEK: { label: 'Swedish krona', Icon: IconCurrencyKroneSwedish }, + SGD: { label: 'Singapore dollar', Icon: IconCurrencyDollarSingapore }, + SHP: { label: 'Saint Helena pound', Icon: IconCoins }, + SLE: { label: 'Sierra Leonean leone', Icon: IconCoins }, + SOS: { label: 'Somalian shilling', Icon: IconCoins }, + SRD: { label: 'Surinamese dollar', Icon: IconCurrencyDollar }, + SSP: { label: 'South Sudanese pound', Icon: IconCoins }, + STN: { label: 'Sรฃo Tomรฉ and Prรญncipe dobra', Icon: IconCoins }, + SVC: { label: 'Salvadoran colรณn', Icon: IconCoins }, + SYP: { label: 'Syrian pound', Icon: IconCoins }, + SZL: { label: 'Swazi lilangeni', Icon: IconCurrencyLeu }, + THB: { label: 'Thai Baht', Icon: IconCurrencyBaht }, + TJS: { label: 'Tajikistani somoni', Icon: IconCoins }, + TMT: { label: 'Turkmenistan manat', Icon: IconCurrencyManat }, + TND: { label: 'Tunisian dinar', Icon: IconCoins }, + TOP: { label: 'Tongan paสปanga', Icon: IconCurrencyPaanga }, + TRY: { label: 'Turkish lira', Icon: IconCurrencyLira }, + TTD: { label: 'Trinidad and Tobago dollar', Icon: IconCurrencyDollar }, + TWD: { label: 'Taiwanese dollar', Icon: IconCurrencyRenminbi }, + TZS: { label: 'Tanzanian shilling', Icon: IconCoins }, + UAH: { label: 'Ukrainian hryvnia', Icon: IconCurrencyHryvnia }, + UGX: { label: 'Ugandan shilling', Icon: IconCoins }, + USD: { label: 'United States dollar', Icon: IconCurrencyDollar }, + UYU: { label: 'Uruguayan peso', Icon: IconCoins }, + UZS: { label: 'Uzbekistani sum', Icon: IconCoins }, + VES: { label: 'Venezuelan bolรญvar', Icon: IconCoins }, + VND: { label: 'Vietnamese ฤ‘แป“ng', Icon: IconCurrencyDong }, + VUV: { label: 'Vanuatu vatu', Icon: IconCoins }, + WST: { label: 'Samoan tala', Icon: IconCoins }, + XCD: { label: 'East Caribbean dollar', Icon: IconCurrencyDollar }, + YER: { label: 'Yemeni rial', Icon: IconCoins }, + ZAR: { label: 'South African rand', Icon: IconCoins }, + ZMW: { label: 'Zambian kwacha', Icon: IconCoins }, + ZWG: { label: 'Zimbabwe Gold', Icon: IconCoins }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts index c8cea11fdeb3..1105be7ca947 100644 --- a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts +++ b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsFieldTypeConfigs.ts @@ -1,196 +1,7 @@ -import { - IconComponent, - IllustrationIconArray, - IllustrationIconCalendarEvent, - IllustrationIconCalendarTime, - IllustrationIconCurrency, - IllustrationIconJson, - IllustrationIconLink, - IllustrationIconMail, - IllustrationIconMap, - IllustrationIconNumbers, - IllustrationIconOneToMany, - IllustrationIconPhone, - IllustrationIconSetting, - IllustrationIconStar, - IllustrationIconTag, - IllustrationIconTags, - IllustrationIconText, - IllustrationIconToggle, - IllustrationIconUid, - IllustrationIconUser, -} from 'twenty-ui'; - -import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; -import { DEFAULT_DATE_VALUE } from '@/settings/data-model/constants/DefaultDateValue'; -import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -DEFAULT_DATE_VALUE.setFullYear(DEFAULT_DATE_VALUE.getFullYear() + 2); - -export type SettingsFieldTypeConfig = { - label: string; - Icon: IconComponent; - exampleValue?: unknown; - category: SettingsFieldTypeCategoryType; -}; +import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; +import { SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs'; export const SETTINGS_FIELD_TYPE_CONFIGS = { - [FieldMetadataType.Uuid]: { - label: 'Unique ID', - Icon: IllustrationIconUid, - exampleValue: '00000000-0000-0000-0000-000000000000', - category: 'Advanced', - }, - [FieldMetadataType.Text]: { - label: 'Text', - Icon: IllustrationIconText, - exampleValue: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.', - category: 'Basic', - }, - [FieldMetadataType.Numeric]: { - label: 'Numeric', - Icon: IllustrationIconNumbers, - exampleValue: 2000, - category: 'Basic', - }, - [FieldMetadataType.Number]: { - label: 'Number', - Icon: IllustrationIconNumbers, - exampleValue: 2000, - category: 'Basic', - }, - [FieldMetadataType.Link]: { - label: 'Link', - Icon: IllustrationIconLink, - exampleValue: { url: 'www.twenty.com', label: '' }, - category: 'Basic', - }, - [FieldMetadataType.Links]: { - label: 'Links', - Icon: IllustrationIconLink, - exampleValue: { primaryLinkUrl: 'twenty.com', primaryLinkLabel: '' }, - category: 'Basic', - }, - [FieldMetadataType.Boolean]: { - label: 'True/False', - Icon: IllustrationIconToggle, - exampleValue: true, - category: 'Basic', - }, - [FieldMetadataType.DateTime]: { - label: 'Date and Time', - Icon: IllustrationIconCalendarTime, - exampleValue: DEFAULT_DATE_VALUE.toISOString(), - category: 'Basic', - }, - [FieldMetadataType.Date]: { - label: 'Date', - Icon: IllustrationIconCalendarEvent, - exampleValue: DEFAULT_DATE_VALUE.toISOString(), - category: 'Basic', - }, - [FieldMetadataType.Select]: { - label: 'Select', - Icon: IllustrationIconTag, - category: 'Basic', - }, - [FieldMetadataType.MultiSelect]: { - label: 'Multi-select', - Icon: IllustrationIconTags, - category: 'Basic', - }, - [FieldMetadataType.Currency]: { - label: 'Currency', - Icon: IllustrationIconCurrency, - exampleValue: { amountMicros: 2000000000, currencyCode: CurrencyCode.USD }, - category: 'Basic', - }, - [FieldMetadataType.Relation]: { - label: 'Relation', - Icon: IllustrationIconOneToMany, - category: 'Relation', - }, - [FieldMetadataType.Email]: { - label: 'Email', - Icon: IllustrationIconMail, - category: 'Basic', - }, - [FieldMetadataType.Emails]: { - label: 'Emails', - Icon: IllustrationIconMail, - exampleValue: { primaryEmail: 'john@twenty.com' }, - category: 'Basic', - }, - [FieldMetadataType.Phone]: { - label: 'Phone', - Icon: IllustrationIconPhone, - exampleValue: '+1234-567-890', - category: 'Basic', - }, - [FieldMetadataType.Phones]: { - label: 'Phones', - Icon: IllustrationIconPhone, - exampleValue: { - primaryPhoneNumber: '234-567-890', - primaryPhoneCountryCode: '+1', - }, - category: 'Basic', - }, - [FieldMetadataType.Rating]: { - label: 'Rating', - Icon: IllustrationIconStar, - exampleValue: '3', - category: 'Basic', - }, - [FieldMetadataType.FullName]: { - label: 'Full Name', - Icon: IllustrationIconUser, - exampleValue: { firstName: 'John', lastName: 'Doe' }, - category: 'Advanced', - }, - [FieldMetadataType.Address]: { - label: 'Address', - Icon: IllustrationIconMap, - exampleValue: { - addressStreet1: '456 Oak Street', - addressStreet2: 'Unit 3B', - addressCity: 'Springfield', - addressState: 'California', - addressCountry: 'United States', - addressPostcode: '90210', - addressLat: 34.0522, - addressLng: -118.2437, - }, - category: 'Basic', - }, - [FieldMetadataType.RawJson]: { - label: 'JSON', - Icon: IllustrationIconJson, - exampleValue: { key: 'value' }, - - category: 'Basic', - }, - [FieldMetadataType.RichText]: { - label: 'System', - Icon: IllustrationIconSetting, - exampleValue: { key: 'value' }, - category: 'Basic', - }, - [FieldMetadataType.Actor]: { - label: 'System', - Icon: IllustrationIconSetting, - category: 'Basic', - }, - [FieldMetadataType.Array]: { - label: 'Array', - Icon: IllustrationIconArray, - category: 'Basic', - exampleValue: ['value1', 'value2'], - }, -} as const satisfies Record< - SettingsSupportedFieldType, - SettingsFieldTypeConfig ->; + ...SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS, + ...SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS, +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs.ts b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs.ts new file mode 100644 index 000000000000..1039646c47ee --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs.ts @@ -0,0 +1,137 @@ +import { + IconComponent, + IllustrationIconArray, + IllustrationIconCalendarEvent, + IllustrationIconCalendarTime, + IllustrationIconJson, + IllustrationIconNumbers, + IllustrationIconOneToMany, + IllustrationIconSetting, + IllustrationIconStar, + IllustrationIconTag, + IllustrationIconTags, + IllustrationIconText, + IllustrationIconToggle, + IllustrationIconUid, +} from 'twenty-ui'; + +import { + FieldArrayValue, + FieldBooleanValue, + FieldDateTimeValue, + FieldDateValue, + FieldJsonValue, + FieldMultiSelectValue, + FieldNumberValue, + FieldRatingValue, + FieldRelationValue, + FieldRichTextValue, + FieldSelectValue, + FieldTextValue, + FieldUUidValue, +} from '@/object-record/record-field/types/FieldMetadata'; +import { DEFAULT_DATE_VALUE } from '@/settings/data-model/constants/DefaultDateValue'; +import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType'; +import { SettingsNonCompositeFieldType } from '@/settings/data-model/types/SettingsNonCompositeFieldType'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +DEFAULT_DATE_VALUE.setFullYear(DEFAULT_DATE_VALUE.getFullYear() + 2); + +export type SettingsFieldTypeConfig<T> = { + label: string; + Icon: IconComponent; + exampleValue?: T; + category: SettingsFieldTypeCategoryType; +}; + +type SettingsNonCompositeFieldTypeConfigArray = Record< + SettingsNonCompositeFieldType, + SettingsFieldTypeConfig<any> +>; + +// TODO: can we derive this from backend definitions ? +export const SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS: SettingsNonCompositeFieldTypeConfigArray = + { + [FieldMetadataType.Uuid]: { + label: 'Unique ID', + Icon: IllustrationIconUid, + exampleValue: '00000000-0000-0000-0000-000000000000', + category: 'Advanced', + } as const satisfies SettingsFieldTypeConfig<FieldUUidValue>, + [FieldMetadataType.Text]: { + label: 'Text', + Icon: IllustrationIconText, + exampleValue: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.', + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldTextValue>, + [FieldMetadataType.Numeric]: { + label: 'Numeric', + Icon: IllustrationIconNumbers, + exampleValue: 2000, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldNumberValue>, + [FieldMetadataType.Number]: { + label: 'Number', + Icon: IllustrationIconNumbers, + exampleValue: 2000, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldNumberValue>, + [FieldMetadataType.Boolean]: { + label: 'True/False', + Icon: IllustrationIconToggle, + exampleValue: true, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldBooleanValue>, + [FieldMetadataType.DateTime]: { + label: 'Date and Time', + Icon: IllustrationIconCalendarTime, + exampleValue: DEFAULT_DATE_VALUE.toISOString(), + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldDateTimeValue>, + [FieldMetadataType.Date]: { + label: 'Date', + Icon: IllustrationIconCalendarEvent, + exampleValue: DEFAULT_DATE_VALUE.toISOString(), + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldDateValue>, + [FieldMetadataType.Select]: { + label: 'Select', + Icon: IllustrationIconTag, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldSelectValue>, + [FieldMetadataType.MultiSelect]: { + label: 'Multi-select', + Icon: IllustrationIconTags, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldMultiSelectValue>, + [FieldMetadataType.Relation]: { + label: 'Relation', + Icon: IllustrationIconOneToMany, + category: 'Relation', + } as const satisfies SettingsFieldTypeConfig<FieldRelationValue<any>>, + [FieldMetadataType.Rating]: { + label: 'Rating', + Icon: IllustrationIconStar, + exampleValue: 'RATING_3', + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldRatingValue>, + [FieldMetadataType.RawJson]: { + label: 'JSON', + Icon: IllustrationIconJson, + exampleValue: { key: 'value' }, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldJsonValue>, + [FieldMetadataType.RichText]: { + label: 'Rich Text', + Icon: IllustrationIconSetting, + exampleValue: { key: 'value' }, + category: 'Basic', + } as const satisfies SettingsFieldTypeConfig<FieldRichTextValue>, + [FieldMetadataType.Array]: { + label: 'Array', + Icon: IllustrationIconArray, + category: 'Basic', + exampleValue: ['value1', 'value2'], + } as const satisfies SettingsFieldTypeConfig<FieldArrayValue>, + }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/components/StyledFormCardTitle.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/components/StyledFormCardTitle.tsx new file mode 100644 index 000000000000..03d4ebb91523 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/components/StyledFormCardTitle.tsx @@ -0,0 +1,9 @@ +import styled from '@emotion/styled'; + +export const StyledFormCardTitle = styled.h3` + color: ${({ theme }) => theme.font.color.extraLight}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + margin: 0; + margin-bottom: ${({ theme }) => theme.spacing(4)}; +`; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx index 114993114cb4..d4771446e5d3 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx @@ -9,9 +9,9 @@ import { IconPicker } from '@/ui/input/components/IconPicker'; import { TextInput } from '@/ui/input/components/TextInput'; export const settingsDataModelFieldIconLabelFormSchema = ( - existingLabels?: string[], + existingOtherLabels: string[] = [], ) => { - return fieldMetadataItemSchema(existingLabels || []).pick({ + return fieldMetadataItemSchema(existingOtherLabels).pick({ icon: true, label: 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 139a1036a4a9..9b50515b10b9 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 @@ -9,6 +9,10 @@ import { settingsDataModelFieldBooleanFormSchema } from '@/settings/data-model/f import { SettingsDataModelFieldBooleanSettingsFormCard } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard'; import { settingsDataModelFieldCurrencyFormSchema } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencyForm'; 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 { @@ -30,6 +34,14 @@ const currencyFieldFormSchema = z .object({ type: z.literal(FieldMetadataType.Currency) }) .merge(settingsDataModelFieldCurrencyFormSchema); +const dateFieldFormSchema = z + .object({ type: z.literal(FieldMetadataType.Date) }) + .merge(settingsDataModelFieldDateFormSchema); + +const dateTimeFieldFormSchema = z + .object({ type: z.literal(FieldMetadataType.DateTime) }) + .merge(settingsDataModelFieldDateFormSchema); + const relationFieldFormSchema = z .object({ type: z.literal(FieldMetadataType.Relation) }) .merge(settingsDataModelFieldRelationFormSchema); @@ -42,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( @@ -51,6 +67,9 @@ const otherFieldsFormSchema = z.object({ FieldMetadataType.Relation, FieldMetadataType.Select, FieldMetadataType.MultiSelect, + FieldMetadataType.Date, + FieldMetadataType.DateTime, + FieldMetadataType.Number, ]), ) as [FieldMetadataType, ...FieldMetadataType[]], ), @@ -61,16 +80,22 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion( [ booleanFieldFormSchema, currencyFieldFormSchema, + dateFieldFormSchema, + dateTimeFieldFormSchema, relationFieldFormSchema, selectFieldFormSchema, multiSelectFieldFormSchema, + numberFieldFormSchema, otherFieldsFormSchema, ], ); type SettingsDataModelFieldSettingsFormCardProps = { - disableCurrencyForm?: boolean; - fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> & + isCreatingField?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'isCustom' + > & Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>; } & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>; @@ -88,11 +113,9 @@ const previewableTypes = [ FieldMetadataType.DateTime, FieldMetadataType.Emails, FieldMetadataType.FullName, - FieldMetadataType.Link, FieldMetadataType.Links, FieldMetadataType.MultiSelect, FieldMetadataType.Number, - FieldMetadataType.Phone, FieldMetadataType.Phones, FieldMetadataType.Rating, FieldMetadataType.RawJson, @@ -102,7 +125,7 @@ const previewableTypes = [ ]; export const SettingsDataModelFieldSettingsFormCard = ({ - disableCurrencyForm, + isCreatingField, fieldMetadataItem, objectMetadataItem, }: SettingsDataModelFieldSettingsFormCardProps) => { @@ -120,7 +143,20 @@ export const SettingsDataModelFieldSettingsFormCard = ({ if (fieldMetadataItem.type === FieldMetadataType.Currency) { return ( <SettingsDataModelFieldCurrencySettingsFormCard - disabled={disableCurrencyForm} + disabled={!isCreatingField} + fieldMetadataItem={fieldMetadataItem} + objectMetadataItem={objectMetadataItem} + /> + ); + } + + if ( + fieldMetadataItem.type === FieldMetadataType.Date || + fieldMetadataItem.type === FieldMetadataType.DateTime + ) { + return ( + <SettingsDataModelFieldDateSettingsFormCard + disabled={!isCreatingField} fieldMetadataItem={fieldMetadataItem} objectMetadataItem={objectMetadataItem} /> @@ -136,6 +172,16 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } + if (fieldMetadataItem.type === FieldMetadataType.Number) { + return ( + <SettingsDataModelFieldNumberSettingsFormCard + disabled={fieldMetadataItem.isCustom === false} + fieldMetadataItem={fieldMetadataItem} + objectMetadataItem={objectMetadataItem} + /> + ); + } + if ( fieldMetadataItem.type === FieldMetadataType.Select || fieldMetadataItem.type === FieldMetadataType.MultiSelect diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle.tsx new file mode 100644 index 000000000000..e859d652daca --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle.tsx @@ -0,0 +1,91 @@ +import { Toggle } from '@/ui/input/components/Toggle'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { createPortal } from 'react-dom'; +import { + AppTooltip, + IconComponent, + IconInfoCircle, + TooltipDelay, +} from 'twenty-ui'; + +const StyledContainer = styled.div<{ disabled?: boolean }>` + align-items: center; + background-color: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + box-sizing: border-box; + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ disabled, theme }) => + disabled ? theme.font.color.tertiary : theme.font.color.primary}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + height: ${({ theme }) => theme.spacing(8)}; + justify-content: space-between; + padding: 0 ${({ theme }) => theme.spacing(2)}; +`; + +const StyledGroup = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +interface SettingsDataModelFieldToggleProps { + disabled?: boolean; + Icon?: IconComponent; + label: string; + tooltip?: string; + value?: boolean; + onChange: (value: boolean) => void; +} + +export const SettingsDataModelFieldToggle = ({ + disabled, + Icon, + label, + tooltip, + value, + onChange, +}: SettingsDataModelFieldToggleProps) => { + const theme = useTheme(); + const infoCircleElementId = `info-circle-id-${Math.random().toString(36).slice(2)}`; + + return ( + <StyledContainer> + <StyledGroup> + {Icon && ( + <Icon color={theme.font.color.tertiary} size={theme.icon.size.md} /> + )} + {label} + </StyledGroup> + <StyledGroup> + {tooltip && ( + <IconInfoCircle + id={infoCircleElementId} + size={theme.icon.size.md} + color={theme.font.color.tertiary} + /> + )} + {tooltip && + createPortal( + <AppTooltip + anchorSelect={`#${infoCircleElementId}`} + content={tooltip} + offset={5} + noArrow + place="bottom" + positionStrategy="absolute" + delay={TooltipDelay.shortDelay} + />, + document.body, + )} + <Toggle + disabled={disabled} + value={value} + onChange={onChange} + toggleSize="small" + /> + </StyledGroup> + </StyledContainer> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect.tsx deleted file mode 100644 index 14e68598e33d..000000000000 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { SettingsCard } from '@/settings/components/SettingsCard'; -import { SETTINGS_FIELD_TYPE_CATEGORIES } from '@/settings/data-model/constants/SettingsFieldTypeCategories'; -import { SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS } from '@/settings/data-model/constants/SettingsFieldTypeCategoryDescriptions'; -import { - SETTINGS_FIELD_TYPE_CONFIGS, - SettingsFieldTypeConfig, -} from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; -import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues'; -import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues'; -import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; -import { TextInput } from '@/ui/input/components/TextInput'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { Section } from '@react-email/components'; -import { useState } from 'react'; -import { Controller, useFormContext } from 'react-hook-form'; -import { H2Title, IconSearch } from 'twenty-ui'; -import { z } from 'zod'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -export const settingsDataModelFieldTypeFormSchema = z.object({ - type: z.enum( - Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [ - SettingsSupportedFieldType, - ...SettingsSupportedFieldType[], - ], - ), -}); - -export type SettingsDataModelFieldTypeFormValues = z.infer< - typeof settingsDataModelFieldTypeFormSchema ->; - -type SettingsDataModelFieldTypeSelectProps = { - className?: string; - excludedFieldTypes?: SettingsSupportedFieldType[]; - fieldMetadataItem?: Pick< - FieldMetadataItem, - 'defaultValue' | 'options' | 'type' - >; - onFieldTypeSelect: () => void; -}; - -const StyledTypeSelectContainer = styled.div` - display: flex; - flex-direction: column; - gap: inherit; - width: 100%; -`; - -const StyledContainer = styled.div` - display: flex; - gap: ${({ theme }) => theme.spacing(2)}; - justify-content: flex-start; - flex-wrap: wrap; - width: 100%; -`; - -const StyledCardContainer = styled.div` - display: flex; - - position: relative; - width: calc(50% - ${({ theme }) => theme.spacing(1)}); -`; - -const StyledSearchInput = styled(TextInput)` - width: 100%; -`; - -export const SettingsDataModelFieldTypeSelect = ({ - className, - excludedFieldTypes = [], - fieldMetadataItem, - onFieldTypeSelect, -}: SettingsDataModelFieldTypeSelectProps) => { - const theme = useTheme(); - const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>(); - const [searchQuery, setSearchQuery] = useState(''); - const fieldTypeConfigs = Object.entries<SettingsFieldTypeConfig>( - SETTINGS_FIELD_TYPE_CONFIGS, - ).filter( - ([key, config]) => - !excludedFieldTypes.includes(key as SettingsSupportedFieldType) && - config.label.toLowerCase().includes(searchQuery.toLowerCase()), - ); - - const { resetDefaultValueField: resetBooleanDefaultValueField } = - useBooleanSettingsFormInitialValues({ fieldMetadataItem }); - - const { resetDefaultValueField: resetCurrencyDefaultValueField } = - useCurrencySettingsFormInitialValues({ fieldMetadataItem }); - - const { resetDefaultValueField: resetSelectDefaultValueField } = - useSelectSettingsFormInitialValues({ fieldMetadataItem }); - - const resetDefaultValueField = (nextValue: SettingsSupportedFieldType) => { - switch (nextValue) { - case FieldMetadataType.Boolean: - resetBooleanDefaultValueField(); - break; - case FieldMetadataType.Currency: - resetCurrencyDefaultValueField(); - break; - case FieldMetadataType.Select: - case FieldMetadataType.MultiSelect: - resetSelectDefaultValueField(); - break; - default: - break; - } - }; - - return ( - <Controller - name="type" - control={control} - defaultValue={ - fieldMetadataItem && fieldMetadataItem.type in fieldTypeConfigs - ? (fieldMetadataItem.type as SettingsSupportedFieldType) - : FieldMetadataType.Text - } - render={({ field: { onChange } }) => ( - <StyledTypeSelectContainer className={className}> - <Section> - <StyledSearchInput - LeftIcon={IconSearch} - placeholder="Search a type" - value={searchQuery} - onChange={setSearchQuery} - /> - </Section> - {SETTINGS_FIELD_TYPE_CATEGORIES.map((category) => ( - <Section key={category}> - <H2Title - title={category} - description={ - SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS[category] - } - /> - <StyledContainer> - {fieldTypeConfigs - .filter(([, config]) => config.category === category) - .map(([key, config]) => ( - <StyledCardContainer> - <SettingsCard - key={key} - onClick={() => { - onChange(key as SettingsSupportedFieldType); - resetDefaultValueField( - key as SettingsSupportedFieldType, - ); - onFieldTypeSelect(); - }} - Icon={ - <config.Icon - size={theme.icon.size.xl} - stroke={theme.icon.stroke.sm} - /> - } - title={config.label} - /> - </StyledCardContainer> - ))} - </StyledContainer> - </Section> - ))} - </StyledTypeSelectContainer> - )} - /> - ); -}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx new file mode 100644 index 000000000000..962fb7aaba5d --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx @@ -0,0 +1,160 @@ +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { SettingsCard } from '@/settings/components/SettingsCard'; +import { SETTINGS_FIELD_TYPE_CATEGORIES } from '@/settings/data-model/constants/SettingsFieldTypeCategories'; +import { SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS } from '@/settings/data-model/constants/SettingsFieldTypeCategoryDescriptions'; +import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; +import { SettingsFieldTypeConfig } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs'; +import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/fields/forms/boolean/hooks/useBooleanSettingsFormInitialValues'; +import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues'; +import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { Section } from '@react-email/components'; +import { useState } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { H2Title, IconSearch } from 'twenty-ui'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { SettingsDataModelFieldTypeFormValues } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect'; + +type SettingsObjectNewFieldSelectorProps = { + className?: string; + excludedFieldTypes?: SettingsFieldType[]; + fieldMetadataItem?: Pick< + FieldMetadataItem, + 'defaultValue' | 'options' | 'type' + >; + + objectSlug: string; +}; + +const StyledTypeSelectContainer = styled.div` + display: flex; + flex-direction: column; + gap: inherit; + width: 100%; +`; + +const StyledContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + justify-content: flex-start; + flex-wrap: wrap; + width: 100%; +`; + +const StyledCardContainer = styled.div` + display: flex; + + position: relative; + width: calc(50% - ${({ theme }) => theme.spacing(1)}); +`; + +const StyledSearchInput = styled(TextInput)` + width: 100%; +`; + +export const SettingsObjectNewFieldSelector = ({ + excludedFieldTypes = [], + fieldMetadataItem, + objectSlug, +}: SettingsObjectNewFieldSelectorProps) => { + const theme = useTheme(); + const { control, setValue } = + useFormContext<SettingsDataModelFieldTypeFormValues>(); + const [searchQuery, setSearchQuery] = useState(''); + const fieldTypeConfigs = Object.entries<SettingsFieldTypeConfig<any>>( + SETTINGS_FIELD_TYPE_CONFIGS, + ).filter( + ([key, config]) => + !excludedFieldTypes.includes(key as SettingsFieldType) && + config.label.toLowerCase().includes(searchQuery.toLowerCase()), + ); + + const { resetDefaultValueField: resetBooleanDefaultValueField } = + useBooleanSettingsFormInitialValues({ fieldMetadataItem }); + + const { resetDefaultValueField: resetCurrencyDefaultValueField } = + useCurrencySettingsFormInitialValues({ fieldMetadataItem }); + + const { resetDefaultValueField: resetSelectDefaultValueField } = + useSelectSettingsFormInitialValues({ fieldMetadataItem }); + + const resetDefaultValueField = (nextValue: SettingsFieldType) => { + switch (nextValue) { + case FieldMetadataType.Boolean: + resetBooleanDefaultValueField(); + break; + case FieldMetadataType.Currency: + resetCurrencyDefaultValueField(); + break; + case FieldMetadataType.Select: + case FieldMetadataType.MultiSelect: + resetSelectDefaultValueField(); + break; + default: + break; + } + }; + + return ( + <> + {' '} + <Section> + <StyledSearchInput + LeftIcon={IconSearch} + placeholder="Search a type" + value={searchQuery} + onChange={setSearchQuery} + /> + </Section> + <Controller + name="type" + control={control} + render={() => ( + <StyledTypeSelectContainer> + {SETTINGS_FIELD_TYPE_CATEGORIES.map((category) => ( + <Section key={category}> + <H2Title + title={category} + description={ + SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS[category] + } + /> + <StyledContainer> + {fieldTypeConfigs + .filter(([, config]) => config.category === category) + .map(([key, config]) => ( + <StyledCardContainer key={key}> + <UndecoratedLink + to={`/settings/objects/${objectSlug}/new-field/configure?fieldType=${key}`} + fullWidth + onClick={() => { + setValue('type', key as SettingsFieldType); + resetDefaultValueField(key as SettingsFieldType); + }} + > + <SettingsCard + key={key} + Icon={ + <config.Icon + size={theme.icon.size.xl} + stroke={theme.icon.stroke.sm} + /> + } + title={config.label} + /> + </UndecoratedLink> + </StyledCardContainer> + ))} + </StyledContainer> + </Section> + ))} + </StyledTypeSelectContainer> + )} + /> + </> + ); +}; 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<typeof SettingsDataModelFieldDescriptionForm> = { @@ -25,11 +25,15 @@ type Story = StoryObj<typeof SettingsDataModelFieldDescriptionForm>; 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<typeof SettingsDataModelFieldIconLabelForm>; 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/components/__stories__/SettingsDataModelFieldTypeSelect.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldTypeSelect.stories.tsx deleted file mode 100644 index dbd3ea3c93bd..000000000000 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldTypeSelect.stories.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { expect, userEvent, within } from '@storybook/test'; -import { ComponentDecorator } from 'twenty-ui'; - -import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; -import { graphqlMocks } from '~/testing/graphqlMocks'; - -import { SettingsDataModelFieldTypeSelect } from '../SettingsDataModelFieldTypeSelect'; - -const meta: Meta<typeof SettingsDataModelFieldTypeSelect> = { - title: - 'Modules/Settings/DataModel/Fields/Forms/SettingsDataModelFieldTypeSelect', - component: SettingsDataModelFieldTypeSelect, - decorators: [FormProviderDecorator, ComponentDecorator], - parameters: { - container: { width: 512 }, - msw: graphqlMocks, - }, -}; - -export default meta; -type Story = StoryObj<typeof SettingsDataModelFieldTypeSelect>; - -export const Default: Story = {}; - -export const WithOpenSelect: Story = { - play: async () => { - const canvas = within(document.body); - - const inputField = await canvas.findByText('Text'); - - await userEvent.click(inputField); - - const input = await canvas.findByText('Unique ID'); - await userEvent.click(input); - - await userEvent.click(inputField); - }, -}; - -export const WithExcludedFieldTypes: Story = { - args: { - excludedFieldTypes: [FieldMetadataType.Uuid, FieldMetadataType.Numeric], - }, - play: async () => { - const canvas = within(document.body); - - const inputField = await canvas.findByText('Text'); - - await userEvent.click(inputField); - - await canvas.findByText('Number'); - - expect(canvas.queryByText('Unique ID')).toBeNull(); - expect(canvas.queryByText('Numeric')).toBeNull(); - }, -}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm.tsx new file mode 100644 index 000000000000..c7d029abe811 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm.tsx @@ -0,0 +1,63 @@ +import { Controller, useFormContext } from 'react-hook-form'; +import { z } from 'zod'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { StyledFormCardTitle } from '@/settings/data-model/fields/components/StyledFormCardTitle'; +import { SettingsDataModelFieldToggle } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldToggle'; +import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues'; +import { CardContent } from '@/ui/layout/card/components/CardContent'; +import { IconClockShare } from 'twenty-ui'; + +export const settingsDataModelFieldDateFormSchema = z.object({ + settings: z + .object({ + displayAsRelativeDate: z.boolean().optional(), + }) + .optional(), +}); + +export type SettingsDataModelFieldDateFormValues = z.infer< + typeof settingsDataModelFieldDateFormSchema +>; + +type SettingsDataModelFieldDateFormProps = { + disabled?: boolean; + fieldMetadataItem: Pick<FieldMetadataItem, 'settings'>; +}; + +export const SettingsDataModelFieldDateForm = ({ + disabled, + fieldMetadataItem, +}: SettingsDataModelFieldDateFormProps) => { + const { control } = useFormContext<SettingsDataModelFieldDateFormValues>(); + + const { initialDisplayAsRelativeDateValue } = + useDateSettingsFormInitialValues({ + fieldMetadataItem, + }); + + return ( + <CardContent> + <Controller + name="settings.displayAsRelativeDate" + control={control} + defaultValue={initialDisplayAsRelativeDateValue} + render={({ field: { onChange, value } }) => ( + <> + <StyledFormCardTitle>Options</StyledFormCardTitle> + <SettingsDataModelFieldToggle + label="Display as relative date" + Icon={IconClockShare} + onChange={onChange} + value={value} + disabled={disabled} + tooltip={ + 'Show dates in a human-friendly format. Example: "13 mins ago" instead of "Jul 30, 2024 7:11pm"' + } + /> + </> + )} + /> + </CardContent> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx new file mode 100644 index 000000000000..418d9d93cc19 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard.tsx @@ -0,0 +1,66 @@ +import styled from '@emotion/styled'; +import { useFormContext } from 'react-hook-form'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard'; +import { + SettingsDataModelFieldDateForm, + SettingsDataModelFieldDateFormValues, +} from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm'; +import { useDateSettingsFormInitialValues } from '@/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues'; +import { + SettingsDataModelFieldPreviewCard, + SettingsDataModelFieldPreviewCardProps, +} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard'; + +type SettingsDataModelFieldDateSettingsFormCardProps = { + disabled?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'settings' + >; +} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>; + +const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` + display: grid; + flex: 1 1 100%; +`; + +export const SettingsDataModelFieldDateSettingsFormCard = ({ + disabled, + fieldMetadataItem, + objectMetadataItem, +}: SettingsDataModelFieldDateSettingsFormCardProps) => { + const { initialDisplayAsRelativeDateValue } = + useDateSettingsFormInitialValues({ + fieldMetadataItem, + }); + + const { watch: watchFormValue } = + useFormContext<SettingsDataModelFieldDateFormValues>(); + + return ( + <SettingsDataModelPreviewFormCard + preview={ + <StyledFieldPreviewCard + fieldMetadataItem={{ + ...fieldMetadataItem, + settings: { + displayAsRelativeDate: watchFormValue( + 'settings.displayAsRelativeDate', + initialDisplayAsRelativeDateValue, + ), + }, + }} + objectMetadataItem={objectMetadataItem} + /> + } + form={ + <SettingsDataModelFieldDateForm + disabled={disabled} + fieldMetadataItem={fieldMetadataItem} + /> + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues.ts new file mode 100644 index 000000000000..4726db3ec55e --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/date/hooks/useDateSettingsFormInitialValues.ts @@ -0,0 +1,25 @@ +import { useFormContext } from 'react-hook-form'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { SettingsDataModelFieldDateFormValues } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm'; + +export const useDateSettingsFormInitialValues = ({ + fieldMetadataItem, +}: { + fieldMetadataItem?: Pick<FieldMetadataItem, 'settings'>; +}) => { + const initialDisplayAsRelativeDateValue = + fieldMetadataItem?.settings?.displayAsRelativeDate; + + const { resetField } = useFormContext<SettingsDataModelFieldDateFormValues>(); + + const resetDefaultValueField = () => + resetField('settings.displayAsRelativeDate', { + defaultValue: initialDisplayAsRelativeDateValue, + }); + + return { + initialDisplayAsRelativeDateValue, + resetDefaultValueField, + }; +}; 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 ( + <> + <StyledTitle>Number of decimals</StyledTitle> + <StyledCounterContainer> + <StyledCounterInnerContainer> + <StyledExampleText>Example: {exampleValue}</StyledExampleText> + <StyledCounterControlsIcons> + <StyledInfoButton variant="tertiary" Icon={IconInfoCircle} /> + <StyledControlButton + variant="secondary" + onClick={handleDecrementCounter} + Icon={IconMinus} + disabled={disabled} + /> + <StyledTextInput + name="decimals" + fullWidth + value={value.toString()} + onChange={(value) => handleTextInputChange(value)} + disabled={disabled} + /> + <StyledControlButton + variant="secondary" + onClick={handleIncrementCounter} + Icon={IconPlus} + disabled={disabled} + /> + </StyledCounterControlsIcons> + </StyledCounterInnerContainer> + </StyledCounterContainer> + </> + ); +}; 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<SettingsDataModelFieldNumberFormValues>(); + + return ( + <CardContent> + <Controller + name="settings" + defaultValue={{ + decimals: + fieldMetadataItem?.settings?.decimals ?? DEFAULT_DECIMAL_VALUE, + }} + control={control} + render={({ field: { onChange, value } }) => { + const count = value?.decimals ?? 0; + + return ( + <SettingsDataModelFieldNumberDecimalsInput + value={count} + onChange={(value) => onChange({ decimals: value })} + disabled={disabled} + ></SettingsDataModelFieldNumberDecimalsInput> + ); + }} + /> + </CardContent> + ); +}; 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<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>; + +const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` + display: grid; + flex: 1 1 100%; +`; + +export const SettingsDataModelFieldNumberSettingsFormCard = ({ + disabled, + fieldMetadataItem, + objectMetadataItem, +}: SettingsDataModelFieldNumberSettingsFormCardProps) => { + return ( + <SettingsDataModelPreviewFormCard + preview={ + <StyledFieldPreviewCard + fieldMetadataItem={fieldMetadataItem} + objectMetadataItem={objectMetadataItem} + /> + } + form={ + <SettingsDataModelFieldNumberForm + disabled={disabled} + fieldMetadataItem={fieldMetadataItem} + /> + } + /> + ); +}; 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 d30e05193f4b..854b59b68154 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 @@ -5,6 +5,7 @@ import { z } from 'zod'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema'; import { FIELD_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/FieldNameMaximumLength'; @@ -14,6 +15,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({ @@ -38,19 +40,19 @@ export type SettingsDataModelFieldRelationFormValues = z.infer< type SettingsDataModelFieldRelationFormProps = { fieldMetadataItem: Pick<FieldMetadataItem, 'type'>; + objectMetadataItem: ObjectMetadataItem; }; 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 +68,11 @@ const StyledInputsContainer = styled.div` `; const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES) - .filter(([value]) => 'ONE_TO_ONE' !== value) + .filter( + ([value]) => + RelationDefinitionType.OneToOne !== value && + RelationDefinitionType.ManyToMany !== value, + ) .map(([value, { label, Icon }]) => ({ label, value: value as RelationType, @@ -75,6 +81,7 @@ const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES) export const SettingsDataModelFieldRelationForm = ({ fieldMetadataItem, + objectMetadataItem, }: SettingsDataModelFieldRelationFormProps) => { const { control, watch: watchFormValue } = useFormContext<SettingsDataModelFieldRelationFormValues>(); @@ -88,15 +95,20 @@ export const SettingsDataModelFieldRelationForm = ({ initialRelationFieldMetadataItem, initialRelationObjectMetadataItem, initialRelationType, - } = useRelationSettingsFormInitialValues({ fieldMetadataItem }); + } = useRelationSettingsFormInitialValues({ + fieldMetadataItem, + objectMetadataItem, + }); const selectedObjectMetadataItem = findObjectMetadataItemById( watchFormValue('relation.objectMetadataId'), ); + const isMobile = useIsMobile(); + return ( <StyledContainer> - <StyledSelectsContainer> + <StyledSelectsContainer isMobile={isMobile}> <Controller name="relation.type" control={control} diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx index 134787be8379..0372ba071417 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard.tsx @@ -14,8 +14,8 @@ import { SettingsDataModelFieldPreviewCard, SettingsDataModelFieldPreviewCardProps, } from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { FieldMetadataType } from '~/generated-metadata/graphql'; - type SettingsDataModelFieldRelationSettingsFormCardProps = { fieldMetadataItem: Pick<FieldMetadataItem, 'icon' | 'label' | 'type'> & Partial<Omit<FieldMetadataItem, 'icon' | 'label' | 'type'>>; @@ -27,13 +27,24 @@ const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` flex: 1 1 100%; `; -const StyledPreviewContent = styled.div` +const StyledPreviewContent = styled.div<{ isMobile: boolean }>` display: flex; gap: 6px; + flex-direction: ${({ isMobile }) => (isMobile ? 'column' : 'row')}; `; -const StyledRelationImage = styled.img<{ flip?: boolean }>` - transform: ${({ flip }) => (flip ? 'scaleX(-1)' : '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; `; @@ -44,12 +55,15 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ const { watch: watchFormValue } = useFormContext<SettingsDataModelFieldRelationFormValues>(); const { findObjectMetadataItemById } = useFilteredObjectMetadataItems(); - + const isMobile = useIsMobile(); const { initialRelationObjectMetadataItem, initialRelationType, initialRelationFieldMetadataItem, - } = useRelationSettingsFormInitialValues({ fieldMetadataItem }); + } = useRelationSettingsFormInitialValues({ + fieldMetadataItem, + objectMetadataItem, + }); const relationObjectMetadataId = watchFormValue( 'relation.objectMetadataId', @@ -67,7 +81,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ return ( <SettingsDataModelPreviewFormCard preview={ - <StyledPreviewContent> + <StyledPreviewContent isMobile={isMobile}> <StyledFieldPreviewCard fieldMetadataItem={fieldMetadataItem} shrink @@ -78,6 +92,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ src={relationTypeConfig.imageSrc} flip={relationTypeConfig.isImageFlipped} alt={relationTypeConfig.label} + isMobile={isMobile} /> <StyledFieldPreviewCard fieldMetadataItem={{ @@ -102,6 +117,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ form={ <SettingsDataModelFieldRelationForm fieldMetadataItem={fieldMetadataItem} + objectMetadataItem={objectMetadataItem} /> } /> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/hooks/useRelationSettingsFormInitialValues.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/hooks/useRelationSettingsFormInitialValues.ts index bd4b2badd0e2..298c3ad6dc24 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/hooks/useRelationSettingsFormInitialValues.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/hooks/useRelationSettingsFormInitialValues.ts @@ -2,15 +2,17 @@ import { useMemo } from 'react'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; +import { SettingsDataModelFieldPreviewCardProps } from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard'; import { RelationDefinitionType } from '~/generated-metadata/graphql'; export const useRelationSettingsFormInitialValues = ({ fieldMetadataItem, + objectMetadataItem, }: { fieldMetadataItem?: Pick<FieldMetadataItem, 'type' | 'relationDefinition'>; + objectMetadataItem?: SettingsDataModelFieldPreviewCardProps['objectMetadataItem']; }) => { const { objectMetadataItems } = useFilteredObjectMetadataItems(); @@ -28,11 +30,13 @@ export const useRelationSettingsFormInitialValues = ({ const initialRelationObjectMetadataItem = useMemo( () => relationObjectMetadataItemFromFieldMetadata ?? - objectMetadataItems.find( - ({ nameSingular }) => nameSingular === CoreObjectNameSingular.Person, - ) ?? + objectMetadataItem ?? objectMetadataItems.filter(isObjectMetadataAvailableForRelation)[0], - [objectMetadataItems, relationObjectMetadataItemFromFieldMetadata], + [ + objectMetadataItem, + objectMetadataItems, + relationObjectMetadataItemFromFieldMetadata, + ], ); const initialRelationType = @@ -44,7 +48,12 @@ export const useRelationSettingsFormInitialValues = ({ disableRelationEdition: !!relationFieldMetadataItem, initialRelationFieldMetadataItem: relationFieldMetadataItem ?? { icon: initialRelationObjectMetadataItem.icon ?? 'IconUsers', - label: '', + label: [ + RelationDefinitionType.ManyToMany, + RelationDefinitionType.ManyToOne, + ].includes(initialRelationType) + ? initialRelationObjectMetadataItem.labelPlural + : initialRelationObjectMetadataItem.labelSingular, }, initialRelationObjectMetadataItem, initialRelationType, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx index 9d3c97628f2e..038c2a14362d 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx @@ -1,8 +1,7 @@ import styled from '@emotion/styled'; import { DropResult } from '@hello-pangea/dnd'; -import { useState } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; -import { IconPlus } from 'twenty-ui'; +import { IconPlus, IconTool, MAIN_COLORS } from 'twenty-ui'; import { z } from 'zod'; import { @@ -25,6 +24,10 @@ import { moveArrayItem } from '~/utils/array/moveArrayItem'; import { toSpliced } from '~/utils/array/toSpliced'; import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString'; +import { EXPANDED_WIDTH_ANIMATION_VARIANTS } from '@/settings/constants/ExpandedWidthAnimationVariants'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useRecoilValue } from 'recoil'; import { SettingsDataModelFieldSelectFormOptionRow } from './SettingsDataModelFieldSelectFormOptionRow'; export const settingsDataModelFieldSelectFormSchema = z.object({ @@ -57,13 +60,49 @@ const StyledContainer = styled(CardContent)` padding-bottom: ${({ theme }) => theme.spacing(3.5)}; `; -const StyledLabel = styled.span` +const StyledOptionsLabel = styled.div<{ + isAdvancedModeEnabled: boolean; +}>` color: ${({ theme }) => theme.font.color.light}; - display: block; font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; - margin-bottom: 6px; + margin-bottom: ${({ theme }) => theme.spacing(1.5)}; margin-top: ${({ theme }) => theme.spacing(1)}; + width: 100%; + margin-left: ${({ theme, isAdvancedModeEnabled }) => + theme.spacing(isAdvancedModeEnabled ? 10 : 0)}; +`; + +const StyledApiKeyContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + width: 100%; +`; + +const StyledApiKey = styled.span` + 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.5)}; + margin-top: ${({ theme }) => theme.spacing(1)}; + width: 100%; + white-space: nowrap; +`; + +const StyledLabelContainer = styled.div` + display: flex; +`; + +const StyledIconContainer = styled.div` + border-right: 1px solid ${MAIN_COLORS.yellow}; + display: flex; + + margin-bottom: ${({ theme }) => theme.spacing(1.5)}; + margin-top: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledIconTool = styled(IconTool)` + margin-right: ${({ theme }) => theme.spacing(0.5)}; `; const StyledFooter = styled(CardFooter)` @@ -79,9 +118,9 @@ const StyledButton = styled(LightButton)` export const SettingsDataModelFieldSelectForm = ({ fieldMetadataItem, }: SettingsDataModelFieldSelectFormProps) => { - const [focusedOptionId, setFocusedOptionId] = useState(''); const { initialDefaultValue, initialOptions } = useSelectSettingsFormInitialValues({ fieldMetadataItem }); + const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); const { control, @@ -183,17 +222,13 @@ export const SettingsDataModelFieldSelectForm = ({ const handleAddOption = () => { const newOptions = getOptionsWithNewOption(); - setFormValue('options', newOptions); + setFormValue('options', newOptions, { shouldDirty: true }); }; const handleInputEnter = () => { const newOptions = getOptionsWithNewOption(); - setFormValue('options', newOptions); - - const lastOptionId = newOptions[newOptions.length - 1].id; - - setFocusedOptionId(lastOptionId); + setFormValue('options', newOptions, { shouldDirty: true }); }; return ( @@ -211,7 +246,33 @@ export const SettingsDataModelFieldSelectForm = ({ render={({ field: { onChange, value: options } }) => ( <> <StyledContainer> - <StyledLabel>Options</StyledLabel> + <StyledLabelContainer> + <AnimatePresence> + {isAdvancedModeEnabled && ( + <motion.div + initial="initial" + animate="animate" + exit="exit" + variants={EXPANDED_WIDTH_ANIMATION_VARIANTS} + > + <StyledApiKeyContainer> + <StyledIconContainer> + <StyledIconTool + size={12} + color={MAIN_COLORS.yellow} + /> + </StyledIconContainer> + <StyledApiKey>API keys</StyledApiKey> + </StyledApiKeyContainer> + </motion.div> + )} + </AnimatePresence> + <StyledOptionsLabel + isAdvancedModeEnabled={isAdvancedModeEnabled} + > + Options + </StyledOptionsLabel> + </StyledLabelContainer> <DraggableList onDragEnd={(result) => handleDragEnd(options, result, onChange)} draggableItems={ @@ -227,7 +288,7 @@ export const SettingsDataModelFieldSelectForm = ({ <SettingsDataModelFieldSelectFormOptionRow key={option.id} option={option} - focused={focusedOptionId === option.id} + isNewRow={index === options.length - 1} onChange={(nextOption) => { const nextOptions = toSpliced( options, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx index 121aa15e0e07..d0c74e805c5a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx @@ -13,6 +13,7 @@ import { import { v4 } from 'uuid'; import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem'; +import { EXPANDED_WIDTH_ANIMATION_VARIANTS } from '@/settings/constants/ExpandedWidthAnimationVariants'; import { OPTION_VALUE_MAXIMUM_LENGTH } from '@/settings/data-model/constants/OptionValueMaximumLength'; import { getOptionValueFromLabel } from '@/settings/data-model/fields/forms/select/utils/getOptionValueFromLabel'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; @@ -23,6 +24,9 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useRecoilValue } from 'recoil'; type SettingsDataModelFieldSelectFormOptionRowProps = { className?: string; @@ -33,7 +37,7 @@ type SettingsDataModelFieldSelectFormOptionRowProps = { onRemoveAsDefault?: () => void; onInputEnter?: () => void; option: FieldMetadataItemOption; - focused?: boolean; + isNewRow?: boolean; }; const StyledRow = styled.div` @@ -45,19 +49,29 @@ const StyledRow = styled.div` const StyledColorSample = styled(ColorSample)` cursor: pointer; - margin-left: 9px; - margin-right: 14px; + margin-top: ${({ theme }) => theme.spacing(1)}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; + + margin-right: ${({ theme }) => theme.spacing(3.5)}; + margin-left: ${({ theme }) => theme.spacing(3.5)}; `; const StyledOptionInput = styled(TextInput)` - flex: 1 0 auto; - margin-right: ${({ theme }) => theme.spacing(2)}; - + flex-grow: 1; + width: 100%; & input { height: ${({ theme }) => theme.spacing(6)}; } `; +const StyledIconGripVertical = styled(IconGripVertical)` + margin-right: ${({ theme }) => theme.spacing(0.75)}; +`; + +const StyledLightIconButton = styled(LightIconButton)` + margin-left: ${({ theme }) => theme.spacing(2)}; +`; + export const SettingsDataModelFieldSelectFormOptionRow = ({ className, isDefault, @@ -67,8 +81,9 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({ onRemoveAsDefault, onInputEnter, option, - focused, + isNewRow, }: SettingsDataModelFieldSelectFormOptionRowProps) => { + const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); const theme = useTheme(); const dropdownIds = useMemo(() => { @@ -90,11 +105,34 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({ return ( <StyledRow className={className}> - <IconGripVertical + <StyledIconGripVertical + style={{ minWidth: theme.icon.size.md }} size={theme.icon.size.md} stroke={theme.icon.stroke.sm} color={theme.font.color.extraLight} /> + <AnimatePresence> + {isAdvancedModeEnabled && ( + <motion.div + initial="initial" + animate="animate" + exit="exit" + variants={EXPANDED_WIDTH_ANIMATION_VARIANTS} + > + <StyledOptionInput + value={option.value} + onChange={(input) => + onChange({ + ...option, + value: getOptionValueFromLabel(input), + }) + } + RightIcon={isDefault ? IconCheck : undefined} + maxLength={OPTION_VALUE_MAXIMUM_LENGTH} + /> + </motion.div> + )} + </AnimatePresence> <Dropdown dropdownId={dropdownIds.color} dropdownPlacement="bottom-start" @@ -122,17 +160,23 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({ /> <StyledOptionInput value={option.label} - onChange={(label) => + onChange={(label) => { + const optionNameHasBeenEdited = !( + option.value === getOptionValueFromLabel(option.label) + ); onChange({ ...option, label, - value: getOptionValueFromLabel(label), - }) - } - focused={focused} + value: optionNameHasBeenEdited + ? option.value + : getOptionValueFromLabel(label), + }); + }} RightIcon={isDefault ? IconCheck : undefined} maxLength={OPTION_VALUE_MAXIMUM_LENGTH} onInputEnter={handleInputEnter} + autoFocusOnMount={isNewRow} + autoSelectOnMount={isNewRow} /> <Dropdown dropdownId={dropdownIds.actions} @@ -140,7 +184,9 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({ dropdownHotkeyScope={{ scope: dropdownIds.actions, }} - clickableComponent={<LightIconButton Icon={IconDotsVertical} />} + clickableComponent={ + <StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} /> + } dropdownComponents={ <DropdownMenu> <DropdownMenuItemsContainer> diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema.ts b/packages/twenty-front/src/modules/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema.ts index 085aa958e883..18ae87b973b0 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema.ts @@ -1,14 +1,13 @@ -import { z } from 'zod'; - import { settingsDataModelFieldDescriptionFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm'; import { settingsDataModelFieldIconLabelFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm'; import { settingsDataModelFieldSettingsFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; -import { settingsDataModelFieldTypeFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect'; +import { z } from 'zod'; +import { settingsDataModelFieldTypeFormSchema } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect'; -export const settingsFieldFormSchema = (existingLabels?: string[]) => { +export const settingsFieldFormSchema = (existingOtherLabels?: string[]) => { return z .object({}) - .merge(settingsDataModelFieldIconLabelFormSchema(existingLabels)) + .merge(settingsDataModelFieldIconLabelFormSchema(existingOtherLabels)) .merge(settingsDataModelFieldDescriptionFormSchema()) .merge(settingsDataModelFieldTypeFormSchema) .and(settingsDataModelFieldSettingsFormSchema); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview.tsx index a7b69af6a905..1fbefb2d3238 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview.tsx @@ -18,7 +18,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; export type SettingsDataModelFieldPreviewProps = { fieldMetadataItem: Pick< FieldMetadataItem, - 'icon' | 'label' | 'type' | 'defaultValue' | 'options' + 'icon' | 'label' | 'type' | 'defaultValue' | 'options' | 'settings' > & { id?: string; name?: string; @@ -132,6 +132,7 @@ export const SettingsDataModelFieldPreview = ({ relationObjectMetadataNameSingular: relationObjectMetadataItem?.nameSingular, options: fieldMetadataItem.options ?? [], + settings: fieldMetadataItem.settings, }, defaultValue: fieldMetadataItem.defaultValue, }, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx index 3df8c6e7664a..6b0fb477eef2 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/__stories__/SettingsDataModelFieldPreviewCard.stories.tsx @@ -6,14 +6,23 @@ 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, - mockedOpportunityObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldPreviewCard } from '../SettingsDataModelFieldPreviewCard'; +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 meta: Meta<typeof SettingsDataModelFieldPreviewCard> = { title: 'Modules/Settings/DataModel/Fields/Preview/SettingsDataModelFieldPreviewCard', @@ -38,7 +47,7 @@ type Story = StoryObj<typeof SettingsDataModelFieldPreviewCard>; 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 }) => ( - <RecoilRoot> - <MockedProvider> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <ObjectMetadataItemsProvider>{children}</ObjectMetadataItemsProvider> - </SnackBarProviderScope> - </MockedProvider> - </RecoilRoot> +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/fields/preview/utils/getCurrencyFieldPreviewValue.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/getCurrencyFieldPreviewValue.ts index fc139304bd81..2fd5b32eae40 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/getCurrencyFieldPreviewValue.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/getCurrencyFieldPreviewValue.ts @@ -16,9 +16,11 @@ export const getCurrencyFieldPreviewValue = ({ }): FieldCurrencyValue | null => { if (fieldMetadataItem.type !== FieldMetadataType.Currency) return null; - const placeholderDefaultValue = getSettingsFieldTypeConfig( + const currencyFieldTypeConfig = getSettingsFieldTypeConfig( FieldMetadataType.Currency, - ).exampleValue; + ); + + const placeholderDefaultValue = currencyFieldTypeConfig.exampleValue; return currencyFieldDefaultValueSchema .transform((value) => ({ diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx index 4fe7cccf71ff..188e5b97211c 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; import { Link } from 'react-router-dom'; import { IconComponent, IconTwentyStar } from 'twenty-ui'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -11,11 +11,11 @@ type SettingsObjectFieldDataTypeProps = { to?: string; Icon?: IconComponent; label?: string; - value: SettingsSupportedFieldType; + value: SettingsFieldType; }; const StyledDataType = styled.div<{ - value: SettingsSupportedFieldType; + value: SettingsFieldType; to?: string; }>` align-items: center; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/states/settingsObjectIndexesFamilyState.ts b/packages/twenty-front/src/modules/settings/data-model/object-details/states/settingsObjectIndexesFamilyState.ts new file mode 100644 index 000000000000..939fcf5920c7 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/states/settingsObjectIndexesFamilyState.ts @@ -0,0 +1,14 @@ +import { IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem'; +import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; + +export type SortedIndexByTableFamilyStateKey = { + objectMetadataItemId: string; +}; + +export const settingsObjectIndexesFamilyState = createFamilyState< + IndexMetadataItem[] | null, + SortedIndexByTableFamilyStateKey +>({ + key: 'settingsObjectIndexesFamilyState', + defaultValue: null, +}); 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/types/CompositeFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/CompositeFieldType.ts new file mode 100644 index 000000000000..ddf7b0d579e0 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/CompositeFieldType.ts @@ -0,0 +1,20 @@ +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { PickLiteral } from '~/types/PickLiteral'; + +// TODO: add to future fullstack shared package +export const COMPOSITE_FIELD_TYPES = [ + 'CURRENCY', + 'EMAILS', + 'LINKS', + 'ADDRESS', + 'PHONES', + 'FULL_NAME', + 'ACTOR', +] as const; + +type CompositeFieldTypeBaseLiteral = (typeof COMPOSITE_FIELD_TYPES)[number]; + +export type CompositeFieldType = PickLiteral< + FieldType, + CompositeFieldTypeBaseLiteral +>; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/FieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/FieldType.ts new file mode 100644 index 000000000000..66a579162c3c --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/FieldType.ts @@ -0,0 +1,3 @@ +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export type FieldType = `${FieldMetadataType}`; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/NonCompositeFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/NonCompositeFieldType.ts new file mode 100644 index 000000000000..fdf439ed0e90 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/NonCompositeFieldType.ts @@ -0,0 +1,8 @@ +import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType'; +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { ExcludeLiteral } from '~/types/ExcludeLiteral'; + +export type NonCompositeFieldType = ExcludeLiteral< + FieldType, + CompositeFieldType +>; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsCompositeFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsCompositeFieldType.ts new file mode 100644 index 000000000000..87b96acaaba0 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/SettingsCompositeFieldType.ts @@ -0,0 +1,8 @@ +import { CompositeFieldType } from '@/settings/data-model/types/CompositeFieldType'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { PickLiteral } from '~/types/PickLiteral'; + +export type SettingsCompositeFieldType = PickLiteral< + SettingsFieldType, + CompositeFieldType +>; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsExcludedFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsExcludedFieldType.ts new file mode 100644 index 000000000000..3c7041e9f233 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/SettingsExcludedFieldType.ts @@ -0,0 +1,7 @@ +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { PickLiteral } from '~/types/PickLiteral'; + +export type SettingsExcludedFieldType = PickLiteral< + FieldType, + 'POSITION' | 'TS_VECTOR' +>; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsFieldType.ts new file mode 100644 index 000000000000..98f2a491a6b5 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/SettingsFieldType.ts @@ -0,0 +1,8 @@ +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { SettingsExcludedFieldType } from '@/settings/data-model/types/SettingsExcludedFieldType'; +import { ExcludeLiteral } from '~/types/ExcludeLiteral'; + +export type SettingsFieldType = ExcludeLiteral< + FieldType, + SettingsExcludedFieldType +>; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsNonCompositeFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsNonCompositeFieldType.ts new file mode 100644 index 000000000000..73aabf77df52 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/types/SettingsNonCompositeFieldType.ts @@ -0,0 +1,8 @@ +import { NonCompositeFieldType } from '@/settings/data-model/types/NonCompositeFieldType'; +import { SettingsExcludedFieldType } from '@/settings/data-model/types/SettingsExcludedFieldType'; +import { ExcludeLiteral } from '~/types/ExcludeLiteral'; + +export type SettingsNonCompositeFieldType = ExcludeLiteral< + NonCompositeFieldType, + SettingsExcludedFieldType +>; diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts deleted file mode 100644 index 0149601685e3..000000000000 --- a/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -export type SettingsSupportedFieldType = Exclude< - FieldMetadataType, - FieldMetadataType.Position ->; 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/data-model/utils/getSettingsFieldTypeConfig.ts b/packages/twenty-front/src/modules/settings/data-model/utils/getSettingsFieldTypeConfig.ts index 3278a1dee42a..307ff6a3f6f6 100644 --- a/packages/twenty-front/src/modules/settings/data-model/utils/getSettingsFieldTypeConfig.ts +++ b/packages/twenty-front/src/modules/settings/data-model/utils/getSettingsFieldTypeConfig.ts @@ -1,13 +1,6 @@ import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; -import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; -export const getSettingsFieldTypeConfig = <T extends FieldMetadataType>( - fieldType: T, -) => - (isFieldTypeSupportedInSettings(fieldType) - ? SETTINGS_FIELD_TYPE_CONFIGS[fieldType] - : undefined) as T extends SettingsSupportedFieldType - ? (typeof SETTINGS_FIELD_TYPE_CONFIGS)[T] - : undefined; +export const getSettingsFieldTypeConfig = (fieldType: SettingsFieldType) => { + return SETTINGS_FIELD_TYPE_CONFIGS[fieldType as SettingsFieldType]; +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/utils/isFieldTypeSupportedInSettings.ts b/packages/twenty-front/src/modules/settings/data-model/utils/isFieldTypeSupportedInSettings.ts index 4d9a377b704a..52171d87e40a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/utils/isFieldTypeSupportedInSettings.ts +++ b/packages/twenty-front/src/modules/settings/data-model/utils/isFieldTypeSupportedInSettings.ts @@ -1,8 +1,7 @@ import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; export const isFieldTypeSupportedInSettings = ( - fieldType: FieldMetadataType, -): fieldType is SettingsSupportedFieldType => - fieldType in SETTINGS_FIELD_TYPE_CONFIGS; + fieldType: FieldType, +): fieldType is SettingsFieldType => fieldType in SETTINGS_FIELD_TYPE_CONFIGS; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx index 4d4dc8d45e6f..d5868b1665d4 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx @@ -1,13 +1,17 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { IconChevronRight } from 'twenty-ui'; +import { IconChevronRight, MOBILE_VIEWPORT } from 'twenty-ui'; import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; export const StyledApisFieldTableRow = styled(TableRow)` - grid-template-columns: 312px 132px 68px; + grid-template-columns: 312px auto 28px; + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: 100%; + grid-template-columns: 12fr 4fr; + } `; const StyledNameTableCell = styled(TableCell)` @@ -18,6 +22,7 @@ const StyledNameTableCell = styled(TableCell)` const StyledIconTableCell = styled(TableCell)` justify-content: center; padding-right: ${({ theme }) => theme.spacing(1)}; + padding-left: 0; `; const StyledIconChevronRight = styled(IconChevronRight)` diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx index ede12c34bf6c..0d1a9fc12660 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx @@ -1,5 +1,3 @@ -import styled from '@emotion/styled'; - import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow'; @@ -10,15 +8,25 @@ import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableRow } from '@/ui/layout/table/components/TableRow'; +import styled from '@emotion/styled'; +import { MOBILE_VIEWPORT } from 'twenty-ui'; const StyledTableBody = styled(TableBody)` border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - max-height: 260px; - overflow-y: auto; + @media (max-width: ${MOBILE_VIEWPORT}px) { + padding-top: ${({ theme }) => theme.spacing(3)}; + display: flex; + justify-content: space-between; + scroll-behavior: smooth; + } `; const StyledTableRow = styled(TableRow)` - grid-template-columns: 312px 132px 68px; + grid-template-columns: 312px auto 28px; + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: 95%; + grid-template-columns: 20fr 2fr; + } `; export const SettingsApiKeysTable = () => { diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookTableRow.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookTableRow.tsx index 0c093d5ecbd2..d559f89a200a 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookTableRow.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { IconChevronRight } from 'twenty-ui'; @@ -8,12 +7,13 @@ import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; export const StyledApisFieldTableRow = styled(TableRow)` - grid-template-columns: 444px 68px; + grid-template-columns: 1fr 28px; `; const StyledIconTableCell = styled(TableCell)` justify-content: center; padding-right: ${({ theme }) => theme.spacing(1)}; + padding-left: 0; `; const StyledUrlTableCell = styled(TableCell)` diff --git a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx new file mode 100644 index 000000000000..eb2e359fff16 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx @@ -0,0 +1,59 @@ +import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState'; +import styled from '@emotion/styled'; +import { ResponsiveLine } from '@nivo/line'; +import { Section } from '@react-email/components'; +import { useRecoilValue } from 'recoil'; +import { H2Title } from 'twenty-ui'; + +export type NivoLineInput = { + id: string | number; + color?: string; + data: Array<{ + x: number | string | Date; + y: number | string | Date; + }>; +}; +const StyledGraphContainer = styled.div` + height: 200px; + width: 100%; +`; +export const SettingsDeveloppersWebhookUsageGraph = () => { + const webhookGraphData = useRecoilValue(webhookGraphDataState); + + return ( + <> + {webhookGraphData.length ? ( + <Section> + <H2Title title="Statistics" /> + <StyledGraphContainer> + <ResponsiveLine + data={webhookGraphData} + colors={(d) => d.color} + margin={{ top: 0, right: 0, bottom: 50, left: 60 }} + xFormat="time:%Y-%m-%d %H:%M%" + xScale={{ + type: 'time', + useUTC: false, + format: '%Y-%m-%d %H:%M:%S', + precision: 'hour', + }} + yScale={{ + type: 'linear', + }} + axisBottom={{ + tickValues: 'every day', + format: '%b %d', + }} + enableTouchCrosshair={true} + enableGridY={false} + enableGridX={false} + enablePoints={false} + /> + </StyledGraphContainer> + </Section> + ) : ( + <></> + )} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraphEffect.tsx b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraphEffect.tsx new file mode 100644 index 000000000000..0c26350243b2 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraphEffect.tsx @@ -0,0 +1,101 @@ +import { NivoLineInput } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph'; +import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState'; +import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; + +type SettingsDevelopersWebhookUsageGraphEffectProps = { + webhookId: string; +}; + +export const SettingsDevelopersWebhookUsageGraphEffect = ({ + webhookId, +}: SettingsDevelopersWebhookUsageGraphEffectProps) => { + const setWebhookGraphData = useSetRecoilState(webhookGraphDataState); + + const { enqueueSnackBar } = useSnackBar(); + + useEffect(() => { + const fetchData = async () => { + try { + const queryString = new URLSearchParams({ + webhookIdRequest: webhookId, + }).toString(); + const token = 'REPLACE_ME'; + const response = await fetch( + `https://api.eu-central-1.aws.tinybird.co/v0/pipes/getWebhooksAnalytics.json?${queryString}`, + { + headers: { + Authorization: 'Bearer ' + token, + }, + }, + ); + const result = await response.json(); + + if (!response.ok) { + enqueueSnackBar('Something went wrong while fetching webhook usage', { + variant: SnackBarVariant.Error, + }); + return; + } + + const graphInput = result.data + .flatMap( + (dataRow: { + start_interval: string; + failure_count: number; + success_count: number; + }) => [ + { + x: dataRow.start_interval, + y: dataRow.failure_count, + id: 'failure_count', + color: 'red', + }, + { + x: dataRow.start_interval, + y: dataRow.success_count, + id: 'success_count', + color: 'green', + }, + ], + ) + .reduce( + ( + acc: NivoLineInput[], + { + id, + x, + y, + color, + }: { id: string; x: string; y: number; color: string }, + ) => { + const existingGroupIndex = acc.findIndex( + (group) => group.id === id, + ); + const isExistingGroup = existingGroupIndex !== -1; + + if (isExistingGroup) { + return acc.map((group, index) => + index === existingGroupIndex + ? { ...group, data: [...group.data, { x, y }] } + : group, + ); + } else { + return [...acc, { id, color, data: [{ x, y }] }]; + } + }, + [], + ); + setWebhookGraphData(graphInput); + } catch (error) { + enqueueSnackBar('Something went wrong while fetching webhook usage', { + variant: SnackBarVariant.Error, + }); + } + }; + fetchData(); + }, [enqueueSnackBar, setWebhookGraphData, webhookId]); + return <></>; +}; diff --git a/packages/twenty-front/src/modules/settings/developers/webhook/states/webhookGraphDataState.ts b/packages/twenty-front/src/modules/settings/developers/webhook/states/webhookGraphDataState.ts new file mode 100644 index 000000000000..bb91864e2782 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/developers/webhook/states/webhookGraphDataState.ts @@ -0,0 +1,7 @@ +import { NivoLineInput } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph'; +import { createState } from 'twenty-ui'; + +export const webhookGraphDataState = createState<NivoLineInput[]>({ + key: 'webhookGraphData', + defaultValue: [], +}); diff --git a/packages/twenty-front/src/modules/settings/hooks/useExpandedHeightAnimation.tsx b/packages/twenty-front/src/modules/settings/hooks/useExpandedHeightAnimation.tsx new file mode 100644 index 000000000000..0e7723c67bba --- /dev/null +++ b/packages/twenty-front/src/modules/settings/hooks/useExpandedHeightAnimation.tsx @@ -0,0 +1,52 @@ +import { useEffect, useRef, useState } from 'react'; +import { isDefined } from 'twenty-ui'; + +const transitionValues = { + transition: { + opacity: { duration: 0.2 }, + height: { duration: 0.4 }, + }, +}; + +const commonStyles = { + opacity: 0, + height: 0, + ...transitionValues, +}; + +const advancedSectionAnimationConfig = ( + isExpanded: boolean, + measuredHeight: number, +) => ({ + initial: { + ...commonStyles, + }, + animate: { + opacity: 1, + height: isExpanded ? measuredHeight : 0, + ...transitionValues, + }, + exit: { + ...commonStyles, + }, +}); + +export const useExpandedHeightAnimation = (isExpanded: boolean) => { + const contentRef = useRef<HTMLDivElement>(null); + const [measuredHeight, setMeasuredHeight] = useState(0); + + useEffect(() => { + if (isDefined(contentRef.current)) { + setMeasuredHeight(contentRef.current.scrollHeight); + } + }, [isExpanded]); + + return { + contentRef, + measuredHeight, + motionAnimationVariants: advancedSectionAnimationConfig( + isExpanded, + measuredHeight, + ), + }; +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts b/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts index 38a639127fb5..d70dd9b493e0 100644 --- a/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts +++ b/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts @@ -29,15 +29,17 @@ export const useSettingsIntegrationCategories = ({ name }) => name === 'stripe', )?.isActive; + const allIntegrations = getSettingsIntegrationAll({ + isAirtableIntegrationEnabled, + isAirtableIntegrationActive, + isPostgresqlIntegrationEnabled, + isPostgresqlIntegrationActive, + isStripeIntegrationEnabled, + isStripeIntegrationActive, + }); + return [ - getSettingsIntegrationAll({ - isAirtableIntegrationEnabled, - isAirtableIntegrationActive, - isPostgresqlIntegrationEnabled, - isPostgresqlIntegrationActive, - isStripeIntegrationEnabled, - isStripeIntegrationActive, - }), + ...(allIntegrations.integrations.length > 0 ? [allIntegrations] : []), SETTINGS_INTEGRATION_ZAPIER_CATEGORY, SETTINGS_INTEGRATION_WINDMILL_CATEGORY, SETTINGS_INTEGRATION_REQUEST_CATEGORY, diff --git a/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts b/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts index a61532673dc9..97061ade9951 100644 --- a/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts +++ b/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts @@ -1,3 +1,4 @@ +import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration'; import { SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory'; export const getSettingsIntegrationAll = ({ @@ -18,44 +19,32 @@ export const getSettingsIntegrationAll = ({ key: 'all', title: 'All', integrations: [ - { + isAirtableIntegrationEnabled && { from: { key: 'airtable', image: '/images/integrations/airtable-logo.png', }, - type: !isAirtableIntegrationEnabled - ? 'Soon' - : isAirtableIntegrationActive - ? 'Active' - : 'Add', + type: isAirtableIntegrationActive ? 'Active' : 'Add', text: 'Airtable', link: '/settings/integrations/airtable', }, - { + isPostgresqlIntegrationEnabled && { from: { key: 'postgresql', image: '/images/integrations/postgresql-logo.png', }, - type: !isPostgresqlIntegrationEnabled - ? 'Soon' - : isPostgresqlIntegrationActive - ? 'Active' - : 'Add', + type: isPostgresqlIntegrationActive ? 'Active' : 'Add', text: 'PostgreSQL', link: '/settings/integrations/postgresql', }, - { + isStripeIntegrationEnabled && { from: { key: 'stripe', image: '/images/integrations/stripe-logo.png', }, - type: !isStripeIntegrationEnabled - ? 'Soon' - : isStripeIntegrationActive - ? 'Active' - : 'Add', + type: isStripeIntegrationActive ? 'Active' : 'Add', text: 'Stripe', link: '/settings/integrations/stripe', }, - ], + ].filter(Boolean) as SettingsIntegration[], }); 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/SettingsServerlessFunctionNewForm.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx index b9ba77a74ce8..46803ca08534 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionNewForm.tsx @@ -1,9 +1,9 @@ -import { H2Title } from 'twenty-ui'; -import { Section } from '@/ui/layout/section/components/Section'; -import { TextInput } from '@/ui/input/components/TextInput'; +import { ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; import { TextArea } from '@/ui/input/components/TextArea'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { Section } from '@/ui/layout/section/components/Section'; import styled from '@emotion/styled'; -import { ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; +import { H2Title } from 'twenty-ui'; const StyledInputsContainer = styled.div` display: flex; @@ -25,7 +25,7 @@ export const SettingsServerlessFunctionNewForm = ({ <TextInput placeholder="Name" fullWidth - focused + autoFocusOnMount value={formValues.name} onChange={onChange('name')} /> 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 a7b857bf621a..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,19 +1,15 @@ +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'; -import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import styled from '@emotion/styled'; -import { useNavigate } from 'react-router-dom'; -import { Key } from 'ts-key-enum'; import { ServerlessFunction } from '~/generated-metadata/graphql'; -import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount'; const StyledTableRow = styled(TableRow)` grid-template-columns: 312px 132px 68px; @@ -25,42 +21,32 @@ const StyledTableBody = styled(TableBody)` export const SettingsServerlessFunctionsTable = () => { const { serverlessFunctions } = useGetManyServerlessFunctions(); - const navigate = useNavigate(); - useHotkeyScopeOnMount( - SettingsServerlessFunctionHotkeyScope.ServerlessFunction, - ); - - useScopedHotkeys( - [Key.Enter], - () => { - navigate(getSettingsPagePath(SettingsPath.NewServerlessFunction)); - }, - SettingsServerlessFunctionHotkeyScope.ServerlessFunction, - ); return ( <> {serverlessFunctions.length ? ( - <Table> - <StyledTableRow> - <TableHeader>Name</TableHeader> - <TableHeader>Runtime</TableHeader> - <TableHeader></TableHeader> - </StyledTableRow> - <StyledTableBody> - {serverlessFunctions.map( - (serverlessFunction: ServerlessFunction) => ( - <SettingsServerlessFunctionsFieldItemTableRow - key={serverlessFunction.id} - serverlessFunction={serverlessFunction} - to={getSettingsPagePath(SettingsPath.ServerlessFunctions, { - id: serverlessFunction.id, - })} - /> - ), - )} - </StyledTableBody> - </Table> + <SettingsPageContainer> + <Table> + <StyledTableRow> + <TableHeader>Name</TableHeader> + <TableHeader>Runtime</TableHeader> + <TableHeader></TableHeader> + </StyledTableRow> + <StyledTableBody> + {serverlessFunctions.map( + (serverlessFunction: ServerlessFunction) => ( + <SettingsServerlessFunctionsFieldItemTableRow + key={serverlessFunction.id} + serverlessFunction={serverlessFunction} + to={getSettingsPagePath(SettingsPath.ServerlessFunctions, { + id: serverlessFunction.id, + })} + /> + ), + )} + </StyledTableBody> + </Table> + </SettingsPageContainer> ) : ( <SettingsServerlessFunctionsTableEmpty /> )} diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx index 6329eb7ac538..5f8886871359 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab.tsx @@ -1,9 +1,8 @@ -import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { Button } from '@/ui/input/button/components/Button'; -import { CodeEditor } from '@/ui/input/code-editor/components/CodeEditor'; +import { CodeEditor, File } from '@/ui/input/code-editor/components/CodeEditor'; import { CoreEditorHeader } from '@/ui/input/code-editor/components/CodeEditorHeader'; import { Section } from '@/ui/layout/section/components/Section'; import { TabList } from '@/ui/layout/tab/components/TabList'; @@ -13,13 +12,16 @@ import { useNavigate } from 'react-router-dom'; import { Key } from 'ts-key-enum'; import { H2Title, IconGitCommit, IconPlayerPlay, IconRestore } from 'twenty-ui'; import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount'; +import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId'; +import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; +import { useRecoilValue } from 'recoil'; const StyledTabList = styled(TabList)` border-bottom: none; `; export const SettingsServerlessFunctionCodeEditorTab = ({ - formValues, + files, handleExecute, handlePublish, handleReset, @@ -28,15 +30,19 @@ export const SettingsServerlessFunctionCodeEditorTab = ({ onChange, setIsCodeValid, }: { - formValues: ServerlessFunctionFormValues; + files: File[]; handleExecute: () => void; handlePublish: () => void; handleReset: () => void; resetDisabled: boolean; publishDisabled: boolean; - onChange: (key: string) => (value: string) => void; + onChange: (filePath: string, value: string) => void; setIsCodeValid: (isCodeValid: boolean) => void; }) => { + const { activeTabIdState } = useTabList( + SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID, + ); + const activeTabId = useRecoilValue(activeTabIdState); const TestButton = ( <Button title="Test" @@ -68,21 +74,15 @@ export const SettingsServerlessFunctionCodeEditorTab = ({ /> ); - const TAB_LIST_COMPONENT_ID = 'serverless-function-editor'; - const HeaderTabList = ( <StyledTabList - tabListId={TAB_LIST_COMPONENT_ID} - tabs={[{ id: 'index.ts', title: 'index.ts' }]} + tabListId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID} + tabs={files.map((file) => { + return { id: file.path, title: file.path.split('/').at(-1) || '' }; + })} /> ); - const Header = ( - <CoreEditorHeader - leftNodes={[HeaderTabList]} - rightNodes={[ResetButton, PublishButton, TestButton]} - /> - ); const navigate = useNavigate(); useHotkeyScopeOnMount( SettingsServerlessFunctionHotkeyScope.ServerlessFunctionEditorTab, @@ -95,18 +95,25 @@ export const SettingsServerlessFunctionCodeEditorTab = ({ }, SettingsServerlessFunctionHotkeyScope.ServerlessFunctionEditorTab, ); + return ( <Section> <H2Title title="Code your function" description="Write your function (in typescript) below" /> - <CodeEditor - value={formValues.code} - onChange={onChange('code')} - setIsCodeValid={setIsCodeValid} - header={Header} + <CoreEditorHeader + leftNodes={[HeaderTabList]} + rightNodes={[ResetButton, PublishButton, TestButton]} /> + {activeTabId && ( + <CodeEditor + files={files} + currentFilePath={activeTabId} + onChange={(newCodeValue) => onChange(activeTabId, newCodeValue)} + setIsCodeValid={setIsCodeValid} + /> + )} </Section> ); }; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx index 6eed09db5122..b2d54cbc03f9 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx @@ -44,28 +44,6 @@ export const SettingsServerlessFunctionTestTab = ({ settingsServerlessFunctionOutput.error || ''; - const InputHeader = ( - <CoreEditorHeader - title={'Input'} - rightNodes={[ - <Button - title="Run Function" - variant="primary" - accent="blue" - size="small" - Icon={IconPlayerPlay} - onClick={handleExecute} - />, - ]} - /> - ); - - const OutputHeader = ( - <CoreEditorHeader - leftNodes={[<SettingsServerlessFunctionsOutputMetadataInfo />]} - rightNodes={[<LightCopyIconButton copyText={result} />]} - /> - ); const navigate = useNavigate(); useHotkeyScopeOnMount( SettingsServerlessFunctionHotkeyScope.ServerlessFunctionTestTab, @@ -86,20 +64,52 @@ export const SettingsServerlessFunctionTestTab = ({ description='Insert a JSON input, then press "Run" to test your function.' /> <StyledInputsContainer> - <CodeEditor - value={settingsServerlessFunctionInput} - height={200} - onChange={setSettingsServerlessFunctionInput} - language={'json'} - header={InputHeader} - /> - <CodeEditor - value={result} - height={settingsServerlessFunctionCodeEditorOutputParams.height} - language={settingsServerlessFunctionCodeEditorOutputParams.language} - options={{ readOnly: true, domReadOnly: true }} - header={OutputHeader} - /> + <div> + <CoreEditorHeader + title={'Input'} + rightNodes={[ + <Button + title="Run Function" + variant="primary" + accent="blue" + size="small" + Icon={IconPlayerPlay} + onClick={handleExecute} + />, + ]} + /> + <CodeEditor + files={[ + { + content: settingsServerlessFunctionInput, + language: 'json', + path: 'input.json', + }, + ]} + currentFilePath={'input.json'} + height={200} + onChange={setSettingsServerlessFunctionInput} + /> + </div> + <div> + <CoreEditorHeader + leftNodes={[<SettingsServerlessFunctionsOutputMetadataInfo />]} + rightNodes={[<LightCopyIconButton copyText={result} />]} + /> + <CodeEditor + files={[ + { + content: result, + language: + settingsServerlessFunctionCodeEditorOutputParams.language, + path: 'result.any', + }, + ]} + currentFilePath={'result.any'} + height={settingsServerlessFunctionCodeEditorOutputParams.height} + options={{ readOnly: true, domReadOnly: true }} + /> + </div> </StyledInputsContainer> </Section> ); diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId.ts b/packages/twenty-front/src/modules/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId.ts new file mode 100644 index 000000000000..0c8c15a91dff --- /dev/null +++ b/packages/twenty-front/src/modules/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId.ts @@ -0,0 +1,2 @@ +export const SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID = + 'settings-serverless-function-editor-tab-list'; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment.ts b/packages/twenty-front/src/modules/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment.ts index 2fd1e2506614..bbcad2f1c31f 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment.ts +++ b/packages/twenty-front/src/modules/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment.ts @@ -5,7 +5,6 @@ export const SERVERLESS_FUNCTION_FRAGMENT = gql` id name description - sourceCodeHash runtime syncStatus latestVersion 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 97780d3fca65..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 = { @@ -9,7 +9,7 @@ export type ServerlessFunctionNewFormValues = { }; export type ServerlessFunctionFormValues = ServerlessFunctionNewFormValues & { - code: string; + code: { [filePath: string]: string } | undefined; }; type SetServerlessFunctionFormValues = Dispatch< @@ -26,7 +26,7 @@ export const useServerlessFunctionUpdateFormState = ( const [formValues, setFormValues] = useState<ServerlessFunctionFormValues>({ name: '', description: '', - code: '', + code: undefined, }); const { serverlessFunction } = @@ -37,7 +37,7 @@ export const useServerlessFunctionUpdateFormState = ( version: 'draft', onCompleted: (data: FindOneServerlessFunctionSourceCodeQuery) => { const newState = { - code: data?.getServerlessFunctionSourceCode || '', + code: data?.getServerlessFunctionSourceCode || undefined, name: serverlessFunction?.name || '', description: serverlessFunction?.description || '', }; diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx index f7fdf0218239..2bad3fa95ffc 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainerEffect.tsx @@ -2,7 +2,6 @@ import { useEffect } from 'react'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; -import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions'; import { SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions'; @@ -23,14 +22,10 @@ export const SignInBackgroundMockContainerEffect = ({ recordTableId, viewId, }: SignInBackgroundMockContainerEffectProps) => { - const { - setAvailableTableColumns, - setOnEntityCountChange, - setTableColumns, - resetTableRowSelection, - } = useRecordTable({ - recordTableId, - }); + const { setAvailableTableColumns, setOnEntityCountChange, setTableColumns } = + useRecordTable({ + recordTableId, + }); const { objectNameSingular } = useObjectNameSingularFromPlural({ objectNamePlural, @@ -75,17 +70,6 @@ export const SignInBackgroundMockContainerEffect = ({ setTableColumns, ]); - const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({ - objectMetadataItem, - selectedRecordIds: [], - callback: resetTableRowSelection, - }); - - useEffect(() => { - setActionBarEntries?.(); - setContextMenuEntries?.(); - }, [setActionBarEntries, setContextMenuEntries]); - useEffect(() => { setOnEntityCountChange( () => (entityCount: number) => setRecordCountInCurrentView(entityCount), diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockPage.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockPage.tsx index 6ce3a80a5d4a..2b17a655549d 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockPage.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockPage.tsx @@ -2,8 +2,6 @@ import styled from '@emotion/styled'; import { IconBuildingSkyscraper } from 'twenty-ui'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; -import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar'; -import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu'; import { SignInBackgroundMockContainer } from '@/sign-in-background-mock/components/SignInBackgroundMockContainer'; import { PageAddButton } from '@/ui/layout/page/PageAddButton'; import { PageBody } from '@/ui/layout/page/PageBody'; @@ -29,8 +27,6 @@ export const SignInBackgroundMockPage = () => { <StyledTableContainer> <SignInBackgroundMockContainer /> </StyledTableContainer> - <RecordTableActionBar recordTableId="mock" /> - <RecordTableContextMenu recordTableId="mock" /> </RecordFieldValueSelectorContextProvider> </PageBody> </PageContainer> diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions.ts b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions.ts index 7a3d5912ff22..e1ebdec28b73 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions.ts +++ b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions.ts @@ -1,4 +1,3 @@ -import { COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; @@ -45,7 +44,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = ( }, { position: 2, - fieldMetadataId: COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID, + fieldMetadataId: 'REPLACE_ME', label: 'Name', size: 100, type: FieldMetadataType.Text, diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions.ts b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions.ts index ab67214dd3db..92040bbb8aae 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions.ts +++ b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockFilterDefinitions.ts @@ -1,4 +1,3 @@ -import { COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; export const SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS = [ @@ -15,7 +14,7 @@ export const SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS = [ type: 'NUMBER', }, { - fieldMetadataId: COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID, + fieldMetadataId: 'REPLACE_ME', label: 'Name', iconName: 'IconBuildingSkyscraper', type: 'TEXT', @@ -44,13 +43,13 @@ export const SIGN_IN_BACKGROUND_MOCK_FILTER_DEFINITIONS = [ fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1', label: 'Linkedin', iconName: 'IconBrandLinkedin', - type: 'LINK', + type: 'LINKS', }, { fieldMetadataId: '20202020-46e3-479a-b8f4-77137c74daa6', label: 'X', iconName: 'IconBrandX', - type: 'LINK', + type: 'LINKS', }, { fieldMetadataId: '20202020-4a5a-466f-92d9-c3870d9502a9', diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions.ts b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions.ts index 24adc014bb37..a65f08439312 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions.ts +++ b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockSortDefinitions.ts @@ -1,4 +1,3 @@ -import { COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition'; export const SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS = [ @@ -13,7 +12,7 @@ export const SIGN_IN_BACKGROUND_MOCK_SORT_DEFINITIONS = [ iconName: 'IconUsers', }, { - fieldMetadataId: COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID, + fieldMetadataId: 'REPLACE_ME', label: 'Name', iconName: 'IconBuildingSkyscraper', }, diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockViewFields.ts b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockViewFields.ts index dabbe2434355..29599b850494 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockViewFields.ts +++ b/packages/twenty-front/src/modules/sign-in-background-mock/constants/SignInBackgroundMockViewFields.ts @@ -1,4 +1,3 @@ -import { COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { ViewField } from '@/views/types/ViewField'; export const SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS = [ @@ -60,7 +59,7 @@ export const SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS = [ { __typename: 'ViewField', id: 'cafacdc8-cbfc-4545-8242-94787f144ace', - fieldMetadataId: COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID, + fieldMetadataId: 'REPLACE_ME', size: 180, createdAt: '2023-11-23T15:38:03.706Z', viewId: '20202020-2441-4424-8163-4002c523d415', diff --git a/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImport.tsx b/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImport.tsx index 50bc89f4358a..032f632a97f1 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImport.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImport.tsx @@ -24,13 +24,16 @@ export const defaultSpreadsheetImportProps: Partial< export const SpreadsheetImport = <T extends string>( props: SpreadsheetImportProps<T>, ) => { + const mergedProps = { + ...defaultSpreadsheetImportProps, + ...props, + } as SpreadsheetImportProps<T>; + return ( - <ReactSpreadsheetImportContextProvider values={props}> - <ModalWrapper isOpen={props.isOpen} onClose={props.onClose}> + <ReactSpreadsheetImportContextProvider values={mergedProps}> + <ModalWrapper isOpen={mergedProps.isOpen} onClose={mergedProps.onClose}> <SpreadsheetImportStepperContainer /> </ModalWrapper> </ReactSpreadsheetImportContextProvider> ); }; - -SpreadsheetImport.defaultProps = defaultSpreadsheetImportProps; 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} > - <Skeleton width={84} height={24} /> + <Skeleton width={84} height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} /> </SkeletonTheme> ); }; diff --git a/packages/twenty-front/src/modules/types/SettingsPath.ts b/packages/twenty-front/src/modules/types/SettingsPath.ts index 92f152619c2a..96efe89cdb7a 100644 --- a/packages/twenty-front/src/modules/types/SettingsPath.ts +++ b/packages/twenty-front/src/modules/types/SettingsPath.ts @@ -12,8 +12,8 @@ export enum SettingsPath { ObjectOverview = 'objects/overview', ObjectDetail = 'objects/:objectSlug', ObjectEdit = 'objects/:objectSlug/edit', - ObjectNewFieldStep1 = 'objects/:objectSlug/new-field/step-1', - ObjectNewFieldStep2 = 'objects/:objectSlug/new-field/step-2', + ObjectNewFieldSelect = 'objects/:objectSlug/new-field/select', + ObjectNewFieldConfigure = 'objects/:objectSlug/new-field/configure', ObjectFieldEdit = 'objects/:objectSlug/:fieldSlug', NewObject = 'objects/new', NewServerlessFunction = 'functions/new', 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<FieldActorValue> & { @@ -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 ( <AvatarChip diff --git a/packages/twenty-front/src/modules/ui/field/display/components/CurrencyDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/CurrencyDisplay.tsx index 55f641ca4d7a..f2af5ae75803 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/CurrencyDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/CurrencyDisplay.tsx @@ -5,6 +5,7 @@ import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMeta import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes'; import { formatAmount } from '~/utils/format/formatAmount'; import { isDefined } from '~/utils/isDefined'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type CurrencyDisplayProps = { currencyValue: FieldCurrencyValue | null | undefined; @@ -29,7 +30,9 @@ export const CurrencyDisplay = ({ currencyValue }: CurrencyDisplayProps) => { ? SETTINGS_FIELD_CURRENCY_CODES[currencyValue?.currencyCode]?.Icon : null; - const amountToDisplay = (currencyValue?.amountMicros ?? 0) / 1000000; + const amountToDisplay = isUndefinedOrNull(currencyValue?.amountMicros) + ? null + : currencyValue?.amountMicros / 1000000; if (!shouldDisplayCurrency) { return <StyledEllipsisDisplay>{0}</StyledEllipsisDisplay>; @@ -46,7 +49,7 @@ export const CurrencyDisplay = ({ currencyValue }: CurrencyDisplayProps) => { />{' '} </> )} - {amountToDisplay !== 0 ? formatAmount(amountToDisplay) : ''} + {amountToDisplay !== null ? formatAmount(amountToDisplay) : ''} </StyledEllipsisDisplay> ); }; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx index 982e7a9af07e..767671d62482 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx @@ -1,17 +1,24 @@ import { formatDateISOStringToDate } from '@/localization/utils/formatDateISOStringToDate'; +import { formatDateISOStringToRelativeDate } from '@/localization/utils/formatDateISOStringToRelativeDate'; import { UserContext } from '@/users/contexts/UserContext'; import { useContext } from 'react'; import { EllipsisDisplay } from './EllipsisDisplay'; type DateDisplayProps = { value: string | null | undefined; + displayAsRelativeDate?: boolean; }; -export const DateDisplay = ({ value }: DateDisplayProps) => { +export const DateDisplay = ({ + value, + displayAsRelativeDate, +}: DateDisplayProps) => { const { dateFormat, timeZone } = useContext(UserContext); const formattedDate = value - ? formatDateISOStringToDate(value, timeZone, dateFormat) + ? displayAsRelativeDate + ? formatDateISOStringToRelativeDate(value, true) + : formatDateISOStringToDate(value, timeZone, dateFormat) : ''; return <EllipsisDisplay>{formattedDate}</EllipsisDisplay>; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx index f90e4a0c560c..7f2432639a11 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx @@ -1,17 +1,24 @@ import { formatDateISOStringToDateTime } from '@/localization/utils/formatDateISOStringToDateTime'; +import { formatDateISOStringToRelativeDate } from '@/localization/utils/formatDateISOStringToRelativeDate'; import { UserContext } from '@/users/contexts/UserContext'; import { useContext } from 'react'; import { EllipsisDisplay } from './EllipsisDisplay'; type DateTimeDisplayProps = { value: string | null | undefined; + displayAsRelativeDate?: boolean; }; -export const DateTimeDisplay = ({ value }: DateTimeDisplayProps) => { +export const DateTimeDisplay = ({ + value, + displayAsRelativeDate, +}: DateTimeDisplayProps) => { const { dateFormat, timeFormat, timeZone } = useContext(UserContext); const formattedDate = value - ? formatDateISOStringToDateTime(value, timeZone, dateFormat, timeFormat) + ? displayAsRelativeDate + ? formatDateISOStringToRelativeDate(value) + : formatDateISOStringToDateTime(value, timeZone, dateFormat, timeFormat) : ''; return <EllipsisDisplay>{formattedDate}</EllipsisDisplay>; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx index d5c336005ff7..1e909c6e613c 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx @@ -1,7 +1,7 @@ import { styled } from '@linaria/react'; const StyledEllipsisDisplay = styled.div<{ maxWidth?: number }>` - max-width: ${({ maxWidth }) => maxWidth ?? '100%'}; + max-width: ${({ maxWidth }) => (maxWidth ? maxWidth + 'px' : '100%')}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx index 9e2e2b48ef95..963887f11e0d 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/LinkDisplay.tsx @@ -1,6 +1,5 @@ import { isNonEmptyString } from '@sniptt/guards'; -import { FieldLinkValue } from '@/object-record/record-field/types/FieldMetadata'; import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink'; import { LinkType, @@ -8,7 +7,7 @@ import { } from '@/ui/navigation/link/components/SocialLink'; type LinkDisplayProps = { - value?: FieldLinkValue; + value?: { url: string; label?: string }; }; export const LinkDisplay = ({ value }: LinkDisplayProps) => { diff --git a/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx index 1834e502a051..cef5ff6e0b81 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/NumberDisplay.tsx @@ -4,8 +4,11 @@ import { EllipsisDisplay } from './EllipsisDisplay'; type NumberDisplayProps = { value: string | number | null | undefined; + decimals?: number; }; -export const NumberDisplay = ({ value }: NumberDisplayProps) => ( - <EllipsisDisplay>{value && formatNumber(Number(value))}</EllipsisDisplay> +export const NumberDisplay = ({ value, decimals }: NumberDisplayProps) => ( + <EllipsisDisplay> + {value && formatNumber(Number(value), decimals)} + </EllipsisDisplay> ); diff --git a/packages/twenty-front/src/modules/ui/field/display/components/PhonesDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/PhonesDisplay.tsx index 17e9d27f4d29..deee867fc16e 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/PhonesDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/PhonesDisplay.tsx @@ -56,16 +56,27 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => { ], ); + const parsePhoneNumberOrReturnInvalidValue = (number: string) => { + try { + return { parsedPhone: parsePhoneNumber(number) }; + } catch (e) { + return { invalidPhone: number }; + } + }; + return isFocused ? ( <ExpandableList isChipCountDisplayed> {phones.map(({ number, countryCode }, index) => { - const parsedPhone = parsePhoneNumber(countryCode + number); - const URI = parsedPhone.getURI(); + const { parsedPhone, invalidPhone } = + parsePhoneNumberOrReturnInvalidValue(countryCode + number); + const URI = parsedPhone?.getURI(); return ( <RoundedLink key={index} - href={URI} - label={parsedPhone.formatInternational()} + href={URI || ''} + label={ + parsedPhone ? parsedPhone.formatInternational() : invalidPhone + } /> ); })} @@ -73,13 +84,16 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => { ) : ( <StyledContainer> {phones.map(({ number, countryCode }, index) => { - const parsedPhone = parsePhoneNumber(countryCode + number); - const URI = parsedPhone.getURI(); + const { parsedPhone, invalidPhone } = + parsePhoneNumberOrReturnInvalidValue(countryCode + number); + const URI = parsedPhone?.getURI(); return ( <RoundedLink key={index} - href={URI} - label={parsedPhone.formatInternational()} + href={URI || ''} + label={ + parsedPhone ? parsedPhone.formatInternational() : invalidPhone + } /> ); })} diff --git a/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx index ced18f9c5d81..052f47954202 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx @@ -1,7 +1,7 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; -import { IMaskInput, IMaskInputProps } from 'react-imask'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { IMaskInput, IMaskInputProps } from 'react-imask'; import { IconComponent, TEXT_INPUT_STYLE } from 'twenty-ui'; import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents'; @@ -138,13 +138,13 @@ export const CurrencyInput = ({ mask={Number} thousandsSeparator={','} radix="." - unmask="typed" onAccept={(value: string) => handleChange(value)} inputRef={wrapperRef} autoComplete="off" placeholder={placeholder} autoFocus={autoFocus} value={value} + unmask /> </StyledContainer> ); diff --git a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx index 1e74e447b5ac..c61ce4491ebe 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/DateInput.tsx @@ -1,5 +1,5 @@ -import { useRef, useState } from 'react'; import styled from '@emotion/styled'; +import { useRef, useState } from 'react'; import { Nullable } from 'twenty-ui'; import { @@ -16,9 +16,6 @@ const StyledCalendarContainer = styled.div` border: 1px solid ${({ theme }) => theme.border.color.light}; border-radius: ${({ theme }) => theme.border.radius.md}; box-shadow: ${({ theme }) => theme.boxShadow.strong}; - top: 0; - - position: absolute; `; export type DateInputProps = { diff --git a/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx index c2da43576095..f9778f17ba05 100644 --- a/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx +++ b/packages/twenty-front/src/modules/ui/field/input/components/TextAreaInput.tsx @@ -36,10 +36,10 @@ const StyledTextArea = styled(TextareaAutosize)` `; const StyledTextAreaContainer = styled.div` - border: ${({ theme }) => `1px solid ${theme.border.color.light}`}; + border: ${({ theme }) => `1px solid ${theme.border.color.medium}`}; position: relative; width: 100%; - padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(0)}; border-radius: ${({ theme }) => theme.border.radius.sm}; background: ${({ theme }) => theme.background.primary}; `; diff --git a/packages/twenty-front/src/modules/ui/input/button/components/IconButton.tsx b/packages/twenty-front/src/modules/ui/input/button/components/IconButton.tsx index e5e83551cdcc..dae5a8f1bc52 100644 --- a/packages/twenty-front/src/modules/ui/input/button/components/IconButton.tsx +++ b/packages/twenty-front/src/modules/ui/input/button/components/IconButton.tsx @@ -33,11 +33,9 @@ const StyledButton = styled.button< case 'default': return css` background: ${theme.background.secondary}; - border-color: ${!disabled - ? focus - ? theme.color.blue - : theme.background.transparent.light - : 'transparent'}; + border-color: ${focus + ? theme.color.blue + : theme.background.transparent.light}; border-width: ${!disabled && focus ? '1px 1px !important' : 0}; box-shadow: ${!disabled && focus ? `0 0 0 3px ${theme.accent.tertiary}` diff --git a/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx b/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx index 3c24d62cdef8..1240a6d6025e 100644 --- a/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx +++ b/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx @@ -1,22 +1,13 @@ import Editor, { Monaco, EditorProps } from '@monaco-editor/react'; +import dotenv from 'dotenv'; import { AutoTypings } from 'monaco-editor-auto-typings'; import { editor, MarkerSeverity } from 'monaco-editor'; import { codeEditorTheme } from '@/ui/input/code-editor/theme/CodeEditorTheme'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useEffect } from 'react'; import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages'; import { isDefined } from '~/utils/isDefined'; -export const DEFAULT_CODE = `export const handler = async ( - event: object, - context: object -): Promise<object> => { - // Your code here - return {}; -} -`; - const StyledEditor = styled(Editor)` border: 1px solid ${({ theme }) => theme.border.color.medium}; border-top: none; @@ -24,25 +15,34 @@ const StyledEditor = styled(Editor)` ${({ theme }) => theme.border.radius.sm}; `; +export type File = { + language: string; + content: string; + path: string; +}; + type CodeEditorProps = Omit<EditorProps, 'onChange'> & { - header: React.ReactNode; + currentFilePath: string; + files: File[]; onChange?: (value: string) => void; setIsCodeValid?: (isCodeValid: boolean) => void; }; export const CodeEditor = ({ - value = DEFAULT_CODE, + currentFilePath, + files, onChange, setIsCodeValid, - language = 'typescript', height = 450, options = undefined, - header, }: CodeEditorProps) => { const theme = useTheme(); const { availablePackages } = useGetAvailablePackages(); + const currentFile = files.find((file) => file.path === currentFilePath); + const environmentVariablesFile = files.find((file) => file.path === '.env'); + const handleEditorDidMount = async ( editor: editor.IStandaloneCodeEditor, monaco: Monaco, @@ -50,7 +50,57 @@ export const CodeEditor = ({ monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme)); monaco.editor.setTheme('codeEditorTheme'); - if (language === 'typescript') { + if (files.length > 1) { + files.forEach((file) => { + const model = monaco.editor.getModel(monaco.Uri.file(file.path)); + if (!isDefined(model)) { + monaco.editor.createModel( + file.content, + file.language, + monaco.Uri.file(file.path), + ); + } + }); + + monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ + ...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(), + moduleResolution: + monaco.languages.typescript.ModuleResolutionKind.NodeJs, + baseUrl: 'file:///src', + paths: { + 'src/*': ['file:///src/*'], + }, + allowSyntheticDefaultImports: true, + esModuleInterop: true, + noEmit: true, + target: monaco.languages.typescript.ScriptTarget.ESNext, + }); + + if (isDefined(environmentVariablesFile)) { + const environmentVariables = dotenv.parse( + environmentVariablesFile.content, + ); + + const environmentDefinition = ` + declare namespace NodeJS { + interface ProcessEnv { + ${Object.keys(environmentVariables) + .map((key) => `${key}: string;`) + .join('\n')} + } + } + + declare const process: { + env: NodeJS.ProcessEnv; + }; + `; + + monaco.languages.typescript.typescriptDefaults.addExtraLib( + environmentDefinition, + 'ts:process-env.d.ts', + ); + } + await AutoTypings.create(editor, { monaco, preloadPackages: true, @@ -71,43 +121,28 @@ export const CodeEditor = ({ setIsCodeValid?.(true); }; - useEffect(() => { - const style = document.createElement('style'); - style.innerHTML = ` - .monaco-editor .margin .line-numbers { - font-weight: bold; - } - `; - document.head.appendChild(style); - return () => { - document.head.removeChild(style); - }; - }, []); - return ( + isDefined(currentFile) && isDefined(availablePackages) && ( - <> - {header} - <StyledEditor - height={height} - language={language} - value={value} - onMount={handleEditorDidMount} - onChange={(value?: string) => value && onChange?.(value)} - onValidate={handleEditorValidation} - options={{ - ...options, - overviewRulerLanes: 0, - scrollbar: { - vertical: 'hidden', - horizontal: 'hidden', - }, - minimap: { - enabled: false, - }, - }} - /> - </> + <StyledEditor + height={height} + value={currentFile.content} + language={currentFile.language} + onMount={handleEditorDidMount} + onChange={(value?: string) => value && onChange?.(value)} + onValidate={handleEditorValidation} + options={{ + ...options, + overviewRulerLanes: 0, + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden', + }, + minimap: { + enabled: false, + }, + }} + /> ) ); }; diff --git a/packages/twenty-front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx b/packages/twenty-front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx index af7945cf7330..4835fab24755 100644 --- a/packages/twenty-front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; import { ColorSchemeCard } from './ColorSchemeCard'; +import { MOBILE_VIEWPORT } from 'twenty-ui'; const StyledContainer = styled.div` display: flex; @@ -11,7 +12,9 @@ const StyledContainer = styled.div` > * + * { margin-left: ${({ theme }) => theme.spacing(4)}; } - overflow: scroll; + @media (max-width: ${MOBILE_VIEWPORT}px) { + overflow: scroll; + } `; const StyledCardContainer = styled.div` diff --git a/packages/twenty-front/src/modules/ui/input/components/Checkbox.tsx b/packages/twenty-front/src/modules/ui/input/components/Checkbox.tsx index 103036b7cfd7..fd3327c63f23 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Checkbox.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Checkbox.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import * as React from 'react'; import { IconCheck, IconMinus } from 'twenty-ui'; -import { v4 } from 'uuid'; export enum CheckboxVariant { Primary = 'primary', @@ -165,7 +164,7 @@ export const Checkbox = ({ setIsInternalChecked(event.target.checked ?? false); }; - const checkboxId = 'checkbox' + v4(); + const checkboxId = React.useId(); return ( <StyledInputContainer diff --git a/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx b/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx index f1f7cff90ab8..b0e8278b89cd 100644 --- a/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/IconPicker.tsx @@ -1,5 +1,5 @@ -import { useMemo, useState } from 'react'; import styled from '@emotion/styled'; +import { useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { IconApps, IconComponent, useIcons } from 'twenty-ui'; @@ -147,6 +147,8 @@ export const IconPicker = ({ [matchingSearchIconKeys], ); + const icon = selectedIconKey ? getIcon(selectedIconKey) : IconApps; + return ( <div className={className}> <Dropdown @@ -160,7 +162,7 @@ export const IconPicker = ({ : `(no icon selected)` }`} disabled={disabled} - Icon={selectedIconKey ? getIcon(selectedIconKey) : IconApps} + Icon={icon} variant={variant} /> } diff --git a/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx b/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx index 48674256c2b5..77779acc58ae 100644 --- a/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx @@ -1,8 +1,7 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import React, { useMemo } from 'react'; -import { IconFileUpload, IconTrash, IconUpload, IconX } from 'twenty-ui'; - +import { IconPhotoUp, IconTrash, IconUpload, IconX } from 'twenty-ui'; import { Button } from '@/ui/input/button/components/Button'; import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI'; import { isDefined } from '~/utils/isDefined'; @@ -15,8 +14,8 @@ const StyledContainer = styled.div` const StyledPicture = styled.button<{ withPicture: boolean }>` align-items: center; background: ${({ theme, disabled }) => - disabled ? theme.background.secondary : theme.background.tertiary}; - border: none; + disabled ? theme.background.secondary : theme.background.transparent.light}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; border-radius: ${({ theme }) => theme.border.radius.sm}; color: ${({ theme }) => theme.font.color.light}; cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; @@ -35,6 +34,10 @@ const StyledPicture = styled.button<{ withPicture: boolean }>` width: 100%; } + &:hover svg { + color: ${({ theme }) => theme.font.color.tertiary}; + } + ${({ theme, withPicture, disabled }) => { if ((withPicture || disabled) === true) { return ''; @@ -42,7 +45,7 @@ const StyledPicture = styled.button<{ withPicture: boolean }>` return ` &:hover { - background: ${theme.background.quaternary}; + background: ${theme.background.transparent.medium}; } `; }}; @@ -122,7 +125,7 @@ export const ImageInput = ({ alt="profile" /> ) : ( - <IconFileUpload size={theme.icon.size.md} /> + <IconPhotoUp size={theme.icon.size.lg} /> )} </StyledPicture> <StyledContent> diff --git a/packages/twenty-front/src/modules/ui/input/components/Radio.tsx b/packages/twenty-front/src/modules/ui/input/components/Radio.tsx index 5530aaa3a332..0d41ca1f8304 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Radio.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Radio.tsx @@ -3,7 +3,6 @@ import { motion } from 'framer-motion'; import * as React from 'react'; import { RGBA } from 'twenty-ui'; -import { v4 } from 'uuid'; import { RadioGroup } from './RadioGroup'; export enum RadioSize { @@ -134,7 +133,7 @@ export const Radio = ({ onCheckedChange?.(event.target.checked); }; - const optionId = v4(); + const optionId = React.useId(); return ( <StyledContainer className={className} labelPosition={labelPosition}> diff --git a/packages/twenty-front/src/modules/ui/input/components/Select.tsx b/packages/twenty-front/src/modules/ui/input/components/Select.tsx index c31a48a198ea..ba29909b509d 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Select.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Select.tsx @@ -1,6 +1,6 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useMemo, useRef, useState } from 'react'; +import React, { MouseEvent, useMemo, useRef, useState } from 'react'; import { IconChevronDown, IconComponent } from 'twenty-ui'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -11,6 +11,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { SelectHotkeyScope } from '../types/SelectHotkeyScope'; +import { isDefined } from '~/utils/isDefined'; export type SelectOption<Value extends string | number | null> = { value: Value; @@ -18,6 +19,12 @@ export type SelectOption<Value extends string | number | null> = { Icon?: IconComponent; }; +type CallToActionButton = { + text: string; + onClick: (event: MouseEvent<HTMLDivElement>) => void; + Icon?: IconComponent; +}; + export type SelectProps<Value extends string | number | null> = { className?: string; disabled?: boolean; @@ -32,6 +39,7 @@ export type SelectProps<Value extends string | number | null> = { options: SelectOption<Value>[]; value?: Value; withSearchInput?: boolean; + callToActionButton?: CallToActionButton; }; const StyledContainer = styled.div<{ fullWidth?: boolean }>` @@ -89,6 +97,7 @@ export const Select = <Value extends string | number | null>({ options, value, withSearchInput, + callToActionButton, }: SelectProps<Value>) => { const selectContainerRef = useRef<HTMLDivElement>(null); @@ -97,8 +106,8 @@ export const Select = <Value extends string | number | null>({ const selectedOption = options.find(({ value: key }) => key === value) || - options[0] || - emptyOption; + emptyOption || + options[0]; const filteredOptions = useMemo( () => searchInputValue @@ -109,7 +118,9 @@ export const Select = <Value extends string | number | null>({ [options, searchInputValue], ); - const isDisabled = disabledFromProps || options.length <= 1; + const isDisabled = + disabledFromProps || + (options.length <= 1 && !isDefined(callToActionButton)); const { closeDropdown } = useDropdown(dropdownId); @@ -177,6 +188,18 @@ export const Select = <Value extends string | number | null>({ ))} </DropdownMenuItemsContainer> )} + {!!callToActionButton && !!filteredOptions.length && ( + <DropdownMenuSeparator /> + )} + {!!callToActionButton && ( + <DropdownMenuItemsContainer hasMaxHeight> + <MenuItem + onClick={callToActionButton.onClick} + LeftIcon={callToActionButton.Icon} + text={callToActionButton.text} + /> + </DropdownMenuItemsContainer> + )} </> } dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }} diff --git a/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx b/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx index 9b50504c757b..b6bb9d545ab2 100644 --- a/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/TextArea.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { FocusEventHandler } from 'react'; +import { FocusEventHandler, useId } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; @@ -10,6 +10,7 @@ import { InputHotkeyScope } from '../types/InputHotkeyScope'; const MAX_ROWS = 5; export type TextAreaProps = { + label?: string; disabled?: boolean; minRows?: number; onChange?: (value: string) => void; @@ -18,6 +19,20 @@ export type TextAreaProps = { className?: string; }; +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const StyledLabel = styled.label` + color: ${({ theme }) => theme.font.color.light}; + display: block; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + const StyledTextArea = styled(TextareaAutosize)` background-color: ${({ theme }) => theme.background.transparent.lighter}; border: 1px solid ${({ theme }) => theme.border.color.medium}; @@ -48,6 +63,7 @@ const StyledTextArea = styled(TextareaAutosize)` `; export const TextArea = ({ + label, disabled, placeholder, minRows = 1, @@ -57,6 +73,8 @@ export const TextArea = ({ }: TextAreaProps) => { const computedMinRows = Math.min(minRows, MAX_ROWS); + const inputId = useId(); + const { goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope, @@ -71,18 +89,23 @@ export const TextArea = ({ }; return ( - <StyledTextArea - placeholder={placeholder} - maxRows={MAX_ROWS} - minRows={computedMinRows} - value={value} - onChange={(event) => - onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value)) - } - onFocus={handleFocus} - onBlur={handleBlur} - disabled={disabled} - className={className} - /> + <StyledContainer> + {label && <StyledLabel htmlFor={inputId}>{label}</StyledLabel>} + + <StyledTextArea + id={inputId} + placeholder={placeholder} + maxRows={MAX_ROWS} + minRows={computedMinRows} + value={value} + onChange={(event) => + onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value)) + } + onFocus={handleFocus} + onBlur={handleBlur} + disabled={disabled} + className={className} + /> + </StyledContainer> ); }; diff --git a/packages/twenty-front/src/modules/ui/input/components/TextInput.tsx b/packages/twenty-front/src/modules/ui/input/components/TextInput.tsx index fcd311e5b013..8e31f598822d 100644 --- a/packages/twenty-front/src/modules/ui/input/components/TextInput.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/TextInput.tsx @@ -14,7 +14,8 @@ export type TextInputProps = TextInputV2ComponentProps & { disableHotkeys?: boolean; onInputEnter?: () => void; dataTestId?: string; - focused?: boolean; + autoFocusOnMount?: boolean; + autoSelectOnMount?: boolean; }; export const TextInput = ({ @@ -22,7 +23,8 @@ export const TextInput = ({ onBlur, onInputEnter, disableHotkeys = false, - focused, + autoFocusOnMount, + autoSelectOnMount, dataTestId, ...props }: TextInputProps) => { @@ -31,11 +33,17 @@ export const TextInput = ({ const [isFocused, setIsFocused] = useState(false); useEffect(() => { - if (focused === true) { + if (autoFocusOnMount === true) { inputRef.current?.focus(); setIsFocused(true); } - }, [focused]); + }, [autoFocusOnMount]); + + useEffect(() => { + if (autoSelectOnMount === true) { + inputRef.current?.select(); + } + }, [autoSelectOnMount]); const { goBackToPreviousHotkeyScope, @@ -89,7 +97,6 @@ export const TextInput = ({ onInputEnter?.(); if (isDefined(inputRef) && 'current' in inputRef) { - inputRef.current?.blur(); setIsFocused(false); } }, diff --git a/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx b/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx index cf4b06f6aba9..1f64b2adc55b 100644 --- a/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx @@ -6,6 +6,7 @@ import { ForwardedRef, InputHTMLAttributes, forwardRef, + useId, useRef, useState, } from 'react'; @@ -21,7 +22,7 @@ const StyledContainer = styled.div< width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')}; `; -const StyledLabel = styled.span` +const StyledLabel = styled.label` color: ${({ theme }) => theme.font.color.light}; font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; @@ -169,9 +170,15 @@ const TextInputV2Component = ( setPasswordVisible(!passwordVisible); }; + const inputId = useId(); + return ( <StyledContainer className={className} fullWidth={fullWidth ?? false}> - {label && <StyledLabel>{label + (required ? '*' : '')}</StyledLabel>} + {label && ( + <StyledLabel htmlFor={inputId}> + {label + (required ? '*' : '')} + </StyledLabel> + )} <StyledInputContainer> {!!LeftIcon && ( <StyledLeftIconContainer> @@ -181,6 +188,7 @@ const TextInputV2Component = ( </StyledLeftIconContainer> )} <StyledInput + id={inputId} data-testid={dataTestId} autoComplete={autoComplete || 'off'} ref={combinedRef} diff --git a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx index f723e93e1c5a..39bafac99582 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx @@ -16,7 +16,7 @@ type ContainerProps = { const StyledContainer = styled.div<ContainerProps>` align-items: center; background-color: ${({ theme, isOn, color }) => - isOn ? (color ?? theme.color.blue) : theme.background.quaternary}; + isOn ? (color ?? theme.color.blue) : theme.background.transparent.medium}; border-radius: 10px; cursor: pointer; display: flex; @@ -69,11 +69,14 @@ export const Toggle = ({ }; useEffect(() => { - if (value !== isOn) { - setIsOn(value ?? false); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]); + setIsOn((isOn) => { + if (value !== isOn) { + return value ?? false; + } + + return isOn; + }); + }, [value, setIsOn]); return ( <StyledContainer diff --git a/packages/twenty-front/src/modules/ui/input/components/__stories__/Select.stories.tsx b/packages/twenty-front/src/modules/ui/input/components/__stories__/Select.stories.tsx index d5ef7dc21c36..f24c7a1c9414 100644 --- a/packages/twenty-front/src/modules/ui/input/components/__stories__/Select.stories.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/__stories__/Select.stories.tsx @@ -4,6 +4,7 @@ import { userEvent, within } from '@storybook/test'; import { ComponentDecorator } from 'twenty-ui'; import { Select, SelectProps } from '../Select'; +import { IconPlus } from 'packages/twenty-ui'; type RenderProps = SelectProps<string | number | null>; @@ -56,3 +57,13 @@ export const Disabled: Story = { export const WithSearch: Story = { args: { withSearchInput: true }, }; + +export const CallToActionButton: Story = { + args: { + callToActionButton: { + onClick: () => {}, + Icon: IconPlus, + text: 'Add action', + }, + }, +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/__stories__/TextArea.stories.tsx b/packages/twenty-front/src/modules/ui/input/components/__stories__/TextArea.stories.tsx index 425ba4f4d835..6583f9cbec24 100644 --- a/packages/twenty-front/src/modules/ui/input/components/__stories__/TextArea.stories.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/__stories__/TextArea.stories.tsx @@ -1,7 +1,9 @@ -import { useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; +import { useState } from 'react'; import { ComponentDecorator } from 'twenty-ui'; +import { expect } from '@storybook/jest'; +import { userEvent, within } from '@storybook/test'; import { TextArea, TextAreaProps } from '../TextArea'; type RenderProps = TextAreaProps; @@ -37,3 +39,20 @@ export const Filled: Story = { export const Disabled: Story = { args: { disabled: true, value: 'Lorem Ipsum' }, }; + +export const WithLabel: Story = { + args: { label: 'My Textarea' }, + play: async () => { + const canvas = within(document.body); + + const label = await canvas.findByText('My Textarea'); + + expect(label).toBeVisible(); + + await userEvent.click(label); + + const input = await canvas.findByRole('textbox'); + + expect(input).toHaveFocus(); + }, +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx new file mode 100644 index 000000000000..1efc985d34f6 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/AbsoluteDatePickerHeader.tsx @@ -0,0 +1,108 @@ +import styled from '@emotion/styled'; +import { DateTime } from 'luxon'; +import { IconChevronLeft, IconChevronRight } from 'twenty-ui'; + +import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { Select } from '@/ui/input/components/Select'; +import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput'; + +import { getMonthSelectOptions } from '@/ui/input/components/internal/date/utils/getMonthSelectOptions'; +import { + MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID, + MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID, +} from './InternalDatePicker'; + +const StyledCustomDatePickerHeader = styled.div` + align-items: center; + display: flex; + justify-content: flex-end; + padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; + padding-top: ${({ theme }) => theme.spacing(2)}; + + gap: ${({ theme }) => theme.spacing(1)}; +`; + +const years = Array.from( + { length: 200 }, + (_, i) => new Date().getFullYear() + 5 - i, +).map((year) => ({ label: year.toString(), value: year })); + +type AbsoluteDatePickerHeaderProps = { + date: Date; + onChange?: (date: Date | null) => void; + onChangeMonth: (month: number) => void; + onChangeYear: (year: number) => void; + onAddMonth: () => void; + onSubtractMonth: () => void; + prevMonthButtonDisabled: boolean; + nextMonthButtonDisabled: boolean; + isDateTimeInput?: boolean; + timeZone: string; +}; + +export const AbsoluteDatePickerHeader = ({ + date, + onChange, + onChangeMonth, + onChangeYear, + onAddMonth, + onSubtractMonth, + prevMonthButtonDisabled, + nextMonthButtonDisabled, + isDateTimeInput, + timeZone, +}: AbsoluteDatePickerHeaderProps) => { + const endOfDayDateTimeInLocalTimezone = DateTime.now().set({ + day: date.getDate(), + month: date.getMonth() + 1, + year: date.getFullYear(), + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + + const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate(); + + return ( + <> + <DateTimeInput + date={date} + isDateTimeInput={isDateTimeInput} + onChange={onChange} + userTimezone={timeZone} + /> + <StyledCustomDatePickerHeader> + <Select + dropdownId={MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID} + options={getMonthSelectOptions()} + disableBlur + onChange={onChangeMonth} + value={endOfDayInLocalTimezone.getMonth()} + fullWidth + /> + <Select + dropdownId={MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID} + onChange={onChangeYear} + value={endOfDayInLocalTimezone.getFullYear()} + options={years} + disableBlur + fullWidth + /> + <LightIconButton + Icon={IconChevronLeft} + onClick={onSubtractMonth} + size="medium" + disabled={prevMonthButtonDisabled} + /> + <LightIconButton + Icon={IconChevronRight} + onClick={onAddMonth} + size="medium" + disabled={nextMonthButtonDisabled} + /> + </StyledCustomDatePickerHeader> + </> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index a3004373d059..e7a330c81ff3 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -2,52 +2,32 @@ import styled from '@emotion/styled'; import { DateTime } from 'luxon'; import ReactDatePicker from 'react-datepicker'; import { Key } from 'ts-key-enum'; -import { - IconCalendarX, - IconChevronLeft, - IconChevronRight, - OVERLAY_BACKGROUND, -} from 'twenty-ui'; +import { IconCalendarX, OVERLAY_BACKGROUND } from 'twenty-ui'; -import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { DateTimeInput } from '@/ui/input/components/internal/date/components/DateTimeInput'; -import { Select } from '@/ui/input/components/Select'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItemLeftContent } from '@/ui/navigation/menu-item/internals/components/MenuItemLeftContent'; import { StyledHoverableMenuItemBase } from '@/ui/navigation/menu-item/internals/components/StyledMenuItemBase'; import { isDefined } from '~/utils/isDefined'; +import { AbsoluteDatePickerHeader } from '@/ui/input/components/internal/date/components/AbsoluteDatePickerHeader'; +import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader'; +import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates'; import { UserContext } from '@/users/contexts/UserContext'; +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; import { useContext } from 'react'; import 'react-datepicker/dist/react-datepicker.css'; -const months = [ - { label: 'January', value: 0 }, - { label: 'February', value: 1 }, - { label: 'March', value: 2 }, - { label: 'April', value: 3 }, - { label: 'May', value: 4 }, - { label: 'June', value: 5 }, - { label: 'July', value: 6 }, - { label: 'August', value: 7 }, - { label: 'September', value: 8 }, - { label: 'October', value: 9 }, - { label: 'November', value: 10 }, - { label: 'December', value: 11 }, -]; - -const years = Array.from( - { length: 200 }, - (_, i) => new Date().getFullYear() + 5 - i, -).map((year) => ({ label: year.toString(), value: year })); - export const MONTH_AND_YEAR_DROPDOWN_ID = 'date-picker-month-and-year-dropdown'; export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID = 'date-picker-month-and-year-dropdown-month-select'; export const MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID = 'date-picker-month-and-year-dropdown-year-select'; -const StyledContainer = styled.div` +const StyledContainer = styled.div<{ calendarDisabled?: boolean }>` & .react-datepicker { border-color: ${({ theme }) => theme.border.color.light}; background: transparent; @@ -207,6 +187,10 @@ const StyledContainer = styled.div` & .react-datepicker__month { margin-top: 0; + + pointer-events: ${({ calendarDisabled }) => + calendarDisabled ? 'none' : 'auto'}; + opacity: ${({ calendarDisabled }) => (calendarDisabled ? '0.5' : '1')}; } & .react-datepicker__day { @@ -288,21 +272,27 @@ const StyledButton = styled(MenuItemLeftContent)` justify-content: start; `; -const StyledCustomDatePickerHeader = styled.div` - align-items: center; - display: flex; - justify-content: flex-end; - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; - padding-top: ${({ theme }) => theme.spacing(2)}; - - gap: ${({ theme }) => theme.spacing(1)}; -`; - type InternalDatePickerProps = { + isRelative?: boolean; date: Date | null; + relativeDate?: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + }; + highlightedDateRange?: { + start: Date; + end: Date; + }; onMouseSelect?: (date: Date | null) => void; onChange?: (date: Date | null) => void; + onRelativeDateChange?: ( + relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + } | null, + ) => void; clearable?: boolean; isDateTimeInput?: boolean; onEnter?: (date: Date | null) => void; @@ -321,6 +311,10 @@ export const InternalDatePicker = ({ isDateTimeInput, keyboardEventsDisabled, onClear, + isRelative, + relativeDate, + onRelativeDateChange, + highlightedDateRange, }: InternalDatePickerProps) => { const internalDate = date ?? new Date(); @@ -469,15 +463,20 @@ export const InternalDatePicker = ({ const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime; + const highlightedDates = getHighlightedDates(highlightedDateRange); + + const selectedDates = isRelative ? highlightedDates : [dateToUse]; + return ( - <StyledContainer onKeyDown={handleKeyDown}> + <StyledContainer onKeyDown={handleKeyDown} calendarDisabled={isRelative}> <div className={clearable ? 'clearable ' : ''}> <ReactDatePicker open={true} selected={dateToUse} + selectedDates={selectedDates} openToDate={isDefined(dateToUse) ? dateToUse : undefined} disabledKeyboardNavigation - onChange={handleDateChange} + onChange={handleDateChange as any} customInput={ <DateTimeInput date={internalDate} @@ -489,47 +488,31 @@ export const InternalDatePicker = ({ renderCustomHeader={({ prevMonthButtonDisabled, nextMonthButtonDisabled, - }) => ( - <> - <DateTimeInput + }) => + isRelative ? ( + <RelativeDatePickerHeader + direction={relativeDate?.direction ?? 'PAST'} + amount={relativeDate?.amount} + unit={relativeDate?.unit ?? 'DAY'} + onChange={onRelativeDateChange} + /> + ) : ( + <AbsoluteDatePickerHeader date={internalDate} - isDateTimeInput={isDateTimeInput} onChange={onChange} - userTimezone={timeZone} + onChangeMonth={handleChangeMonth} + onChangeYear={handleChangeYear} + onAddMonth={handleAddMonth} + onSubtractMonth={handleSubtractMonth} + prevMonthButtonDisabled={prevMonthButtonDisabled} + nextMonthButtonDisabled={nextMonthButtonDisabled} + isDateTimeInput={isDateTimeInput} + timeZone={timeZone} /> - <StyledCustomDatePickerHeader> - <Select - dropdownId={MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID} - options={months} - disableBlur - onChange={handleChangeMonth} - value={endOfDayInLocalTimezone.getMonth()} - fullWidth - /> - <Select - dropdownId={MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID} - onChange={handleChangeYear} - value={endOfDayInLocalTimezone.getFullYear()} - options={years} - disableBlur - fullWidth - /> - <LightIconButton - Icon={IconChevronLeft} - onClick={handleSubtractMonth} - size="medium" - disabled={prevMonthButtonDisabled} - /> - <LightIconButton - Icon={IconChevronRight} - onClick={handleAddMonth} - size="medium" - disabled={nextMonthButtonDisabled} - /> - </StyledCustomDatePickerHeader> - </> - )} + ) + } onSelect={handleDateSelect} + selectsMultiple={isRelative} /> </div> {clearable && ( diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx new file mode 100644 index 000000000000..0a9328577dba --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/RelativeDatePickerHeader.tsx @@ -0,0 +1,113 @@ +import { RELATIVE_DATE_DIRECTION_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions'; +import { RELATIVE_DATE_UNITS_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions'; +import { Select } from '@/ui/input/components/Select'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { + VariableDateViewFilterValueDirection, + variableDateViewFilterValuePartsSchema, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; +import styled from '@emotion/styled'; +import { useEffect, useState } from 'react'; + +const StyledContainer = styled.div` + display: flex; + align-items: center; + gap: ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(2)}; + padding-bottom: 0; +`; + +type RelativeDatePickerHeaderProps = { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + onChange?: (value: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; + }) => void; +}; + +export const RelativeDatePickerHeader = ( + props: RelativeDatePickerHeaderProps, +) => { + const [direction, setDirection] = useState(props.direction); + const [amountString, setAmountString] = useState( + props.amount ? props.amount.toString() : '', + ); + const [unit, setUnit] = useState(props.unit); + + useEffect(() => { + setAmountString(props.amount ? props.amount.toString() : ''); + setUnit(props.unit); + setDirection(props.direction); + }, [props.amount, props.unit, props.direction]); + + const textInputValue = direction === 'THIS' ? '' : amountString; + const textInputPlaceholder = direction === 'THIS' ? '-' : 'Number'; + + const isUnitPlural = props.amount && props.amount > 1 && direction !== 'THIS'; + const unitSelectOptions = RELATIVE_DATE_UNITS_SELECT_OPTIONS.map((unit) => ({ + ...unit, + label: `${unit.label}${isUnitPlural ? 's' : ''}`, + })); + + return ( + <StyledContainer> + <Select + disableBlur + dropdownId="direction-select" + value={direction} + onChange={(newDirection) => { + setDirection(newDirection); + if (props.amount === undefined && newDirection !== 'THIS') return; + props.onChange?.({ + direction: newDirection, + amount: props.amount, + unit: unit, + }); + }} + options={RELATIVE_DATE_DIRECTION_SELECT_OPTIONS} + /> + <TextInput + value={textInputValue} + onChange={(text) => { + const amountString = text.replace(/[^0-9]|^0+/g, ''); + const amount = parseInt(amountString); + + setAmountString(amountString); + + const valueParts = { + direction, + amount, + unit, + }; + + if ( + variableDateViewFilterValuePartsSchema.safeParse(valueParts).success + ) { + props.onChange?.(valueParts); + } + }} + placeholder={textInputPlaceholder} + disabled={direction === 'THIS'} + /> + <Select + disableBlur + dropdownId="unit-select" + value={unit} + onChange={(newUnit) => { + setUnit(newUnit); + if (direction !== 'THIS' && props.amount === undefined) return; + props.onChange?.({ + direction, + amount: props.amount, + unit: newUnit, + }); + }} + options={unitSelectOptions} + /> + </StyledContainer> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions.ts new file mode 100644 index 000000000000..d13926719f0f --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions.ts @@ -0,0 +1,13 @@ +import { VariableDateViewFilterValueDirection } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; + +type RelativeDateDirectionOption = { + value: VariableDateViewFilterValueDirection; + label: string; +}; + +export const RELATIVE_DATE_DIRECTION_SELECT_OPTIONS: RelativeDateDirectionOption[] = + [ + { value: 'PAST', label: 'Past' }, + { value: 'THIS', label: 'This' }, + { value: 'NEXT', label: 'Next' }, + ]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions.ts new file mode 100644 index 000000000000..bf65953f63bc --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions.ts @@ -0,0 +1,13 @@ +import { VariableDateViewFilterValueUnit } from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; + +type RelativeDateUnit = { + value: VariableDateViewFilterValueUnit; + label: string; +}; + +export const RELATIVE_DATE_UNITS_SELECT_OPTIONS: RelativeDateUnit[] = [ + { value: 'DAY', label: 'Day' }, + { value: 'WEEK', label: 'Week' }, + { value: 'MONTH', label: 'Month' }, + { value: 'YEAR', label: 'Year' }, +]; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/__tests__/getHighlightedDates.test.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/__tests__/getHighlightedDates.test.ts new file mode 100644 index 000000000000..d47038665337 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/__tests__/getHighlightedDates.test.ts @@ -0,0 +1,48 @@ +import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates'; + +describe('getHighlightedDates', () => { + it('should should return empty if range is undefined', () => { + const dateRange = undefined; + expect(getHighlightedDates(dateRange)).toEqual([]); + }); + + it('should should return empty if range is one day', () => { + const dateRange = { + start: new Date('2024-10-12T00:00:00.000Z'), + end: new Date('2024-10-12T00:00:00.000Z'), + }; + expect(getHighlightedDates(dateRange)).toEqual([ + new Date('2024-10-12T00:00:00.000Z'), + ]); + }); + + it('should should return empty if range is 2 days', () => { + const dateRange = { + start: new Date('2024-10-12T00:00:00.000Z'), + end: new Date('2024-10-13T00:00:00.000Z'), + }; + expect(getHighlightedDates(dateRange)).toEqual([ + new Date('2024-10-12T00:00:00.000Z'), + new Date('2024-10-13T00:00:00.000Z'), + ]); + }); + + it('should should return empty if range is 10 days', () => { + const dateRange = { + start: new Date('2024-10-12T00:00:00.000Z'), + end: new Date('2024-10-21T00:00:00.000Z'), + }; + expect(getHighlightedDates(dateRange)).toEqual([ + new Date('2024-10-12T00:00:00.000Z'), + new Date('2024-10-13T00:00:00.000Z'), + new Date('2024-10-14T00:00:00.000Z'), + new Date('2024-10-15T00:00:00.000Z'), + new Date('2024-10-16T00:00:00.000Z'), + new Date('2024-10-17T00:00:00.000Z'), + new Date('2024-10-18T00:00:00.000Z'), + new Date('2024-10-19T00:00:00.000Z'), + new Date('2024-10-20T00:00:00.000Z'), + new Date('2024-10-21T00:00:00.000Z'), + ]); + }); +}); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts new file mode 100644 index 000000000000..813b36996833 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getHighlightedDates.ts @@ -0,0 +1,24 @@ +import { addDays, addMonths, startOfDay, subMonths } from 'date-fns'; + +export const getHighlightedDates = (highlightedDateRange?: { + start: Date; + end: Date; +}): Date[] => { + if (!highlightedDateRange) return []; + const { start, end } = highlightedDateRange; + + const highlightedDates: Date[] = []; + const currentDate = startOfDay(new Date()); + const minDate = subMonths(currentDate, 2); + const maxDate = addMonths(currentDate, 2); + + let dateToHighlight = start < minDate ? minDate : start; + const lastDate = end > maxDate ? maxDate : end; + + while (dateToHighlight <= lastDate) { + highlightedDates.push(dateToHighlight); + dateToHighlight = addDays(dateToHighlight, 1); + } + + return highlightedDates; +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getMonthSelectOptions.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getMonthSelectOptions.ts new file mode 100644 index 000000000000..3f5e395174ee --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/utils/getMonthSelectOptions.ts @@ -0,0 +1,16 @@ +const getMonthName = (index: number): string => + new Intl.DateTimeFormat('en-US', { month: 'long' }).format( + new Date(0, index, 1), + ); + +const getMonthNames = (monthNames: string[] = []): string[] => { + if (monthNames.length === 12) return monthNames; + + return getMonthNames([...monthNames, getMonthName(monthNames.length)]); +}; + +export const getMonthSelectOptions = (): { label: string; value: number }[] => + getMonthNames().map((month, index) => ({ + label: month, + value: index, + })); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownSelect.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownSelect.tsx index b651d38007e6..d5f46a71a2c7 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownSelect.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownSelect.tsx @@ -48,7 +48,7 @@ export const PhoneCountryPickerDropdownSelect = ({ ); return ( - <DropdownMenu width="200px" disableBlur> + <DropdownMenu width="auto" disableBlur> <DropdownMenuSearchInput value={searchFilter} onChange={(event) => setSearchFilter(event.currentTarget.value)} 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} > - <Skeleton height={16} /> + <Skeleton height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} /> </SkeletonTheme> </StyledDropdownMenuSkeletonContainer> ); diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/components/BottomBar.tsx b/packages/twenty-front/src/modules/ui/layout/bottom-bar/components/BottomBar.tsx new file mode 100644 index 000000000000..46ccff887a7a --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/components/BottomBar.tsx @@ -0,0 +1,61 @@ +import styled from '@emotion/styled'; + +import { useBottomBarInternalHotkeyScopeManagement } from '@/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement'; +import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext'; +import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +const StyledContainerActionBar = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.md}; + bottom: 38px; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; + display: flex; + height: 48px; + width: max-content; + left: 50%; + padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; + position: absolute; + top: auto; + + transform: translateX(-50%); + z-index: 1; +`; + +type BottomBarProps = { + bottomBarId: string; + bottomBarHotkeyScopeFromParent: HotkeyScope; + children: React.ReactNode; +}; + +export const BottomBar = ({ + bottomBarId, + bottomBarHotkeyScopeFromParent, + children, +}: BottomBarProps) => { + const isBottomBarOpen = useRecoilComponentValueV2( + isBottomBarOpenedComponentState, + bottomBarId, + ); + + useBottomBarInternalHotkeyScopeManagement({ + bottomBarId, + bottomBarHotkeyScopeFromParent, + }); + + if (!isBottomBarOpen) { + return null; + } + + return ( + <BottomBarInstanceContext.Provider value={{ instanceId: bottomBarId }}> + <StyledContainerActionBar data-select-disable className="bottom-bar"> + {children} + </StyledContainerActionBar> + </BottomBarInstanceContext.Provider> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/components/__stories__/BottomBar.stories.tsx b/packages/twenty-front/src/modules/ui/layout/bottom-bar/components/__stories__/BottomBar.stories.tsx new file mode 100644 index 000000000000..8562f67cdce3 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/components/__stories__/BottomBar.stories.tsx @@ -0,0 +1,65 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { IconPlus } from 'twenty-ui'; + +import { Button } from '@/ui/input/button/components/Button'; +import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar'; +import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; +import styled from '@emotion/styled'; +import { RecoilRoot } from 'recoil'; + +const StyledContainer = styled.div` + display: flex; + gap: 10px; +`; + +const meta: Meta<typeof BottomBar> = { + title: 'UI/Layout/BottomBar/BottomBar', + component: BottomBar, + args: { + bottomBarId: 'test', + bottomBarHotkeyScopeFromParent: { scope: 'test' }, + children: ( + <StyledContainer> + <Button title="Test 1" Icon={IconPlus} /> + <Button title="Test 2" Icon={IconPlus} /> + <Button title="Test 3" Icon={IconPlus} /> + </StyledContainer> + ), + }, + argTypes: { + bottomBarId: { control: false }, + bottomBarHotkeyScopeFromParent: { control: false }, + children: { control: false }, + }, +}; + +export default meta; + +export const Default: StoryObj<typeof BottomBar> = { + decorators: [ + (Story) => ( + <RecoilRoot + initializeState={({ set }) => { + set( + isBottomBarOpenedComponentState.atomFamily({ + instanceId: 'test', + }), + true, + ); + }} + > + <Story /> + </RecoilRoot> + ), + ], +}; + +export const Closed: StoryObj<typeof BottomBar> = { + decorators: [ + (Story) => ( + <RecoilRoot> + <Story /> + </RecoilRoot> + ), + ], +}; diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/hooks/useBottomBar.ts b/packages/twenty-front/src/modules/ui/layout/bottom-bar/hooks/useBottomBar.ts new file mode 100644 index 000000000000..bfae8a470fac --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/hooks/useBottomBar.ts @@ -0,0 +1,87 @@ +import { useRecoilCallback } from 'recoil'; + +import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState'; +import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { isDefined } from '~/utils/isDefined'; + +export const useBottomBar = () => { + const { + setHotkeyScopeAndMemorizePreviousScope, + goBackToPreviousHotkeyScope, + } = usePreviousHotkeyScope(); + + const closeBottomBar = useRecoilCallback( + ({ set }) => + (specificComponentId: string) => { + goBackToPreviousHotkeyScope(); + set( + isBottomBarOpenedComponentState.atomFamily({ + instanceId: specificComponentId, + }), + false, + ); + }, + [goBackToPreviousHotkeyScope], + ); + + const openBottomBar = useRecoilCallback( + ({ set, snapshot }) => + (specificComponentId: string, customHotkeyScope?: HotkeyScope) => { + const bottomBarHotkeyScope = snapshot + .getLoadable( + bottomBarHotkeyComponentState.atomFamily({ + instanceId: specificComponentId, + }), + ) + .getValue(); + + set( + isBottomBarOpenedComponentState.atomFamily({ + instanceId: specificComponentId, + }), + true, + ); + + if (isDefined(customHotkeyScope)) { + setHotkeyScopeAndMemorizePreviousScope( + customHotkeyScope.scope, + customHotkeyScope.customScopes, + ); + } else if (isDefined(bottomBarHotkeyScope)) { + setHotkeyScopeAndMemorizePreviousScope( + bottomBarHotkeyScope.scope, + bottomBarHotkeyScope.customScopes, + ); + } + }, + [setHotkeyScopeAndMemorizePreviousScope], + ); + + const toggleBottomBar = useRecoilCallback( + ({ snapshot }) => + (specificComponentId: string) => { + const isBottomBarOpen = snapshot + .getLoadable( + isBottomBarOpenedComponentState.atomFamily({ + instanceId: specificComponentId, + }), + ) + .getValue(); + + if (isBottomBarOpen) { + closeBottomBar(specificComponentId); + } else { + openBottomBar(specificComponentId); + } + }, + [closeBottomBar, openBottomBar], + ); + + return { + closeBottomBar, + openBottomBar, + toggleBottomBar, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement.ts b/packages/twenty-front/src/modules/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement.ts new file mode 100644 index 000000000000..a35ccfc69ead --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement.ts @@ -0,0 +1,27 @@ +import { useEffect } from 'react'; + +import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; + +export const useBottomBarInternalHotkeyScopeManagement = ({ + bottomBarId, + bottomBarHotkeyScopeFromParent, +}: { + bottomBarId?: string; + bottomBarHotkeyScopeFromParent?: HotkeyScope; +}) => { + const [bottomBarHotkeyScope, setBottomBarHotkeyScope] = + useRecoilComponentStateV2(bottomBarHotkeyComponentState, bottomBarId); + + useEffect(() => { + if (!isDeeplyEqual(bottomBarHotkeyScopeFromParent, bottomBarHotkeyScope)) { + setBottomBarHotkeyScope(bottomBarHotkeyScopeFromParent); + } + }, [ + bottomBarHotkeyScope, + bottomBarHotkeyScopeFromParent, + setBottomBarHotkeyScope, + ]); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState.ts b/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState.ts new file mode 100644 index 000000000000..89144d6c8c30 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState.ts @@ -0,0 +1,11 @@ +import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext'; +import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const bottomBarHotkeyComponentState = createComponentStateV2< + HotkeyScope | null | undefined +>({ + key: 'bottomBarHotkeyComponentState', + defaultValue: null, + componentInstanceContext: BottomBarInstanceContext, +}); diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext.tsx b/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext.tsx new file mode 100644 index 000000000000..e2b29e54cfc3 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext.tsx @@ -0,0 +1,3 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const BottomBarInstanceContext = createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState.ts b/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState.ts new file mode 100644 index 000000000000..071ad8de53cf --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState.ts @@ -0,0 +1,8 @@ +import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const isBottomBarOpenedComponentState = createComponentStateV2<boolean>({ + key: 'isBottomBarOpenedComponentState', + defaultValue: false, + componentInstanceContext: BottomBarInstanceContext, +}); 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..5b985fab332a 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,11 @@ 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)}; + border-color: ${theme.color.blue};`; + }}; } ${({ withRightComponent }) => @@ -44,6 +51,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<HTMLInputElement>; export type DropdownMenuInputProps = HTMLInputProps & { @@ -60,6 +73,8 @@ export type DropdownMenuInputProps = HTMLInputProps & { autoFocus: HTMLInputProps['autoFocus']; placeholder: HTMLInputProps['placeholder']; }) => React.ReactNode; + error?: string | null; + hasError?: boolean; }; export const DropdownMenuInput = forwardRef< @@ -81,6 +96,8 @@ export const DropdownMenuInput = forwardRef< onTab, rightComponent, renderInput, + error = '', + hasError = false, }, ref, ) => { @@ -99,28 +116,32 @@ export const DropdownMenuInput = forwardRef< }); return ( - <StyledInputContainer className={className}> - {renderInput ? ( - renderInput({ - value, - onChange, - autoFocus, - placeholder, - }) - ) : ( - <StyledInput - autoFocus={autoFocus} - value={value} - placeholder={placeholder} - onChange={onChange} - ref={combinedRef} - withRightComponent={!!rightComponent} - /> - )} - {!!rightComponent && ( - <StyledRightContainer>{rightComponent}</StyledRightContainer> - )} - </StyledInputContainer> + <> + <StyledInputContainer className={className}> + {renderInput ? ( + renderInput({ + value, + onChange, + autoFocus, + placeholder, + }) + ) : ( + <StyledInput + hasError={hasError} + autoFocus={autoFocus} + value={value} + placeholder={placeholder} + onChange={onChange} + ref={combinedRef} + withRightComponent={!!rightComponent} + /> + )} + {!!rightComponent && ( + <StyledRightContainer>{rightComponent}</StyledRightContainer> + )} + </StyledInputContainer> + {error && <StyledErrorDiv>{error}</StyledErrorDiv>} + </> ); }, ); diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx index b9f1bc87d286..ec761aa46680 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx @@ -36,15 +36,15 @@ const StyledInput = styled.input` export const DropdownMenuSearchInput = forwardRef< HTMLInputElement, InputHTMLAttributes<HTMLInputElement> ->(({ value, onChange, placeholder = 'Search', type }) => { +>(({ value, onChange, placeholder = 'Search', type }, forwardedRef) => { const { inputRef } = useInputFocusWithoutScrollOnMount(); - + const ref = forwardedRef ?? inputRef; return ( <StyledDropdownMenuSearchInputContainer> <StyledInput autoComplete="off" {...{ onChange, placeholder, type, value }} - ref={inputRef} + ref={ref} /> </StyledDropdownMenuSearchInputContainer> ); diff --git a/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useIsMenuNavbarDisplayed.test.tsx b/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useIsMenuNavbarDisplayed.test.tsx deleted file mode 100644 index 990ff743bbf6..000000000000 --- a/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useIsMenuNavbarDisplayed.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as reactRouterDom from 'react-router-dom'; - -import { useIsMenuNavbarDisplayed } from '../useIsMenuNavbarDisplayed'; - -jest.mock('react-router-dom', () => ({ - useLocation: jest.fn(), -})); - -const setupMockLocation = (pathname: string) => { - jest.spyOn(reactRouterDom, 'useLocation').mockReturnValueOnce({ - pathname, - state: undefined, - key: '', - search: '', - hash: '', - }); -}; - -describe('useIsMenuNavbarDisplayed', () => { - it('Should return true for paths starting with "/companies"', () => { - setupMockLocation('/companies'); - - const result = useIsMenuNavbarDisplayed(); - expect(result).toBeTruthy(); - }); - - it('Should return true for paths starting with "/companies/"', () => { - setupMockLocation('/companies/test-some-subpath'); - - const result = useIsMenuNavbarDisplayed(); - expect(result).toBeTruthy(); - }); - - it('Should return false for paths not starting with "/companies"', () => { - setupMockLocation('/test-path'); - - const result = useIsMenuNavbarDisplayed(); - expect(result).toBeFalsy(); - }); -}); diff --git a/packages/twenty-front/src/modules/ui/layout/hooks/useIsMenuNavbarDisplayed.ts b/packages/twenty-front/src/modules/ui/layout/hooks/useIsMenuNavbarDisplayed.ts deleted file mode 100644 index 08f6103f310d..000000000000 --- a/packages/twenty-front/src/modules/ui/layout/hooks/useIsMenuNavbarDisplayed.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useLocation } from 'react-router-dom'; - -export const useIsMenuNavbarDisplayed = () => { - const currentPath = useLocation().pathname; - return currentPath.match(/^\/companies(\/.*)?$/) !== null; -}; diff --git a/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx index 4108f6a5cee6..0a05da36515e 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx @@ -1,7 +1,4 @@ -import { css, Global, useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; -import { Outlet } from 'react-router-dom'; +import { AuthModal } from '@/auth/components/AuthModal'; import { CommandMenu } from '@/command-menu/components/CommandMenu'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu'; @@ -14,7 +11,10 @@ import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize'; -import { AuthModal } from '@/auth/components/AuthModal'; +import { css, Global, useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; +import { Outlet } from 'react-router-dom'; const StyledLayout = styled.div` background: ${({ theme }) => theme.background.noisy}; diff --git a/packages/twenty-front/src/modules/ui/layout/page/ShowPageContainer.tsx b/packages/twenty-front/src/modules/ui/layout/page/ShowPageContainer.tsx index 9a1aa473b987..79d8778660d2 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/ShowPageContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/ShowPageContainer.tsx @@ -6,7 +6,6 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; const StyledOuterContainer = styled.div` display: flex; - gap: ${({ theme }) => (useIsMobile() ? theme.spacing(3) : '0')}; height: 100%; width: 100%; diff --git a/packages/twenty-front/src/modules/ui/layout/page/SubMenuTopBarContainer.tsx b/packages/twenty-front/src/modules/ui/layout/page/SubMenuTopBarContainer.tsx index ef767a67a41e..9b26f71bc153 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/SubMenuTopBarContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/SubMenuTopBarContainer.tsx @@ -1,6 +1,5 @@ import styled from '@emotion/styled'; import { JSX, ReactNode } from 'react'; -import { IconComponent } from 'twenty-ui'; import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; import { @@ -14,7 +13,6 @@ type SubMenuTopBarContainerProps = { children: JSX.Element | JSX.Element[]; title?: string; actionButton?: ReactNode; - Icon: IconComponent; className?: string; links: BreadcrumbProps['links']; }; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx index d28366027c44..92ed242e7d6d 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx @@ -28,10 +28,18 @@ import { RightDrawerHotkeyScope } from '../types/RightDrawerHotkeyScope'; import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent'; import { RightDrawerRouter } from './RightDrawerRouter'; -const StyledContainer = styled(motion.div)` +const StyledContainer = styled(motion.div)<{ isRightDrawerMinimized: boolean }>` background: ${({ theme }) => theme.background.primary}; - border-left: 1px solid ${({ theme }) => theme.border.color.medium}; - box-shadow: ${({ theme }) => theme.boxShadow.strong}; + border-left: ${({ theme, isRightDrawerMinimized }) => + isRightDrawerMinimized + ? `1px solid ${theme.border.color.strong}` + : `1px solid ${theme.border.color.medium}`}; + border-top: ${({ theme, isRightDrawerMinimized }) => + isRightDrawerMinimized ? `1px solid ${theme.border.color.strong}` : 'none'}; + border-top-left-radius: ${({ theme, isRightDrawerMinimized }) => + isRightDrawerMinimized ? theme.border.radius.md : '0'}; + box-shadow: ${({ theme, isRightDrawerMinimized }) => + isRightDrawerMinimized ? 'none' : theme.boxShadow.light}; height: 100dvh; overflow-x: hidden; position: fixed; @@ -157,6 +165,7 @@ export const RightDrawer = () => { return ( <StyledContainer + isRightDrawerMinimized={isRightDrawerMinimized} animate={targetVariantForAnimation} variants={animationVariants} transition={{ diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx index 972b2a75e3a2..a5640cfc045e 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx @@ -11,6 +11,7 @@ import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDraw import { ComponentByRightDrawerPage } from '@/ui/layout/right-drawer/types/ComponentByRightDrawerPage'; import { RightDrawerWorkflowEditStep } from '@/workflow/components/RightDrawerWorkflowEditStep'; import { RightDrawerWorkflowSelectAction } from '@/workflow/components/RightDrawerWorkflowSelectAction'; +import { RightDrawerWorkflowViewStep } from '@/workflow/components/RightDrawerWorkflowViewStep'; import { isDefined } from 'twenty-ui'; import { rightDrawerPageState } from '../states/rightDrawerPageState'; import { RightDrawerPages } from '../types/RightDrawerPages'; @@ -41,6 +42,7 @@ const RIGHT_DRAWER_PAGES_CONFIG: ComponentByRightDrawerPage = { <RightDrawerWorkflowSelectAction /> ), [RightDrawerPages.WorkflowStepEdit]: <RightDrawerWorkflowEditStep />, + [RightDrawerPages.WorkflowStepView]: <RightDrawerWorkflowViewStep />, }; export const RightDrawerRouter = () => { 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 && ( <Chip + disabled={isNewViewableRecordLoading} label={label} leftComponent={<Icon size={theme.icon.size.md} />} size={ChipSize.Large} diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/__stories__/RightDrawerTopBar.stories.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/__stories__/RightDrawerTopBar.stories.tsx index 3f69b0dc387d..16f53f1a213f 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/__stories__/RightDrawerTopBar.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/__stories__/RightDrawerTopBar.stories.tsx @@ -1,27 +1,56 @@ +import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; +import { RightDrawerTopBar } from '../RightDrawerTopBar'; import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; -import { graphqlMocks } from '~/testing/graphqlMocks'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; +import { useSetRecoilState } from 'recoil'; +import { rightDrawerPageState } from '@/ui/layout/right-drawer/states/rightDrawerPageState'; +import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState'; +import { useEffect } from 'react'; +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; +import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; +import { within } from '@storybook/test'; -import { RightDrawerTopBar } from '../RightDrawerTopBar'; +const RightDrawerTopBarStateSetterEffect = () => { + const setRightDrawerPage = useSetRecoilState(rightDrawerPageState); + + const setIsRightDrawerMinimizedState = useSetRecoilState( + isRightDrawerMinimizedState, + ); + + useEffect(() => { + setRightDrawerPage(RightDrawerPages.ViewRecord); + setIsRightDrawerMinimizedState(false); + }, [setIsRightDrawerMinimizedState, setRightDrawerPage]); + return null; +}; const meta: Meta<typeof RightDrawerTopBar> = { - title: 'Modules/Activities/RightDrawer/RightDrawerActivityTopBar', + title: 'Modules/Activities/RightDrawer/RightDrawerTopBar', component: RightDrawerTopBar, decorators: [ (Story) => ( <div style={{ width: '500px' }}> <Story /> + <RightDrawerTopBarStateSetterEffect /> </div> ), + IconsProviderDecorator, ComponentWithRouterDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, ], - parameters: { - msw: graphqlMocks, - }, }; export default meta; type Story = StoryObj<typeof RightDrawerTopBar>; -export const Default: Story = {}; +export const Default: Story = { + play: async () => { + const canvas = within(document.body); + + expect(await canvas.findByText('Company')).toBeInTheDocument(); + }, +}; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts index 7fc5d9849d89..85dc75ee18b6 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts @@ -7,4 +7,5 @@ export const RIGHT_DRAWER_PAGE_ICONS = { [RightDrawerPages.Copilot]: 'IconSparkles', [RightDrawerPages.WorkflowStepEdit]: 'IconSparkles', [RightDrawerPages.WorkflowStepSelectAction]: 'IconSparkles', + [RightDrawerPages.WorkflowStepView]: 'IconSparkles', }; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts index 749fb10384fc..9cba79382a0a 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts @@ -7,4 +7,5 @@ export const RIGHT_DRAWER_PAGE_TITLES = { [RightDrawerPages.Copilot]: 'Copilot', [RightDrawerPages.WorkflowStepEdit]: 'Workflow', [RightDrawerPages.WorkflowStepSelectAction]: 'Workflow', + [RightDrawerPages.WorkflowStepView]: 'Workflow', }; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts index f016669b48a2..68e20913a4f6 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts @@ -4,5 +4,6 @@ export enum RightDrawerPages { ViewRecord = 'view-record', Copilot = 'copilot', WorkflowStepSelectAction = 'workflow-step-select-action', + WorkflowStepView = 'workflow-step-view', WorkflowStepEdit = 'workflow-step-edit', } 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 ? ( <ScrollWrapper contextProviderName="showPageActivityContainer"> <StyledShowPageActivityContainer> <RichTextEditor @@ -30,5 +36,7 @@ export const ShowPageActivityContainer = ({ /> </StyledShowPageActivityContainer> </ScrollWrapper> + ) : ( + <></> ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx index 9cecd4558277..8b44e15639cd 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx @@ -11,7 +11,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; -import { useDestroyManyRecords } from '@/object-record/hooks/useDestroyManyRecords'; +import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { Dropdown } from '../../dropdown/components/Dropdown'; @@ -35,7 +35,7 @@ export const ShowPageMoreButton = ({ const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular, }); - const { destroyManyRecords } = useDestroyManyRecords({ + const { destroyOneRecord } = useDestroyOneRecord({ objectNameSingular, }); const { restoreManyRecords } = useRestoreManyRecords({ @@ -45,11 +45,10 @@ export const ShowPageMoreButton = ({ const handleDelete = () => { deleteOneRecord(recordId); closeDropdown(); - navigate(navigationMemorizedUrl, { replace: true }); }; const handleDestroy = () => { - destroyManyRecords([recordId]); + destroyOneRecord(recordId); closeDropdown(); navigate(navigationMemorizedUrl, { replace: true }); }; 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 0df732bfe2a9..449963c0135b 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 @@ -1,16 +1,3 @@ -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; -import { - IconCalendarEvent, - IconCheckbox, - IconList, - IconMail, - IconNotes, - IconPaperclip, - IconSettings, - IconTimelineEvent, -} from 'twenty-ui'; - import { Calendar } from '@/activities/calendar/components/Calendar'; import { EmailThreads } from '@/activities/emails/components/EmailThreads'; import { Attachments } from '@/activities/files/components/Attachments'; @@ -19,20 +6,43 @@ import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks'; import { TimelineActivities } from '@/activities/timelineActivities/components/TimelineActivities'; 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'; import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer'; import { TabList } from '@/ui/layout/tab/components/TabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import { Workflow } from '@/workflow/components/Workflow'; +import { WorkflowVersionVisualizer } from '@/workflow/components/WorkflowVersionVisualizer'; +import { WorkflowVersionVisualizerEffect } from '@/workflow/components/WorkflowVersionVisualizerEffect'; +import { WorkflowVisualizer } from '@/workflow/components/WorkflowVisualizer'; +import { WorkflowVisualizerEffect } from '@/workflow/components/WorkflowVisualizerEffect'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import styled from '@emotion/styled'; +import { useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { + IconCalendarEvent, + IconCheckbox, + IconList, + IconMail, + IconNotes, + IconPaperclip, + IconSettings, + IconTimelineEvent, + IconTrash, +} from 'twenty-ui'; const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>` display: flex; - flex: 1 0 0; flex-direction: column; + height: 100%; justify-content: start; width: 100%; height: 100%; + overflow: auto; `; const StyledTabListContainer = styled.div` @@ -57,6 +67,26 @@ const StyledGreyBox = styled.div<{ isInRightDrawer: boolean }>` isInRightDrawer ? theme.spacing(4) : ''}; `; +const StyledButtonContainer = styled.div` + align-items: center; + bottom: 0; + border-top: 1px solid ${({ theme }) => theme.border.color.light}; + display: flex; + justify-content: flex-end; + padding: ${({ theme }) => theme.spacing(2)}; + width: 100%; + box-sizing: border-box; + position: absolute; + width: 100%; +`; + +const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>` + flex: 1; + overflow-y: auto; + padding-bottom: ${({ theme, isInRightDrawer }) => + isInRightDrawer ? theme.spacing(16) : 0}; +`; + export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list'; type ShowPageRightContainerProps = { @@ -103,11 +133,19 @@ export const ShowPageRightContainer = ({ isWorkflowEnabled && targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Workflow; + const isWorkflowVersion = + isWorkflowEnabled && + targetableObject.targetObjectNameSingular === + CoreObjectNameSingular.WorkflowVersion; const shouldDisplayCalendarTab = isCompanyOrPerson; const shouldDisplayEmailsTab = emails && isCompanyOrPerson; - const isMobile = useIsMobile() || isInRightDrawer; + const isMobile = useIsMobile(); + + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); const tabs = [ { @@ -125,13 +163,13 @@ export const ShowPageRightContainer = ({ id: 'fields', title: 'Fields', Icon: IconList, - hide: !isMobile, + hide: !(isMobile || isInRightDrawer), }, { id: 'timeline', title: 'Timeline', Icon: IconTimelineEvent, - hide: !timeline || isInRightDrawer || isWorkflow, + hide: !timeline || isInRightDrawer || isWorkflow || isWorkflowVersion, }, { id: 'tasks', @@ -143,7 +181,8 @@ export const ShowPageRightContainer = ({ CoreObjectNameSingular.Note || targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Task || - isWorkflow, + isWorkflow || + isWorkflowVersion, }, { id: 'notes', @@ -155,13 +194,14 @@ export const ShowPageRightContainer = ({ CoreObjectNameSingular.Note || targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Task || - isWorkflow, + isWorkflow || + isWorkflowVersion, }, { id: 'files', title: 'Files', Icon: IconPaperclip, - hide: !notes || isWorkflow, + hide: !notes || isWorkflow || isWorkflowVersion, }, { id: 'emails', @@ -181,6 +221,12 @@ export const ShowPageRightContainer = ({ Icon: IconSettings, hide: !isWorkflow, }, + { + id: 'workflowVersion', + title: 'Workflow Version', + Icon: IconSettings, + hide: !isWorkflowVersion, + }, ]; const renderActiveTabContent = () => { switch (activeTabId) { @@ -220,22 +266,69 @@ export const ShowPageRightContainer = ({ case 'calendar': return <Calendar targetableObject={targetableObject} />; case 'workflow': - return <Workflow targetableObject={targetableObject} />; + return ( + <> + <WorkflowVisualizerEffect workflowId={targetableObject.id} /> + + <WorkflowVisualizer targetableObject={targetableObject} /> + </> + ); + case 'workflowVersion': + return ( + <> + <WorkflowVersionVisualizerEffect + workflowVersionId={targetableObject.id} + /> + + <WorkflowVersionVisualizer + workflowVersionId={targetableObject.id} + /> + </> + ); default: return <></>; } }; + + const [isDeleting, setIsDeleting] = useState(false); + + const { deleteOneRecord } = useDeleteOneRecord({ + objectNameSingular: targetableObject.targetObjectNameSingular, + }); + + const handleDelete = async () => { + setIsDeleting(true); + await deleteOneRecord(targetableObject.id); + setIsDeleting(false); + }; + + const [recordFromStore] = useRecoilState<ObjectRecord | null>( + recordStoreFamilyState(targetableObject.id), + ); + return ( <StyledShowPageRightContainer isMobile={isMobile}> <StyledTabListContainer> <TabList - loading={loading} + loading={loading || isNewViewableRecordLoading} tabListId={`${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}`} tabs={tabs} /> </StyledTabListContainer> {summaryCard} - {renderActiveTabContent()} + <StyledContentContainer isInRightDrawer={isInRightDrawer}> + {renderActiveTabContent()} + </StyledContentContainer> + {isInRightDrawer && recordFromStore && !recordFromStore.deletedAt && ( + <StyledButtonContainer> + <Button + Icon={IconTrash} + onClick={handleDelete} + disabled={isDeleting} + title={isDeleting ? 'Deleting...' : 'Delete'} + ></Button> + </StyledButtonContainer> + )} </StyledShowPageRightContainer> ); }; 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 3d228d5bd792..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,8 +1,9 @@ +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'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; -import { AppTooltip, Avatar, AvatarType } from 'twenty-ui'; +import { AppTooltip, Avatar, AvatarType, IconComponent } from 'twenty-ui'; import { v4 as uuidV4 } from 'uuid'; import { @@ -17,6 +18,8 @@ type ShowPageSummaryCardProps = { date: string; id?: string; logoOrAvatar?: string; + icon?: IconComponent; + iconColor?: string; onUploadPicture?: (file: File) => void; title: ReactNode; loading: boolean; @@ -58,11 +61,12 @@ const StyledTitle = styled.div<{ isMobile: boolean }>` font-size: ${({ theme }) => theme.font.size.xl}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; justify-content: ${({ isMobile }) => (isMobile ? 'flex-start' : 'center')}; - width: ${({ isMobile }) => (isMobile ? '' : '100%')}; + max-width: 90%; `; -const StyledAvatarWrapper = styled.div` - cursor: pointer; +const StyledAvatarWrapper = styled.div<{ isAvatarEditable: boolean }>` + cursor: ${({ isAvatarEditable }) => + isAvatarEditable ? 'pointer' : 'default'}; `; const StyledFileInput = styled.input` @@ -85,9 +89,9 @@ const StyledShowPageSummaryCardSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - <Skeleton width={40} height={40} /> + <Skeleton width={40} height={SKELETON_LOADER_HEIGHT_SIZES.standard.xl} /> <StyledSubSkeleton> - <Skeleton width={96} height={16} /> + <Skeleton width={96} height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} /> </StyledSubSkeleton> </SkeletonTheme> ); @@ -99,6 +103,8 @@ export const ShowPageSummaryCard = ({ date, id, logoOrAvatar, + icon, + iconColor, onUploadPicture, title, loading, @@ -126,14 +132,16 @@ export const ShowPageSummaryCard = ({ return ( <StyledShowPageSummaryCard isMobile={isMobile}> - <StyledAvatarWrapper> + <StyledAvatarWrapper isAvatarEditable={!!onUploadPicture}> <Avatar avatarUrl={logoOrAvatar} onClick={onUploadPicture ? handleAvatarClick : undefined} size="xl" placeholderColorSeed={id} placeholder={avatarPlaceholder} - type={avatarType} + type={icon ? 'icon' : avatarType} + Icon={icon} + iconColor={iconColor} /> <StyledFileInput ref={inputFileRef} 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 ( + <SkeletonTheme + baseColor={theme.background.tertiary} + highlightColor={theme.background.transparent.lighter} + > + <StyledContainer> + <Skeleton + height={SKELETON_LOADER_HEIGHT_SIZES.standard.xl} + width={40} + /> + <StyledRectangularSkeleton /> + </StyledContainer> + </SkeletonTheme> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/table/components/SortableTableHeader.tsx b/packages/twenty-front/src/modules/ui/layout/table/components/SortableTableHeader.tsx index fcdb02ebf543..da59fd4af380 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/components/SortableTableHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/table/components/SortableTableHeader.tsx @@ -2,7 +2,7 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { sortedFieldByTableFamilyState } from '@/ui/layout/table/states/sortedFieldByTableFamilyState'; import { TableSortValue } from '@/ui/layout/table/types/TableSortValue'; import { useRecoilState } from 'recoil'; -import { IconArrowDown, IconArrowUp } from 'twenty-ui'; +import { IconArrowDown, IconArrowUp, IconComponent } from 'twenty-ui'; export const SortableTableHeader = ({ tableId, @@ -10,12 +10,14 @@ export const SortableTableHeader = ({ label, align = 'left', initialSort, + Icon, }: { tableId: string; fieldName: string; label: string; align?: 'left' | 'center' | 'right'; initialSort?: TableSortValue; + Icon?: IconComponent; }) => { const [sortedFieldByTable, setSortedFieldByTable] = useRecoilState( sortedFieldByTableFamilyState({ tableId }), @@ -54,6 +56,7 @@ export const SortableTableHeader = ({ <IconArrowDown size="14" /> ) ) : null} + {Icon && <Icon size={14} />} {label} {isSortActive && align === 'left' ? ( isAsc ? ( diff --git a/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx b/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx index a73b21d95669..f88f2d1b36cc 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx +++ b/packages/twenty-front/src/modules/ui/layout/table/components/TableRow.tsx @@ -1,6 +1,7 @@ -import { Link } from 'react-router-dom'; import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; +import { Link } from 'react-router-dom'; +import { MOBILE_VIEWPORT } from 'twenty-ui'; const StyledTableRow = styled('div', { shouldForwardProp: (prop) => @@ -10,12 +11,19 @@ const StyledTableRow = styled('div', { onClick?: () => void; to?: string; gridAutoColumns?: string; + mobileGridAutoColumns?: string; }>` background-color: ${({ isSelected, theme }) => isSelected ? theme.accent.quaternary : 'transparent'}; border-radius: ${({ theme }) => theme.border.radius.sm}; display: grid; grid-auto-columns: ${({ gridAutoColumns }) => gridAutoColumns ?? '1fr'}; + + @media (max-width: ${MOBILE_VIEWPORT}px) { + grid-auto-columns: ${({ mobileGridAutoColumns, gridAutoColumns }) => + mobileGridAutoColumns ?? gridAutoColumns ?? '1fr'}; + } + grid-auto-flow: column; transition: background-color ${({ theme }) => theme.animation.duration.normal}s; @@ -35,6 +43,7 @@ type TableRowProps = { to?: string; className?: string; gridAutoColumns?: string; + mobileGridAutoColumns?: string; }; export const TableRow = ({ @@ -44,12 +53,14 @@ export const TableRow = ({ className, children, gridAutoColumns, + mobileGridAutoColumns, }: React.PropsWithChildren<TableRowProps>) => ( <StyledTableRow isSelected={isSelected} onClick={onClick} gridAutoColumns={gridAutoColumns} className={className} + mobileGridAutoColumns={mobileGridAutoColumns} to={to} as={to ? Link : 'div'} > diff --git a/packages/twenty-front/src/modules/ui/layout/table/components/TableSection.tsx b/packages/twenty-front/src/modules/ui/layout/table/components/TableSection.tsx index cbec405327e2..84be28c93e60 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/components/TableSection.tsx +++ b/packages/twenty-front/src/modules/ui/layout/table/components/TableSection.tsx @@ -1,8 +1,7 @@ -import { ReactNode, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { ReactNode, useState } from 'react'; import { IconChevronDown, IconChevronUp } from 'twenty-ui'; - import { TableBody } from './TableBody'; type TableSectionProps = { @@ -28,7 +27,7 @@ const StyledSectionHeader = styled.div<{ isExpanded: boolean }>` `; const StyledSection = styled.div<{ isExpanded: boolean }>` - max-height: ${({ isExpanded }) => (isExpanded ? '1000px' : 0)}; + max-height: ${({ isExpanded }) => (isExpanded ? 'fit-content' : 0)}; opacity: ${({ isExpanded }) => (isExpanded ? 1 : 0)}; overflow: hidden; transition: diff --git a/packages/twenty-front/src/modules/ui/layout/table/types/TableFieldMetadata.ts b/packages/twenty-front/src/modules/ui/layout/table/types/TableFieldMetadata.ts index 4e807bda6533..9e42c14ee18f 100644 --- a/packages/twenty-front/src/modules/ui/layout/table/types/TableFieldMetadata.ts +++ b/packages/twenty-front/src/modules/ui/layout/table/types/TableFieldMetadata.ts @@ -1,6 +1,9 @@ +import { IconComponent } from 'twenty-ui'; + export type TableFieldMetadata<ItemType> = { fieldLabel: string; fieldName: keyof ItemType; fieldType: 'string' | 'number'; align: 'left' | 'right'; + FieldIcon?: IconComponent; }; diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx deleted file mode 100644 index c020f42709c6..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import styled from '@emotion/styled'; -import { useEffect, useRef } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; - -import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; -import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; -import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal'; - -import { isDefined } from '~/utils/isDefined'; -import { ActionBarItem } from './ActionBarItem'; - -type ActionBarProps = { - selectedIds?: string[]; - totalNumberOfSelectedRecords?: number; -}; - -const StyledContainerActionBar = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.md}; - bottom: 38px; - box-shadow: ${({ theme }) => theme.boxShadow.strong}; - display: flex; - height: 48px; - width: max-content; - left: 50%; - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; - position: absolute; - top: auto; - - transform: translateX(-50%); - z-index: 1; -`; - -const StyledLabel = styled.div` - color: ${({ theme }) => theme.font.color.tertiary}; - font-size: ${({ theme }) => theme.font.size.md}; - font-weight: ${({ theme }) => theme.font.weight.medium}; - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; -`; - -export const ActionBar = ({ - selectedIds = [], - totalNumberOfSelectedRecords, -}: ActionBarProps) => { - const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); - - useEffect(() => { - if (selectedIds && selectedIds.length > 1) { - setContextMenuOpenState(false); - } - }, [selectedIds, setContextMenuOpenState]); - - const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); - const actionBarEntries = useRecoilValue(actionBarEntriesState); - const wrapperRef = useRef<HTMLDivElement>(null); - - if (contextMenuIsOpen) { - return null; - } - - const selectedNumberLabel = - totalNumberOfSelectedRecords ?? selectedIds?.length; - - const showSelectedNumberLabel = - isDefined(totalNumberOfSelectedRecords) || Array.isArray(selectedIds); - - return ( - <> - <StyledContainerActionBar - data-select-disable - className="action-bar" - ref={wrapperRef} - > - {showSelectedNumberLabel && ( - <StyledLabel>{selectedNumberLabel} selected:</StyledLabel> - )} - {actionBarEntries.map((item, index) => ( - <ActionBarItem key={index} item={item} /> - ))} - </StyledContainerActionBar> - <SharedNavigationModal - actionBarEntries={actionBarEntries} - customClassName="action-bar" - /> - </> - ); -}; diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBarItem.tsx b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBarItem.tsx deleted file mode 100644 index dbb7623d25d4..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBarItem.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { IconChevronDown } from 'twenty-ui'; - -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { ActionBarEntry } from '@/ui/navigation/action-bar/types/ActionBarEntry'; -import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; -import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent'; - -type ActionBarItemProps = { - item: ActionBarEntry; -}; - -const StyledButton = styled.div<{ accent: MenuItemAccent }>` - border-radius: ${({ theme }) => theme.border.radius.sm}; - color: ${(props) => - props.accent === 'danger' - ? props.theme.color.red - : props.theme.font.color.secondary}; - cursor: pointer; - display: flex; - justify-content: center; - - padding: ${({ theme }) => theme.spacing(2)}; - transition: background 0.1s ease; - user-select: none; - - &:hover { - background: ${({ theme, accent }) => - accent === 'danger' - ? theme.background.danger - : theme.background.tertiary}; - } -`; - -const StyledButtonLabel = styled.div` - font-weight: ${({ theme }) => theme.font.weight.medium}; - margin-left: ${({ theme }) => theme.spacing(1)}; -`; - -export const ActionBarItem = ({ item }: ActionBarItemProps) => { - const theme = useTheme(); - const dropdownId = `action-bar-item-${item.label}`; - const { toggleDropdown, closeDropdown } = useDropdown(dropdownId); - return ( - <> - {Array.isArray(item.subActions) ? ( - <Dropdown - dropdownId={dropdownId} - dropdownPlacement="top-start" - dropdownHotkeyScope={{ - scope: dropdownId, - }} - clickableComponent={ - <StyledButton - accent={item.accent ?? 'default'} - onClick={toggleDropdown} - > - {item.Icon && <item.Icon size={theme.icon.size.md} />} - <StyledButtonLabel>{item.label}</StyledButtonLabel> - <IconChevronDown size={theme.icon.size.md} /> - </StyledButton> - } - dropdownComponents={ - <DropdownMenuItemsContainer> - {item.subActions.map((subAction) => ( - <MenuItem - key={subAction.label} - text={subAction.label} - LeftIcon={subAction.Icon} - onClick={() => { - closeDropdown(); - subAction.onClick?.(); - }} - /> - ))} - </DropdownMenuItemsContainer> - } - /> - ) : ( - <StyledButton - accent={item.accent ?? 'default'} - onClick={() => item.onClick?.()} - > - {item.Icon && <item.Icon size={theme.icon.size.md} />} - <StyledButtonLabel>{item.label}</StyledButtonLabel> - </StyledButton> - )} - </> - ); -}; diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx deleted file mode 100644 index 9610eb43b311..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { useSetRecoilState } from 'recoil'; -import { ComponentDecorator } from 'twenty-ui'; - -import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope'; -import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; - -import { actionBarOpenState } from '../../states/actionBarIsOpenState'; -import { ActionBar } from '../ActionBar'; - -const FilledActionBar = () => { - const setActionBarOpenState = useSetRecoilState(actionBarOpenState); - setActionBarOpenState(true); - return <ActionBar />; -}; - -const meta: Meta<typeof ActionBar> = { - title: 'UI/Navigation/ActionBar/ActionBar', - component: FilledActionBar, - decorators: [ - MemoryRouterDecorator, - (Story) => ( - <RecordTableScope - recordTableScopeId="companies" - onColumnsChange={() => {}} - > - <Story /> - </RecordTableScope> - ), - ComponentDecorator, - ], - args: { selectedIds: ['TestId'] }, -}; - -export default meta; -type Story = StoryObj<typeof ActionBar>; - -export const Default: Story = {}; diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/states/actionBarEntriesState.ts b/packages/twenty-front/src/modules/ui/navigation/action-bar/states/actionBarEntriesState.ts deleted file mode 100644 index 35f8cab412b7..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/states/actionBarEntriesState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createState } from 'twenty-ui'; - -import { ActionBarEntry } from '../types/ActionBarEntry'; - -export const actionBarEntriesState = createState<ActionBarEntry[]>({ - key: 'actionBarEntriesState', - defaultValue: [], -}); diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/states/actionBarIsOpenState.ts b/packages/twenty-front/src/modules/ui/navigation/action-bar/states/actionBarIsOpenState.ts deleted file mode 100644 index 0ef918e6652e..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/states/actionBarIsOpenState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createState } from 'twenty-ui'; - -export const actionBarOpenState = createState<boolean>({ - key: 'actionBarOpenState', - defaultValue: false, -}); diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/types/ActionBarEntry.ts b/packages/twenty-front/src/modules/ui/navigation/action-bar/types/ActionBarEntry.ts deleted file mode 100644 index a276736cfb1c..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/types/ActionBarEntry.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry'; - -export type ActionBarEntry = ContextMenuEntry & { - subActions?: ActionBarEntry[]; -}; diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx deleted file mode 100644 index e27c6096cb07..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useRef } from 'react'; -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; - -import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; -import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; -import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal'; - -import { contextMenuEntriesState } from '../states/contextMenuEntriesState'; -import { contextMenuIsOpenState } from '../states/contextMenuIsOpenState'; -import { PositionType } from '../types/PositionType'; - -import { ContextMenuItem } from './ContextMenuItem'; - -type StyledContainerProps = { - position: PositionType; -}; - -const StyledContainerContextMenu = styled.div<StyledContainerProps>` - align-items: flex-start; - background: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.light}; - border-radius: ${({ theme }) => theme.border.radius.md}; - box-shadow: ${({ theme }) => theme.boxShadow.strong}; - display: flex; - flex-direction: column; - gap: 1px; - - left: ${(props) => `${props.position.x}px`}; - position: fixed; - top: ${(props) => `${props.position.y}px`}; - - transform: translateX(-50%); - width: auto; - z-index: 2; -`; - -export const ContextMenu = () => { - const contextMenuPosition = useRecoilValue(contextMenuPositionState); - const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); - const contextMenuEntries = useRecoilValue(contextMenuEntriesState); - const wrapperRef = useRef<HTMLDivElement>(null); - const actionBarEntries = useRecoilValue(actionBarEntriesState); - - if (!contextMenuIsOpen) { - return null; - } - - const width = contextMenuEntries.some( - (contextMenuEntry) => contextMenuEntry.label === 'Remove from favorites', - ) - ? 200 - : undefined; - - return ( - <> - <StyledContainerContextMenu - className="context-menu" - ref={wrapperRef} - position={contextMenuPosition} - > - <DropdownMenu data-select-disable width={width}> - <DropdownMenuItemsContainer> - {contextMenuEntries.map((item, index) => { - return <ContextMenuItem key={index} item={item} />; - })} - </DropdownMenuItemsContainer> - </DropdownMenu> - </StyledContainerContextMenu> - <SharedNavigationModal - actionBarEntries={actionBarEntries} - customClassName="context-menu" - /> - </> - ); -}; diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenuItem.tsx b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenuItem.tsx deleted file mode 100644 index 4ec9822a6750..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenuItem.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry'; -import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; - -type ContextMenuItemProps = { - item: ContextMenuEntry; -}; - -export const ContextMenuItem = ({ item }: ContextMenuItemProps) => ( - <MenuItem - LeftIcon={item.Icon} - onClick={item.onClick} - accent={item.accent} - text={item.label} - /> -); diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/__stories__/ContextMenu.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/__stories__/ContextMenu.stories.tsx deleted file mode 100644 index 2b2f29e5c4ee..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/__stories__/ContextMenu.stories.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { useSetRecoilState } from 'recoil'; -import { ComponentDecorator } from 'twenty-ui'; - -import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope'; -import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; - -import { contextMenuIsOpenState } from '../../states/contextMenuIsOpenState'; -import { contextMenuPositionState } from '../../states/contextMenuPositionState'; -import { ContextMenu } from '../ContextMenu'; - -const FilledContextMenu = () => { - const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); - setContextMenuPosition({ - x: 100, - y: 10, - }); - const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); - setContextMenuOpenState(true); - return <ContextMenu />; -}; - -const meta: Meta<typeof ContextMenu> = { - title: 'UI/Navigation/ContextMenu/ContextMenu', - component: FilledContextMenu, - decorators: [ - MemoryRouterDecorator, - (Story) => ( - <RecordTableScope - recordTableScopeId="companies" - onColumnsChange={() => {}} - > - <Story /> - </RecordTableScope> - ), - ComponentDecorator, - ], - args: { selectedIds: ['TestId'] }, -}; - -export default meta; -type Story = StoryObj<typeof ContextMenu>; - -export const Default: Story = {}; diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuEntriesState.ts b/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuEntriesState.ts deleted file mode 100644 index 1e22b562187e..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuEntriesState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createState } from 'twenty-ui'; - -import { ContextMenuEntry } from '../types/ContextMenuEntry'; - -export const contextMenuEntriesState = createState<ContextMenuEntry[]>({ - key: 'contextMenuEntriesState', - defaultValue: [], -}); diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuIsOpenState.ts b/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuIsOpenState.ts deleted file mode 100644 index d5aec39b905e..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuIsOpenState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createState } from 'twenty-ui'; - -export const contextMenuIsOpenState = createState<boolean>({ - key: 'contextMenuIsOpenState', - defaultValue: false, -}); diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuPositionState.ts b/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuPositionState.ts deleted file mode 100644 index a47df13eb017..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/states/contextMenuPositionState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createState } from 'twenty-ui'; - -import { PositionType } from '@/ui/navigation/context-menu/types/PositionType'; - -export const contextMenuPositionState = createState<PositionType>({ - key: 'contextMenuPositionState', - defaultValue: { - x: null, - y: null, - }, -}); diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/types/ContextMenuItemAccent.ts b/packages/twenty-front/src/modules/ui/navigation/context-menu/types/ContextMenuItemAccent.ts deleted file mode 100644 index ceae88f5beea..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/types/ContextMenuItemAccent.ts +++ /dev/null @@ -1 +0,0 @@ -export type ContextMenuItemAccent = 'default' | 'danger'; diff --git a/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx b/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx new file mode 100644 index 000000000000..e08f60031bf1 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/link/components/AdvancedSettingsToggle.tsx @@ -0,0 +1,64 @@ +import { Toggle } from '@/ui/input/components/Toggle'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; +import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; +import { IconTool, MAIN_COLORS } from 'twenty-ui'; + +const StyledContainer = styled.div` + align-items: center; + display: flex; + width: 100%; + gap: ${({ theme }) => theme.spacing(2)}; + position: relative; +`; + +const StyledText = styled.span` + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + padding: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledIconContainer = styled.div` + border-right: 1px solid ${MAIN_COLORS.yellow}; + height: 16px; + position: absolute; + left: ${({ theme }) => theme.spacing(-5)}; +`; + +const StyledToggleContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +`; + +const StyledIconTool = styled(IconTool)` + margin-right: ${({ theme }) => theme.spacing(0.5)}; +`; + +export const AdvancedSettingsToggle = () => { + const [isAdvancedModeEnabled, setIsAdvancedModeEnabled] = useRecoilState( + isAdvancedModeEnabledState, + ); + + const onChange = (newValue: boolean) => { + setIsAdvancedModeEnabled(newValue); + }; + + return ( + <StyledContainer> + <StyledIconContainer> + <StyledIconTool size={12} color={MAIN_COLORS.yellow} /> + </StyledIconContainer> + <StyledToggleContainer> + <StyledText>Advanced:</StyledText> + <Toggle + onChange={onChange} + color={MAIN_COLORS.yellow} + value={isAdvancedModeEnabled} + /> + </StyledToggleContainer> + </StyledContainer> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx index 7e8dff27ecfc..8086e4ad0a3c 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx @@ -22,7 +22,7 @@ type MenuItemMultiSelectAvatarProps = { avatar?: ReactNode; selected: boolean; isKeySelected?: boolean; - text: string; + text?: string; className?: string; onSelectChange?: (selected: boolean) => void; }; diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx index 5f6dee4cd9d1..7a5a976709f0 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelect.tsx @@ -1,6 +1,6 @@ import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { IconCheck, IconComponent } from 'twenty-ui'; +import { IconCheck, IconChevronRight, IconComponent } from 'twenty-ui'; import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase'; @@ -45,6 +45,7 @@ type MenuItemSelectProps = { onClick?: () => void; disabled?: boolean; hovered?: boolean; + hasSubMenu?: boolean; }; export const MenuItemSelect = ({ @@ -55,6 +56,7 @@ export const MenuItemSelect = ({ onClick, disabled, hovered, + hasSubMenu = false, }: MenuItemSelectProps) => { const theme = useTheme(); @@ -68,6 +70,12 @@ export const MenuItemSelect = ({ > <MenuItemLeftContent LeftIcon={LeftIcon} text={text} /> {selected && <IconCheck size={theme.icon.size.md} />} + {hasSubMenu && ( + <IconChevronRight + size={theme.icon.size.sm} + color={theme.font.color.tertiary} + /> + )} </StyledMenuItemSelect> ); }; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx index 35fb3cae0d54..997441038d29 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx @@ -40,21 +40,21 @@ const StyledContainer = styled.div<{ isSubMenu?: boolean }>` ${({ isSubMenu, theme }) => isSubMenu ? css` + padding-left: ${theme.spacing(0)}; padding-right: ${theme.spacing(8)}; ` : ''} @media (max-width: ${MOBILE_VIEWPORT}px) { width: 100%; + padding-left: 20px; + padding-right: 20px; } `; - const StyledItemsContainer = styled.div` display: flex; flex-direction: column; - gap: ${({ theme }) => theme.spacing(3)}; margin-bottom: auto; - overflow-y: auto; `; export const NavigationDrawer = ({ diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx index bea1d0f8e46a..2ba98503329b 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx @@ -4,6 +4,8 @@ const StyledSection = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.betweenSiblingsGap}; + width: 100%; + margin-bottom: ${({ theme }) => theme.spacing(3)}; `; export { StyledSection as NavigationDrawerSection }; 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} > <StyledSkeletonTitle> - <Skeleton width={56} height={13} /> + <Skeleton + width={56} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.xs} + /> </StyledSkeletonTitle> </SkeletonTheme> ); diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState.ts b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState.ts new file mode 100644 index 000000000000..05fd34d432a3 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState.ts @@ -0,0 +1,8 @@ +import { atom } from 'recoil'; +import { localStorageEffect } from '~/utils/recoil-effects'; + +export const isAdvancedModeEnabledState = atom<boolean>({ + key: 'isAdvancedModeEnabledAtom', + default: false, + effects: [localStorageEffect()], +}); diff --git a/packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx deleted file mode 100644 index becc1b72ab66..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { IconTrash } from 'twenty-ui'; - -import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; -import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal'; - -const meta: Meta<typeof SharedNavigationModal> = { - title: 'UI/Navigation/Shared/SharedNavigationModal', - component: SharedNavigationModal, - args: { - actionBarEntries: [ - { - ConfirmationModal: ( - <ConfirmationModal - title="Title" - deleteButtonText="Delete" - onConfirmClick={() => {}} - setIsOpen={() => {}} - isOpen={false} - subtitle="Subtitle" - /> - ), - Icon: IconTrash, - label: 'Label', - onClick: () => {}, - }, - ], - customClassName: 'customClassName', - }, -}; - -export default meta; -type Story = StoryObj<typeof SharedNavigationModal>; - -export const Default: Story = {}; diff --git a/packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx b/packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx deleted file mode 100644 index eeba07087165..000000000000 --- a/packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ActionBarEntry } from '@/ui/navigation/action-bar/types/ActionBarEntry'; - -type SharedNavigationModalProps = { - actionBarEntries: ActionBarEntry[]; - customClassName: string; -}; - -const SharedNavigationModal = ({ - actionBarEntries, - customClassName, -}: SharedNavigationModalProps) => { - return ( - <div data-select-disable className={customClassName}> - {actionBarEntries.map((actionBarEntry, index) => - actionBarEntry.ConfirmationModal ? ( - <div key={index}>{actionBarEntry.ConfirmationModal}</div> - ) : null, - )} - </div> - ); -}; - -export default SharedNavigationModal; diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/utils/isNonTextWritingKey.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/utils/isNonTextWritingKey.ts index 8c1bd4be4b79..6c3a0f96ab81 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/utils/isNonTextWritingKey.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/utils/isNonTextWritingKey.ts @@ -35,7 +35,7 @@ export const isNonTextWritingKey = (key: string) => { 'Delete', 'End', 'PageDown', - 'ContextMenu', + 'ActionMenuDropdown', 'PrintScreen', 'BrowserBack', 'BrowserForward', diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2.ts index a76b91c2eb15..588cca873c9c 100644 --- a/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2.ts +++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2.ts @@ -1,10 +1,15 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { ComponentReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentReadOnlySelectorV2'; +import { ComponentSelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentSelectorV2'; import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2'; import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap'; -import { useRecoilValue } from 'recoil'; +import { RecoilState, RecoilValueReadOnly, useRecoilValue } from 'recoil'; export const useRecoilComponentValueV2 = <StateType>( - componentStateV2: ComponentStateV2<StateType>, + componentStateV2: + | ComponentStateV2<StateType> + | ComponentSelectorV2<StateType> + | ComponentReadOnlySelectorV2<StateType>, instanceIdFromProps?: string, ) => { const instanceContext = globalComponentInstanceContextMap.get( @@ -22,5 +27,18 @@ export const useRecoilComponentValueV2 = <StateType>( instanceIdFromProps, ); - return useRecoilValue(componentStateV2.atomFamily({ instanceId })); + let state: RecoilState<StateType> | RecoilValueReadOnly<StateType>; + + if (componentStateV2.type === 'ComponentState') { + state = componentStateV2.atomFamily({ instanceId }); + } else if ( + componentStateV2.type === 'ComponentSelector' || + componentStateV2.type === 'ComponentReadOnlySelector' + ) { + state = componentStateV2.selectorFamily({ instanceId }); + } else { + throw new Error('Invalid component state type'); + } + + return useRecoilValue(state); }; diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index bbaa9af31b03..b8b9fe56ffa4 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -9,6 +9,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { EditableFilterChip } from '@/views/components/EditableFilterChip'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; @@ -29,6 +30,7 @@ export const EditableFilterDropdownButton = ({ setFilterDefinitionUsedInDropdown, setSelectedOperandInDropdown, setSelectedFilter, + setIsObjectFilterDropdownOperandSelectUnfolded, } = useFilterDropdown({ filterDropdownId: viewFilterDropdownId, }); @@ -73,12 +75,22 @@ export const EditableFilterDropdownButton = ({ const { id: fieldId, value, operand } = viewFilter; if ( !value && - ![FilterOperand.IsEmpty, FilterOperand.IsNotEmpty].includes(operand) + ![ + FilterOperand.IsEmpty, + FilterOperand.IsNotEmpty, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, + ].includes(operand) ) { deleteCombinedViewFilter(fieldId); } }, [viewFilter, deleteCombinedViewFilter]); + const handleDropdownClose = useCallback(() => { + setIsObjectFilterDropdownOperandSelectUnfolded(false); + }, [setIsObjectFilterDropdownOperandSelectUnfolded]); + return ( <Dropdown dropdownId={viewFilterDropdownId} @@ -94,6 +106,7 @@ export const EditableFilterDropdownButton = ({ dropdownOffset={{ y: 8, x: 0 }} dropdownPlacement="bottom-start" onClickOutside={handleDropdownClickOutside} + onClose={handleDropdownClose} /> ); }; diff --git a/packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx b/packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx index 10ecdfa83b87..f811df0ddf6b 100644 --- a/packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/QueryParamsFiltersEffect.tsx @@ -1,23 +1,24 @@ import { useEffect } from 'react'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2'; import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams'; -import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; +import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { unsavedToUpsertViewFiltersComponentFamilyState } from '@/views/states/unsavedToUpsertViewFiltersComponentFamilyState'; -import { isDefined } from 'twenty-ui'; export const QueryParamsFiltersEffect = () => { const { hasFiltersQueryParams, getFiltersFromQueryParams, viewIdQueryParam } = useViewFromQueryParams(); + const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState); + const setUnsavedViewFilter = useSetRecoilComponentFamilyStateV2( unsavedToUpsertViewFiltersComponentFamilyState, - { viewId: viewIdQueryParam }, + { viewId: viewIdQueryParam ?? currentViewId }, ); const { resetUnsavedViewStates } = useResetUnsavedViewStates(); - const { currentViewId } = useGetCurrentView(); useEffect(() => { if (!hasFiltersQueryParams) { @@ -29,18 +30,11 @@ export const QueryParamsFiltersEffect = () => { setUnsavedViewFilter(filtersFromParams); } }); - - return () => { - if (isDefined(currentViewId)) { - resetUnsavedViewStates(currentViewId); - } - }; }, [ getFiltersFromQueryParams, hasFiltersQueryParams, resetUnsavedViewStates, setUnsavedViewFilter, - currentViewId, ]); return <></>; diff --git a/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx b/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx index 2ee24d43fdc3..f306f29bd7d5 100644 --- a/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/QueryParamsViewIdEffect.tsx @@ -1,3 +1,4 @@ +import { contextStoreCurrentViewIdState } from '@/context-store/states/contextStoreCurrentViewIdState'; import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem'; import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; @@ -7,6 +8,7 @@ import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState'; import { isUndefined } from '@sniptt/guards'; import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDefined } from '~/utils/isDefined'; @@ -37,6 +39,9 @@ export const QueryParamsViewIdEffect = () => { objectMetadataItemId?.id, lastVisitedObjectMetadataItemId, ); + const setContextStoreCurrentViewId = useSetRecoilState( + contextStoreCurrentViewIdState, + ); // // TODO: scope view bar per view id if possible // const { resetCurrentView } = useResetCurrentView(); @@ -59,6 +64,7 @@ export const QueryParamsViewIdEffect = () => { }); } setCurrentViewId(lastVisitedViewId); + setContextStoreCurrentViewId(lastVisitedViewId); return; } @@ -73,6 +79,7 @@ export const QueryParamsViewIdEffect = () => { }); } setCurrentViewId(viewIdQueryParam); + setContextStoreCurrentViewId(viewIdQueryParam); return; } @@ -87,8 +94,13 @@ export const QueryParamsViewIdEffect = () => { }); } setCurrentViewId(indexView.id); + setContextStoreCurrentViewId(indexView.id); return; } + + return () => { + setContextStoreCurrentViewId(null); + }; }, [ currentViewId, getFiltersFromQueryParams, @@ -96,6 +108,7 @@ export const QueryParamsViewIdEffect = () => { lastVisitedViewId, objectMetadataItemId?.id, objectNamePlural, + setContextStoreCurrentViewId, setCurrentViewId, setLastVisitedObjectMetadataItem, setLastVisitedView, diff --git a/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx b/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx index 0fab6fffdece..1b55e6a32790 100644 --- a/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx @@ -39,8 +39,11 @@ const StyledChip = styled.div<{ variant: SortOrFitlerChipVariant }>` flex-shrink: 0; font-size: ${({ theme }) => theme.font.size.sm}; font-weight: ${({ theme }) => theme.font.weight.medium}; - padding: ${({ theme }) => theme.spacing(1) + ' ' + theme.spacing(2)}; + padding: ${({ theme }) => theme.spacing(0.5) + ' ' + theme.spacing(2)}; user-select: none; + white-space: nowrap; + + max-height: ${({ theme }) => theme.spacing(4.5)}; `; const StyledIcon = styled.div` @@ -52,6 +55,7 @@ const StyledIcon = styled.div` const StyledDelete = styled.div<{ variant: SortOrFitlerChipVariant }>` align-items: center; cursor: pointer; + padding: ${({ theme }) => theme.spacing(0.5)}; display: flex; font-size: ${({ theme }) => theme.font.size.sm}; margin-left: ${({ theme }) => theme.spacing(2)}; diff --git a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx index fa40544755de..ffe89f6b6b60 100644 --- a/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx +++ b/packages/twenty-front/src/modules/views/components/UpdateViewButtonGroup.tsx @@ -27,7 +27,9 @@ const StyledContainer = styled.div` margin-right: ${({ theme }) => theme.spacing(2)}; position: relative; `; - +const StyledButton = styled(Button)` + padding: ${({ theme }) => theme.spacing(1)}; +`; export type UpdateViewButtonGroupProps = { hotkeyScope: HotkeyScope; }; @@ -99,7 +101,7 @@ export const UpdateViewButtonGroup = ({ dropdownId={UPDATE_VIEW_BUTTON_DROPDOWN_ID} dropdownHotkeyScope={hotkeyScope} clickableComponent={ - <Button + <StyledButton size="small" accent="blue" Icon={IconChevronDown} diff --git a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx index e3d443ace9e1..18ef21581f11 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx @@ -31,24 +31,26 @@ export type ViewBarDetailsProps = { const StyledBar = styled.div` align-items: center; + align-items: center; + border-top: 1px solid ${({ theme }) => theme.border.color.light}; border-top: 1px solid ${({ theme }) => theme.border.color.light}; display: flex; flex-direction: row; - min-height: 32px; justify-content: space-between; - z-index: 4; + min-height: 32px; padding-top: ${({ theme }) => theme.spacing(1)}; padding-bottom: ${({ theme }) => theme.spacing(1)}; + z-index: 4; `; const StyledChipcontainer = styled.div` align-items: center; display: flex; flex-direction: row; + overflow: scroll; gap: ${({ theme }) => theme.spacing(1)}; - min-height: 32px; - margin-left: ${({ theme }) => theme.spacing(2)}; - flex-wrap: wrap; + padding-top: ${({ theme }) => theme.spacing(1)}; + z-index: 1; `; const StyledCancelButton = styled.button` @@ -57,15 +59,8 @@ const StyledCancelButton = styled.button` color: ${({ theme }) => theme.font.color.tertiary}; cursor: pointer; font-weight: ${({ theme }) => theme.font.weight.medium}; - margin-left: auto; - margin-right: ${({ theme }) => theme.spacing(2)}; - padding: ${(props) => { - const horiz = props.theme.spacing(2); - const vert = props.theme.spacing(1); - return `${vert} ${horiz} ${vert} ${horiz}`; - }}; user-select: none; - + margin-right: ${({ theme }) => theme.spacing(2)}; &:hover { background-color: ${({ theme }) => theme.background.tertiary}; border-radius: ${({ theme }) => theme.spacing(1)}; @@ -73,8 +68,10 @@ const StyledCancelButton = styled.button` `; const StyledFilterContainer = styled.div` - align-items: center; display: flex; + align-items: center; + flex: 1; + overflow-x: hidden; `; const StyledSeperatorContainer = styled.div` 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} > - <Skeleton width={140} height={16} /> + <Skeleton width={140} height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} /> </SkeletonTheme> ); }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts index 4161777a574d..488b33c31bd9 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts @@ -1,15 +1,15 @@ -import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; +import { useCallback } from 'react'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; -import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { GraphQLView } from '@/views/types/GraphQLView'; @@ -24,7 +24,7 @@ export const usePersistViewFilterRecords = () => { objectNameSingular: CoreObjectNameSingular.ViewFilter, }); - const { deleteOneRecordMutation } = useDeleteOneRecordMutation({ + const { destroyOneRecordMutation } = useDestroyOneRecordMutation({ objectNameSingular: CoreObjectNameSingular.ViewFilter, }); @@ -129,12 +129,12 @@ export const usePersistViewFilterRecords = () => { return Promise.all( viewFilterIdsToDelete.map((viewFilterId) => apolloClient.mutate({ - mutation: deleteOneRecordMutation, + mutation: destroyOneRecordMutation, variables: { - idToDelete: viewFilterId, + idToDestroy: viewFilterId, }, update: (cache, { data }) => { - const record = data?.['deleteViewFilter']; + const record = data?.['destroyViewFilter']; if (!record) return; @@ -142,10 +142,10 @@ export const usePersistViewFilterRecords = () => { if (!cachedRecord) return; - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + recordsToDestroy: [cachedRecord], objectMetadataItems, }); }, @@ -155,7 +155,7 @@ export const usePersistViewFilterRecords = () => { }, [ apolloClient, - deleteOneRecordMutation, + destroyOneRecordMutation, getRecordFromCache, objectMetadataItem, objectMetadataItems, diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts index 139fff85ffb5..1dae1bf39d62 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts @@ -1,15 +1,15 @@ -import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; +import { useCallback } from 'react'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; -import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { GraphQLView } from '@/views/types/GraphQLView'; @@ -24,7 +24,7 @@ export const usePersistViewSortRecords = () => { objectNameSingular: CoreObjectNameSingular.ViewSort, }); - const { deleteOneRecordMutation } = useDeleteOneRecordMutation({ + const { destroyOneRecordMutation } = useDestroyOneRecordMutation({ objectNameSingular: CoreObjectNameSingular.ViewSort, }); @@ -124,12 +124,12 @@ export const usePersistViewSortRecords = () => { return Promise.all( viewSortIdsToDelete.map((viewSortId) => apolloClient.mutate({ - mutation: deleteOneRecordMutation, + mutation: destroyOneRecordMutation, variables: { - idToDelete: viewSortId, + idToDestroy: viewSortId, }, update: (cache, { data }) => { - const record = data?.['deleteViewSort']; + const record = data?.['destroyViewSort']; if (!record) return; @@ -137,10 +137,10 @@ export const usePersistViewSortRecords = () => { if (!cachedRecord) return; - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + recordsToDestroy: [cachedRecord], objectMetadataItems, }); }, @@ -150,7 +150,7 @@ export const usePersistViewSortRecords = () => { }, [ apolloClient, - deleteOneRecordMutation, + destroyOneRecordMutation, getRecordFromCache, objectMetadataItem, objectMetadataItems, diff --git a/packages/twenty-front/src/modules/views/hooks/useChangeView.ts b/packages/twenty-front/src/modules/views/hooks/useChangeView.ts index 669de5157b1b..9ed9aeeff420 100644 --- a/packages/twenty-front/src/modules/views/hooks/useChangeView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useChangeView.ts @@ -1,19 +1,11 @@ import { useResetUnsavedViewStates } from '@/views/hooks/useResetUnsavedViewStates'; -import { useSearchParams } from 'react-router-dom'; +import { useSetViewInUrl } from '@/views/hooks/useSetViewInUrl'; export const useChangeView = (viewBarComponentId?: string) => { const { resetUnsavedViewStates } = useResetUnsavedViewStates(viewBarComponentId); - const [, setSearchParams] = useSearchParams(); - - const setViewInUrl = (viewId: string) => { - setSearchParams(() => { - const searchParams = new URLSearchParams(); - searchParams.set('view', viewId); - return searchParams; - }); - }; + const { setViewInUrl } = useSetViewInUrl(); const changeView = async (viewId: string) => { setViewInUrl(viewId); diff --git a/packages/twenty-front/src/modules/views/hooks/useSetViewInUrl.ts b/packages/twenty-front/src/modules/views/hooks/useSetViewInUrl.ts new file mode 100644 index 000000000000..01e0397ffd6d --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useSetViewInUrl.ts @@ -0,0 +1,15 @@ +import { useSearchParams } from 'react-router-dom'; + +export const useSetViewInUrl = () => { + const [, setSearchParams] = useSearchParams(); + + const setViewInUrl = (viewId: string) => { + setSearchParams(() => { + const searchParams = new URLSearchParams(); + searchParams.set('view', viewId); + return searchParams; + }); + }; + + return { setViewInUrl }; +}; diff --git a/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewFilters.ts b/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewFilters.ts index fd33eb009583..7c5e2d99b956 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpsertCombinedViewFilters.ts @@ -106,15 +106,18 @@ export const useUpsertCombinedViewFilters = (viewBarComponentId?: string) => { return; } + const newValue = [ + ...unsavedToUpsertViewFilters, + { + ...upsertedFilter, + id: upsertedFilter.id, + __typename: 'ViewFilter', + } satisfies ViewFilter, + ] satisfies ViewFilter[]; + set( unsavedToUpsertViewFiltersCallbackState({ viewId: currentViewId }), - [ - ...unsavedToUpsertViewFilters, - { - ...upsertedFilter, - __typename: 'ViewFilter', - } satisfies ViewFilter, - ], + newValue, ); }, [ diff --git a/packages/twenty-front/src/modules/views/types/ViewFilter.ts b/packages/twenty-front/src/modules/views/types/ViewFilter.ts index 608e13918550..f5175cf41433 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilter.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilter.ts @@ -1,3 +1,4 @@ +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { ViewFilterOperand } from './ViewFilterOperand'; export type ViewFilter = { @@ -11,4 +12,5 @@ export type ViewFilter = { createdAt?: string; updatedAt?: string; viewId?: string; + definition?: FilterDefinition; }; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts index 025d0085d49d..0d6446de9ea4 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts @@ -4,8 +4,14 @@ export enum ViewFilterOperand { IsNot = 'isNot', LessThan = 'lessThan', GreaterThan = 'greaterThan', + IsBefore = 'isBefore', + IsAfter = 'isAfter', Contains = 'contains', DoesNotContain = 'doesNotContain', IsEmpty = 'isEmpty', IsNotEmpty = 'isNotEmpty', + IsRelative = 'isRelative', + IsInPast = 'isInPast', + IsInFuture = 'isInFuture', + IsToday = 'isToday', } diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getCombinedViewFilters.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getCombinedViewFilters.test.ts new file mode 100644 index 000000000000..95e1b37ae7f1 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getCombinedViewFilters.test.ts @@ -0,0 +1,112 @@ +// Generate test for getCombinedViewFilters + +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getCombinedViewFilters } from '../getCombinedViewFilters'; + +describe('getCombinedViewFilters', () => { + it('should return expected combined view filters when additional filters are present', () => { + const viewFilters: ViewFilter[] = [ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]; + const toUpsertViewFilters: ViewFilter[] = [ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]; + const toDeleteViewFilterIds: string[] = []; + + expect( + getCombinedViewFilters( + viewFilters, + toUpsertViewFilters, + toDeleteViewFilterIds, + ), + ).toEqual([ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]); + }); + + it('should return expected combined view filters when additional filters are not present', () => { + const viewFilters: ViewFilter[] = [ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]; + const toUpsertViewFilters: ViewFilter[] = []; + const toDeleteViewFilterIds: string[] = []; + + expect( + getCombinedViewFilters( + viewFilters, + toUpsertViewFilters, + toDeleteViewFilterIds, + ), + ).toEqual([ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]); + }); + + it('should return expected combined view filters when additional filters are present and some filters are to be deleted', () => { + const viewFilters: ViewFilter[] = [ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]; + const toUpsertViewFilters: ViewFilter[] = [ + { + __typename: 'ViewFilter', + id: 'id', + fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', + value: 'testValue', + displayValue: 'Test Display Value', + operand: ViewFilterOperand.Is, + }, + ]; + const toDeleteViewFilterIds: string[] = ['id']; + + expect( + getCombinedViewFilters( + viewFilters, + toUpsertViewFilters, + toDeleteViewFilterIds, + ), + ).toEqual([]); + }); +}); diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts index 68acdb054655..c191b799d550 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts @@ -16,6 +16,7 @@ const baseDefinition = { fieldMetadataId: '05731f68-6e7a-4903-8374-c0b6a9063482', label: 'label', iconName: 'iconName', + fieldName: 'fieldName', }; describe('mapViewSortsToSorts', () => { 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<FieldMetadata>; }) .filter(isDefined); diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts index 104ba6afdaae..773815c7ca58 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts @@ -23,7 +23,7 @@ export const mapViewFiltersToFilters = ( value: viewFilter.value, displayValue: viewFilter.displayValue, operand: viewFilter.operand, - definition: availableFilterDefinition, + definition: viewFilter.definition ?? availableFilterDefinition, }; }) .filter(isDefined); diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts new file mode 100644 index 000000000000..1b09bc91348b --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/computeVariableDateViewFilterValue.ts @@ -0,0 +1,10 @@ +import { + VariableDateViewFilterValueDirection, + VariableDateViewFilterValueUnit, +} from '@/views/utils/view-filter-value/resolveDateViewFilterValue'; + +export const computeVariableDateViewFilterValue = ( + direction: VariableDateViewFilterValueDirection, + amount: number | undefined, + unit: VariableDateViewFilterValueUnit, +) => `${direction}_${amount?.toString()}_${unit}`; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts new file mode 100644 index 000000000000..da940310505c --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveDateViewFilterValue.ts @@ -0,0 +1,190 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { + addDays, + addMonths, + addWeeks, + addYears, + endOfDay, + endOfMonth, + endOfWeek, + endOfYear, + roundToNearestMinutes, + startOfDay, + startOfMonth, + startOfWeek, + startOfYear, + subDays, + subMonths, + subWeeks, + subYears, +} from 'date-fns'; + +import { z } from 'zod'; + +const variableDateViewFilterValueDirectionSchema = z.enum([ + 'NEXT', + 'THIS', + 'PAST', +]); + +export type VariableDateViewFilterValueDirection = z.infer< + typeof variableDateViewFilterValueDirectionSchema +>; + +const variableDateViewFilterValueAmountSchema = z + .union([z.coerce.number().int().positive(), z.literal('undefined')]) + .transform((val) => (val === 'undefined' ? undefined : val)); + +export const variableDateViewFilterValueUnitSchema = z.enum([ + 'DAY', + 'WEEK', + 'MONTH', + 'YEAR', +]); + +export type VariableDateViewFilterValueUnit = z.infer< + typeof variableDateViewFilterValueUnitSchema +>; + +export const variableDateViewFilterValuePartsSchema = z + .object({ + direction: variableDateViewFilterValueDirectionSchema, + amount: variableDateViewFilterValueAmountSchema, + unit: variableDateViewFilterValueUnitSchema, + }) + .refine((data) => !(data.amount === undefined && data.direction !== 'THIS'), { + message: "Amount cannot be 'undefined' unless direction is 'THIS'", + }); + +const variableDateViewFilterValueSchema = z.string().transform((value) => { + const [direction, amount, unit] = value.split('_'); + + return variableDateViewFilterValuePartsSchema.parse({ + direction, + amount, + unit, + }); +}); + +const addUnit = ( + date: Date, + amount: number, + unit: VariableDateViewFilterValueUnit, +) => { + switch (unit) { + case 'DAY': + return addDays(date, amount); + case 'WEEK': + return addWeeks(date, amount); + case 'MONTH': + return addMonths(date, amount); + case 'YEAR': + return addYears(date, amount); + } +}; + +const subUnit = ( + date: Date, + amount: number, + unit: VariableDateViewFilterValueUnit, +) => { + switch (unit) { + case 'DAY': + return subDays(date, amount); + case 'WEEK': + return subWeeks(date, amount); + case 'MONTH': + return subMonths(date, amount); + case 'YEAR': + return subYears(date, amount); + } +}; + +const startOfUnit = (date: Date, unit: VariableDateViewFilterValueUnit) => { + switch (unit) { + case 'DAY': + return startOfDay(date); + case 'WEEK': + return startOfWeek(date); + case 'MONTH': + return startOfMonth(date); + case 'YEAR': + return startOfYear(date); + } +}; + +const endOfUnit = (date: Date, unit: VariableDateViewFilterValueUnit) => { + switch (unit) { + case 'DAY': + return endOfDay(date); + case 'WEEK': + return endOfWeek(date); + case 'MONTH': + return endOfMonth(date); + case 'YEAR': + return endOfYear(date); + } +}; + +const resolveVariableDateViewFilterValueFromRelativeDate = (relativeDate: { + direction: VariableDateViewFilterValueDirection; + amount?: number; + unit: VariableDateViewFilterValueUnit; +}) => { + const { direction, amount, unit } = relativeDate; + const now = roundToNearestMinutes(new Date()); + + switch (direction) { + case 'NEXT': + if (amount === undefined) throw new Error('Amount is required'); + return { + start: now, + end: addUnit(now, amount, unit), + ...relativeDate, + }; + case 'PAST': + if (amount === undefined) throw new Error('Amount is required'); + return { + start: subUnit(now, amount, unit), + end: now, + ...relativeDate, + }; + case 'THIS': + return { + start: startOfUnit(now, unit), + end: endOfUnit(now, unit), + ...relativeDate, + }; + } +}; + +const resolveVariableDateViewFilterValue = (value?: string | null) => { + if (!value) return null; + + const relativeDate = variableDateViewFilterValueSchema.parse(value); + return resolveVariableDateViewFilterValueFromRelativeDate(relativeDate); +}; + +export type ResolvedDateViewFilterValue<O extends ViewFilterOperand> = + O extends ViewFilterOperand.IsRelative + ? ReturnType<typeof resolveVariableDateViewFilterValue> + : Date | null; + +type PartialViewFilter<O extends ViewFilterOperand> = Pick< + ViewFilter, + 'value' +> & { operand: O }; + +export const resolveDateViewFilterValue = <O extends ViewFilterOperand>( + viewFilter: PartialViewFilter<O>, +): ResolvedDateViewFilterValue<O> => { + if (!viewFilter.value) return null; + + if (viewFilter.operand === ViewFilterOperand.IsRelative) { + return resolveVariableDateViewFilterValue( + viewFilter.value, + ) as ResolvedDateViewFilterValue<O>; + } + return new Date(viewFilter.value) as ResolvedDateViewFilterValue<O>; +}; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts new file mode 100644 index 000000000000..34afbb46ad1a --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveFilterValue.ts @@ -0,0 +1,42 @@ +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { FilterableFieldType } from '@/object-record/object-filter-dropdown/types/FilterableFieldType'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { resolveNumberViewFilterValue } from '@/views/utils/view-filter-value/resolveNumberViewFilterValue'; +import { + resolveDateViewFilterValue, + ResolvedDateViewFilterValue, +} from './resolveDateViewFilterValue'; + +type ResolvedFilterValue< + T extends FilterableFieldType, + O extends ViewFilterOperand, +> = T extends 'DATE' | 'DATE_TIME' + ? ResolvedDateViewFilterValue<O> + : T extends 'NUMBER' + ? ReturnType<typeof resolveNumberViewFilterValue> + : string; + +type PartialFilter< + T extends FilterableFieldType, + O extends ViewFilterOperand, +> = Pick<Filter, 'value'> & { + definition: { type: T }; + operand: O; +}; + +export const resolveFilterValue = < + T extends FilterableFieldType, + O extends ViewFilterOperand, +>( + filter: PartialFilter<T, O>, +) => { + switch (filter.definition.type) { + case 'DATE': + case 'DATE_TIME': + return resolveDateViewFilterValue(filter) as ResolvedFilterValue<T, O>; + case 'NUMBER': + return resolveNumberViewFilterValue(filter) as ResolvedFilterValue<T, O>; + default: + return filter.value as ResolvedFilterValue<T, O>; + } +}; diff --git a/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts new file mode 100644 index 000000000000..4e26ca096332 --- /dev/null +++ b/packages/twenty-front/src/modules/views/utils/view-filter-value/resolveNumberViewFilterValue.ts @@ -0,0 +1,7 @@ +import { ViewFilter } from '@/views/types/ViewFilter'; + +export const resolveNumberViewFilterValue = ( + viewFilter: Pick<ViewFilter, 'value'>, +) => { + return viewFilter.value === '' ? null : +viewFilter.value; +}; diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx index 1d4fad3080cd..c3ce24018b9e 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx @@ -30,6 +30,7 @@ import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState'; import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState'; import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState'; +import { useState } from 'react'; const StyledNoKanbanFieldAvailableContainer = styled.div` color: ${({ theme }) => theme.font.color.light}; @@ -41,6 +42,7 @@ const StyledNoKanbanFieldAvailableContainer = styled.div` export const ViewPickerContentCreateMode = () => { const { setViewPickerMode } = useViewPickerMode(); + const [hasManuallySelectedIcon, setHasManuallySelectedIcon] = useState(false); const [viewPickerInputName, setViewPickerInputName] = useRecoilComponentStateV2(viewPickerInputNameComponentState); @@ -87,9 +89,17 @@ export const ViewPickerContentCreateMode = () => { ViewsHotkeyScope.ListDropdown, ); + const defaultIcon = + viewPickerType === ViewType.Kanban ? 'IconLayoutKanban' : 'IconTable'; + + const selectedIcon = hasManuallySelectedIcon + ? viewPickerSelectedIcon + : defaultIcon; + const onIconChange = ({ iconKey }: { iconKey: string }) => { setViewPickerIsDirty(true); setViewPickerSelectedIcon(iconKey); + setHasManuallySelectedIcon(true); }; const handleClose = async () => { @@ -106,7 +116,7 @@ export const ViewPickerContentCreateMode = () => { <ViewPickerIconAndNameContainer> <IconPicker onChange={onIconChange} - selectedIconKey={viewPickerSelectedIcon} + selectedIconKey={selectedIcon} disableBlur onClose={() => setHotkeyScope(ViewsHotkeyScope.ListDropdown)} /> diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts index 23e082c2c73c..1021a104d66a 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts @@ -38,7 +38,7 @@ export const useGetAvailableFieldsForKanban = () => { navigate( `/settings/objects/${getObjectSlug( objectMetadataItem, - )}/new-field/step-2?fieldType=${FieldMetadataType.Select}`, + )}/new-field/configure?fieldType=${FieldMetadataType.Select}`, ); } else { navigate(`/settings/objects`); diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx new file mode 100644 index 000000000000..c2000974e5f9 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx @@ -0,0 +1,96 @@ +import { Button } from '@/ui/input/button/components/Button'; +import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; +import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; +import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { + IconPlayerPlay, + IconPlayerStop, + IconPower, + IconTrash, + isDefined, +} from 'twenty-ui'; +import { assertWorkflowWithCurrentVersionIsDefined } from '../utils/assertWorkflowWithCurrentVersionIsDefined'; + +export const RecordShowPageWorkflowHeader = ({ + workflowId, +}: { + workflowId: string; +}) => { + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); + + const isWaitingForWorkflowWithCurrentVersion = + !isDefined(workflowWithCurrentVersion) || + !isDefined(workflowWithCurrentVersion.currentVersion); + + const { activateWorkflowVersion } = useActivateWorkflowVersion(); + const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); + const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion(); + + return ( + <> + <Button + title="Test" + variant="secondary" + Icon={IconPlayerPlay} + disabled={isWaitingForWorkflowWithCurrentVersion} + onClick={() => {}} + /> + + {workflowWithCurrentVersion?.currentVersion?.status === 'DRAFT' && + workflowWithCurrentVersion.versions?.length > 1 ? ( + <Button + title="Discard Draft" + variant="secondary" + Icon={IconTrash} + disabled={isWaitingForWorkflowWithCurrentVersion} + onClick={() => { + assertWorkflowWithCurrentVersionIsDefined( + workflowWithCurrentVersion, + ); + + return deleteOneWorkflowVersion({ + workflowVersionId: workflowWithCurrentVersion.currentVersion.id, + }); + }} + /> + ) : null} + + {workflowWithCurrentVersion?.currentVersion?.status === 'DRAFT' || + workflowWithCurrentVersion?.currentVersion?.status === 'DEACTIVATED' ? ( + <Button + title="Activate" + variant="secondary" + Icon={IconPower} + disabled={isWaitingForWorkflowWithCurrentVersion} + onClick={() => { + assertWorkflowWithCurrentVersionIsDefined( + workflowWithCurrentVersion, + ); + + return activateWorkflowVersion({ + workflowVersionId: workflowWithCurrentVersion.currentVersion.id, + workflowId: workflowWithCurrentVersion.id, + }); + }} + /> + ) : workflowWithCurrentVersion?.currentVersion?.status === 'ACTIVE' ? ( + <Button + title="Deactivate" + variant="secondary" + Icon={IconPlayerStop} + disabled={isWaitingForWorkflowWithCurrentVersion} + onClick={() => { + assertWorkflowWithCurrentVersionIsDefined( + workflowWithCurrentVersion, + ); + + return deactivateWorkflowVersion( + workflowWithCurrentVersion.currentVersion.id, + ); + }} + /> + ) : null} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx new file mode 100644 index 000000000000..79cbe7c27f77 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx @@ -0,0 +1,130 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { Button } from '@/ui/input/button/components/Button'; +import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion'; +import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; +import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion'; +import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; +import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow'; +import { IconPencil, IconPlayerStop, IconPower, isDefined } from 'twenty-ui'; + +export const RecordShowPageWorkflowVersionHeader = ({ + workflowVersionId, +}: { + workflowVersionId: string; +}) => { + const workflowVersion = useWorkflowVersion(workflowVersionId); + + const workflowVersionRelatedWorkflowQuery = useFindOneRecord< + Pick<Workflow, '__typename' | 'id' | 'lastPublishedVersionId'> + >({ + objectNameSingular: CoreObjectNameSingular.Workflow, + objectRecordId: workflowVersion?.workflowId, + recordGqlFields: { + id: true, + lastPublishedVersionId: true, + }, + skip: !isDefined(workflowVersion), + }); + + // TODO: In the future, use the workflow.status property to determine if there is a draft version + const { + records: draftWorkflowVersions, + loading: loadingDraftWorkflowVersions, + } = useFindManyRecords<WorkflowVersion>({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + filter: { + workflowId: { + eq: workflowVersion?.workflow.id, + }, + status: { + eq: 'DRAFT', + }, + }, + skip: !isDefined(workflowVersion), + limit: 1, + }); + + const showUseAsDraftButton = + !loadingDraftWorkflowVersions && + isDefined(workflowVersion) && + !workflowVersionRelatedWorkflowQuery.loading && + isDefined(workflowVersionRelatedWorkflowQuery.record) && + workflowVersion.status !== 'DRAFT' && + workflowVersion.id !== + workflowVersionRelatedWorkflowQuery.record.lastPublishedVersionId; + + const hasAlreadyDraftVersion = + !loadingDraftWorkflowVersions && draftWorkflowVersions.length > 0; + + const isWaitingForWorkflowVersion = !isDefined(workflowVersion); + + const { activateWorkflowVersion } = useActivateWorkflowVersion(); + const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion(); + const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); + + const { updateOneRecord: updateOneWorkflowVersion } = + useUpdateOneRecord<WorkflowVersion>({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + return ( + <> + {showUseAsDraftButton ? ( + <Button + title={`Use as Draft${hasAlreadyDraftVersion ? ' (override)' : ''}`} + variant="secondary" + Icon={IconPencil} + disabled={isWaitingForWorkflowVersion} + onClick={async () => { + if (hasAlreadyDraftVersion) { + await updateOneWorkflowVersion({ + idToUpdate: draftWorkflowVersions[0].id, + updateOneRecordInput: { + trigger: workflowVersion.trigger, + steps: workflowVersion.steps, + }, + }); + } else { + await createNewWorkflowVersion({ + workflowId: workflowVersion.workflow.id, + name: `v${workflowVersion.workflow.versions.length + 1}`, + status: 'DRAFT', + trigger: workflowVersion.trigger, + steps: workflowVersion.steps, + }); + } + }} + /> + ) : null} + + {workflowVersion?.status === 'DRAFT' || + workflowVersion?.status === 'DEACTIVATED' ? ( + <Button + title="Activate" + variant="secondary" + Icon={IconPower} + disabled={isWaitingForWorkflowVersion} + onClick={() => { + return activateWorkflowVersion({ + workflowVersionId: workflowVersion.id, + workflowId: workflowVersion.workflowId, + }); + }} + /> + ) : workflowVersion?.status === 'ACTIVE' ? ( + <Button + title="Deactivate" + variant="secondary" + Icon={IconPlayerStop} + disabled={isWaitingForWorkflowVersion} + onClick={() => { + return deactivateWorkflowVersion(workflowVersion.id); + }} + /> + ) : null} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx index 9fb36225678c..8154fbd7472a 100644 --- a/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx +++ b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowEditStepContent.tsx @@ -1,57 +1,11 @@ -import { WorkflowEditActionForm } from '@/workflow/components/WorkflowEditActionForm'; -import { WorkflowEditTriggerForm } from '@/workflow/components/WorkflowEditTriggerForm'; -import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId'; +import { WorkflowStepDetail } from '@/workflow/components/WorkflowStepDetail'; import { useUpdateWorkflowVersionStep } from '@/workflow/hooks/useUpdateWorkflowVersionStep'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/hooks/useUpdateWorkflowVersionTrigger'; import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState'; import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; -import { findStepPositionOrThrow } from '@/workflow/utils/findStepPositionOrThrow'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; -const getStepDefinitionOrThrow = ({ - stepId, - workflow, -}: { - stepId: string; - workflow: WorkflowWithCurrentVersion; -}) => { - const currentVersion = workflow.currentVersion; - if (!isDefined(currentVersion)) { - throw new Error('Expected to find a current version'); - } - - if (stepId === TRIGGER_STEP_ID) { - if (!isDefined(currentVersion.trigger)) { - return { - type: 'trigger', - definition: undefined, - } as const; - } - - return { - type: 'trigger', - definition: currentVersion.trigger, - } as const; - } - - if (!isDefined(currentVersion.steps)) { - throw new Error( - 'Malformed workflow version: missing steps information; be sure to create at least one step before trying to edit one', - ); - } - - const selectedNodePosition = findStepPositionOrThrow({ - steps: currentVersion.steps, - stepId: stepId, - }); - - return { - type: 'action', - definition: selectedNodePosition.steps[selectedNodePosition.index], - } as const; -}; - export const RightDrawerWorkflowEditStepContent = ({ workflow, }: { @@ -70,24 +24,12 @@ export const RightDrawerWorkflowEditStepContent = ({ stepId: workflowSelectedNode, }); - const stepDefinition = getStepDefinitionOrThrow({ - stepId: workflowSelectedNode, - workflow, - }); - - if (stepDefinition.type === 'trigger') { - return ( - <WorkflowEditTriggerForm - trigger={stepDefinition.definition} - onTriggerUpdate={updateTrigger} - /> - ); - } - return ( - <WorkflowEditActionForm - action={stepDefinition.definition} + <WorkflowStepDetail + stepId={workflowSelectedNode} + workflowVersion={workflow.currentVersion} onActionUpdate={updateStep} + onTriggerUpdate={updateTrigger} /> ); }; diff --git a/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowViewStep.tsx b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowViewStep.tsx new file mode 100644 index 000000000000..f692f4649aee --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowViewStep.tsx @@ -0,0 +1,22 @@ +import { RightDrawerWorkflowViewStepContent } from '@/workflow/components/RightDrawerWorkflowViewStepContent'; +import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; +import { workflowVersionIdState } from '@/workflow/states/workflowVersionIdState'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const RightDrawerWorkflowViewStep = () => { + const workflowVersionId = useRecoilValue(workflowVersionIdState); + if (!isDefined(workflowVersionId)) { + throw new Error('Expected a workflow version id'); + } + + const workflowVersion = useWorkflowVersion(workflowVersionId); + + if (!isDefined(workflowVersion)) { + return null; + } + + return ( + <RightDrawerWorkflowViewStepContent workflowVersion={workflowVersion} /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowViewStepContent.tsx b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowViewStepContent.tsx new file mode 100644 index 000000000000..95a467fd53ac --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/RightDrawerWorkflowViewStepContent.tsx @@ -0,0 +1,26 @@ +import { WorkflowStepDetail } from '@/workflow/components/WorkflowStepDetail'; +import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const RightDrawerWorkflowViewStepContent = ({ + workflowVersion, +}: { + workflowVersion: WorkflowVersion; +}) => { + const workflowSelectedNode = useRecoilValue(workflowSelectedNodeState); + if (!isDefined(workflowSelectedNode)) { + throw new Error( + 'Expected a node to be selected. Selecting a node is mandatory to edit it.', + ); + } + + return ( + <WorkflowStepDetail + stepId={workflowSelectedNode} + workflowVersion={workflowVersion} + readonly + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/Workflow.tsx b/packages/twenty-front/src/modules/workflow/components/Workflow.tsx deleted file mode 100644 index a9e33c882136..000000000000 --- a/packages/twenty-front/src/modules/workflow/components/Workflow.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; -import { WorkflowDiagramCanvas } from '@/workflow/components/WorkflowDiagramCanvas'; -import { WorkflowEffect } from '@/workflow/components/WorkflowEffect'; -import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; -import styled from '@emotion/styled'; -import '@xyflow/react/dist/style.css'; -import { useRecoilValue } from 'recoil'; -import { isDefined } from 'twenty-ui'; - -const StyledFlowContainer = styled.div` - height: 100%; - width: 100%; - position: relative; - - /* Below we reset the default styling of Reactflow */ - .react-flow__node-input, - .react-flow__node-default, - .react-flow__node-output, - .react-flow__node-group { - padding: 0; - } - - --xy-node-border-radius: none; - --xy-node-border: none; - --xy-node-background-color: none; - --xy-node-boxshadow-hover: none; - --xy-node-boxshadow-selected: none; -`; - -export const Workflow = ({ - targetableObject, -}: { - targetableObject: ActivityTargetableObject; -}) => { - const workflowId = targetableObject.id; - - const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); - const workflowDiagram = useRecoilValue(workflowDiagramState); - - return ( - <> - <WorkflowEffect - workflowId={workflowId} - workflowWithCurrentVersion={workflowWithCurrentVersion} - /> - - <StyledFlowContainer> - {isDefined(workflowDiagram) && isDefined(workflowWithCurrentVersion) ? ( - <WorkflowDiagramCanvas - diagram={workflowDiagram} - workflowWithCurrentVersion={workflowWithCurrentVersion} - /> - ) : null} - </StyledFlowContainer> - </> - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx index 8c05d48baa09..8484a29e7eaf 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramBaseStepNode.tsx @@ -2,6 +2,7 @@ import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram'; import styled from '@emotion/styled'; import { Handle, Position } from '@xyflow/react'; import React from 'react'; +import { isDefined } from 'twenty-ui'; import { capitalize } from '~/utils/string/capitalize'; type Variant = 'placeholder'; @@ -76,16 +77,24 @@ export const StyledTargetHandle = styled(Handle)` visibility: hidden; `; +const StyledRightFloatingElementContainer = styled.div` + position: absolute; + transform: translateX(100%); + right: ${({ theme }) => theme.spacing(-2)}; +`; + export const WorkflowDiagramBaseStepNode = ({ nodeType, label, variant, Icon, + RightFloatingElement, }: { nodeType: WorkflowDiagramStepNodeData['nodeType']; label: string; variant?: Variant; Icon?: React.ReactNode; + RightFloatingElement?: React.ReactNode; }) => { return ( <StyledStepNodeContainer> @@ -101,6 +110,12 @@ export const WorkflowDiagramBaseStepNode = ({ {label} </StyledStepNodeLabel> + + {isDefined(RightFloatingElement) ? ( + <StyledRightFloatingElementContainer> + {RightFloatingElement} + </StyledRightFloatingElementContainer> + ) : null} </StyledStepNodeInnerContainer> <StyledSourceHandle type="source" position={Position.Bottom} /> diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasBase.tsx similarity index 63% rename from packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx rename to packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasBase.tsx index 83b9c0c67ad3..79273d062aa6 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvas.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasBase.tsx @@ -1,14 +1,11 @@ -import { WorkflowDiagramCanvasEffect } from '@/workflow/components/WorkflowDiagramCanvasEffect'; -import { WorkflowDiagramCreateStepNode } from '@/workflow/components/WorkflowDiagramCreateStepNode'; -import { WorkflowDiagramEmptyTrigger } from '@/workflow/components/WorkflowDiagramEmptyTrigger'; -import { WorkflowDiagramStepNode } from '@/workflow/components/WorkflowDiagramStepNode'; import { WorkflowVersionStatusTag } from '@/workflow/components/WorkflowVersionStatusTag'; import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; -import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; +import { WorkflowVersionStatus } from '@/workflow/types/Workflow'; import { WorkflowDiagram, WorkflowDiagramEdge, WorkflowDiagramNode, + WorkflowDiagramNodeType, } from '@/workflow/types/WorkflowDiagram'; import { getOrganizedDiagram } from '@/workflow/utils/getOrganizedDiagram'; import styled from '@emotion/styled'; @@ -17,14 +14,36 @@ import { applyNodeChanges, Background, EdgeChange, + FitViewOptions, NodeChange, + NodeProps, ReactFlow, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -import { useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useSetRecoilState } from 'recoil'; import { GRAY_SCALE, isDefined } from 'twenty-ui'; +const StyledResetReactflowStyles = styled.div` + height: 100%; + width: 100%; + position: relative; + + /* Below we reset the default styling of Reactflow */ + .react-flow__node-input, + .react-flow__node-default, + .react-flow__node-output, + .react-flow__node-group { + padding: 0; + } + + --xy-node-border-radius: none; + --xy-node-border: none; + --xy-node-background-color: none; + --xy-node-boxshadow-hover: none; + --xy-node-boxshadow-selected: none; +`; + const StyledStatusTagContainer = styled.div` left: 0; top: 0; @@ -32,12 +51,31 @@ const StyledStatusTagContainer = styled.div` padding: ${({ theme }) => theme.spacing(2)}; `; -export const WorkflowDiagramCanvas = ({ +const defaultFitViewOptions: FitViewOptions = { + minZoom: 1.3, + maxZoom: 1.3, +}; + +export const WorkflowDiagramCanvasBase = ({ diagram, - workflowWithCurrentVersion, + status, + nodeTypes, + children, }: { diagram: WorkflowDiagram; - workflowWithCurrentVersion: WorkflowWithCurrentVersion; + status: WorkflowVersionStatus; + nodeTypes: Partial< + Record< + WorkflowDiagramNodeType, + React.ComponentType< + NodeProps & { + data: any; + type: any; + } + > + > + >; + children?: React.ReactNode; }) => { const { nodes, edges } = useMemo( () => getOrganizedDiagram(diagram), @@ -81,29 +119,26 @@ export const WorkflowDiagramCanvas = ({ }; return ( - <> + <StyledResetReactflowStyles> <ReactFlow - nodeTypes={{ - default: WorkflowDiagramStepNode, - 'create-step': WorkflowDiagramCreateStepNode, - 'empty-trigger': WorkflowDiagramEmptyTrigger, + onInit={({ fitView }) => { + fitView(defaultFitViewOptions); }} + nodeTypes={nodeTypes} fitView nodes={nodes.map((node) => ({ ...node, draggable: false }))} edges={edges} onNodesChange={handleNodesChange} onEdgesChange={handleEdgesChange} > - <WorkflowDiagramCanvasEffect /> - <Background color={GRAY_SCALE.gray25} size={2} /> - </ReactFlow> - <StyledStatusTagContainer> - <WorkflowVersionStatusTag - versionStatus={workflowWithCurrentVersion.currentVersion.status} - /> - </StyledStatusTagContainer> - </> + {children} + + <StyledStatusTagContainer> + <WorkflowVersionStatusTag versionStatus={status} /> + </StyledStatusTagContainer> + </ReactFlow> + </StyledResetReactflowStyles> ); }; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEditable.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEditable.tsx new file mode 100644 index 000000000000..ed953f511fa3 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEditable.tsx @@ -0,0 +1,30 @@ +import { WorkflowDiagramCanvasBase } from '@/workflow/components/WorkflowDiagramCanvasBase'; +import { WorkflowDiagramCanvasEditableEffect } from '@/workflow/components/WorkflowDiagramCanvasEditableEffect'; +import { WorkflowDiagramCreateStepNode } from '@/workflow/components/WorkflowDiagramCreateStepNode'; +import { WorkflowDiagramEmptyTrigger } from '@/workflow/components/WorkflowDiagramEmptyTrigger'; +import { WorkflowDiagramStepNodeEditable } from '@/workflow/components/WorkflowDiagramStepNodeEditable'; +import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; +import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram'; + +export const WorkflowDiagramCanvasEditable = ({ + diagram, + workflowWithCurrentVersion, +}: { + diagram: WorkflowDiagram; + workflowWithCurrentVersion: WorkflowWithCurrentVersion; +}) => { + return ( + <WorkflowDiagramCanvasBase + key={workflowWithCurrentVersion.currentVersion.id} + diagram={diagram} + status={workflowWithCurrentVersion.currentVersion.status} + nodeTypes={{ + default: WorkflowDiagramStepNodeEditable, + 'create-step': WorkflowDiagramCreateStepNode, + 'empty-trigger': WorkflowDiagramEmptyTrigger, + }} + > + <WorkflowDiagramCanvasEditableEffect /> + </WorkflowDiagramCanvasBase> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEffect.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEditableEffect.tsx similarity index 62% rename from packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEffect.tsx rename to packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEditableEffect.tsx index 4f6bfde09488..ad383a527b8c 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasEditableEffect.tsx @@ -1,33 +1,20 @@ import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; import { useStartNodeCreation } from '@/workflow/hooks/useStartNodeCreation'; -import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/states/workflowDiagramTriggerNodeSelectionState'; +import { useTriggerNodeSelection } from '@/workflow/hooks/useTriggerNodeSelection'; import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState'; -import { - WorkflowDiagramEdge, - WorkflowDiagramNode, -} from '@/workflow/types/WorkflowDiagram'; -import { - OnSelectionChangeParams, - useOnSelectionChange, - useReactFlow, -} from '@xyflow/react'; -import { useCallback, useEffect } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { WorkflowDiagramNode } from '@/workflow/types/WorkflowDiagram'; +import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; +import { useCallback } from 'react'; +import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-ui'; -export const WorkflowDiagramCanvasEffect = () => { - const reactflow = useReactFlow<WorkflowDiagramNode, WorkflowDiagramEdge>(); - +export const WorkflowDiagramCanvasEditableEffect = () => { const { startNodeCreation } = useStartNodeCreation(); const { openRightDrawer, closeRightDrawer } = useRightDrawer(); const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); - const workflowDiagramTriggerNodeSelection = useRecoilValue( - workflowDiagramTriggerNodeSelectionState, - ); - const handleSelectionChange = useCallback( ({ nodes }: OnSelectionChangeParams) => { const selectedNode = nodes[0] as WorkflowDiagramNode; @@ -65,15 +52,7 @@ export const WorkflowDiagramCanvasEffect = () => { onChange: handleSelectionChange, }); - useEffect(() => { - if (!isDefined(workflowDiagramTriggerNodeSelection)) { - return; - } - - reactflow.updateNode(workflowDiagramTriggerNodeSelection, { - selected: true, - }); - }, [reactflow, workflowDiagramTriggerNodeSelection]); + useTriggerNodeSelection(); return null; }; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasReadonly.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasReadonly.tsx new file mode 100644 index 000000000000..d6c50fa9034e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasReadonly.tsx @@ -0,0 +1,28 @@ +import { WorkflowDiagramCanvasBase } from '@/workflow/components/WorkflowDiagramCanvasBase'; +import { WorkflowDiagramCanvasReadonlyEffect } from '@/workflow/components/WorkflowDiagramCanvasReadonlyEffect'; +import { WorkflowDiagramEmptyTrigger } from '@/workflow/components/WorkflowDiagramEmptyTrigger'; +import { WorkflowDiagramStepNodeReadonly } from '@/workflow/components/WorkflowDiagramStepNodeReadonly'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; +import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram'; + +export const WorkflowDiagramCanvasReadonly = ({ + diagram, + workflowVersion, +}: { + diagram: WorkflowDiagram; + workflowVersion: WorkflowVersion; +}) => { + return ( + <WorkflowDiagramCanvasBase + key={workflowVersion.id} + diagram={diagram} + status={workflowVersion.status} + nodeTypes={{ + default: WorkflowDiagramStepNodeReadonly, + 'empty-trigger': WorkflowDiagramEmptyTrigger, + }} + > + <WorkflowDiagramCanvasReadonlyEffect /> + </WorkflowDiagramCanvasBase> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasReadonlyEffect.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasReadonlyEffect.tsx new file mode 100644 index 000000000000..17cf9ae1df2e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCanvasReadonlyEffect.tsx @@ -0,0 +1,39 @@ +import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; +import { useTriggerNodeSelection } from '@/workflow/hooks/useTriggerNodeSelection'; +import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState'; +import { WorkflowDiagramNode } from '@/workflow/types/WorkflowDiagram'; +import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react'; +import { useCallback } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const WorkflowDiagramCanvasReadonlyEffect = () => { + const { openRightDrawer, closeRightDrawer } = useRightDrawer(); + const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState); + + const handleSelectionChange = useCallback( + ({ nodes }: OnSelectionChangeParams) => { + const selectedNode = nodes[0] as WorkflowDiagramNode; + const isClosingStep = isDefined(selectedNode) === false; + + if (isClosingStep) { + closeRightDrawer(); + + return; + } + + setWorkflowSelectedNode(selectedNode.id); + openRightDrawer(RightDrawerPages.WorkflowStepView); + }, + [closeRightDrawer, openRightDrawer, setWorkflowSelectedNode], + ); + + useOnSelectionChange({ + onChange: handleSelectionChange, + }); + + useTriggerNodeSelection(); + + return null; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCreateStepNode.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCreateStepNode.tsx index 27706668b4b1..2e1b1328a0b1 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCreateStepNode.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramCreateStepNode.tsx @@ -12,7 +12,7 @@ export const WorkflowDiagramCreateStepNode = () => { <> <StyledTargetHandle type="target" position={Position.Top} /> - <IconButton Icon={IconPlus} /> + <IconButton Icon={IconPlus} size="small" /> </> ); }; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEffect.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEffect.tsx new file mode 100644 index 000000000000..8b714cc7613c --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramEffect.tsx @@ -0,0 +1,66 @@ +import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; +import { + WorkflowVersion, + WorkflowWithCurrentVersion, +} from '@/workflow/types/Workflow'; + +import { addCreateStepNodes } from '@/workflow/utils/addCreateStepNodes'; +import { getWorkflowVersionDiagram } from '@/workflow/utils/getWorkflowVersionDiagram'; +import { mergeWorkflowDiagrams } from '@/workflow/utils/mergeWorkflowDiagrams'; +import { useEffect } from 'react'; +import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const WorkflowDiagramEffect = ({ + workflowWithCurrentVersion, +}: { + workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined; +}) => { + const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); + + const computeAndMergeNewWorkflowDiagram = useRecoilCallback( + ({ snapshot, set }) => { + return (currentVersion: WorkflowVersion) => { + const previousWorkflowDiagram = getSnapshotValue( + snapshot, + workflowDiagramState, + ); + + const nextWorkflowDiagram = getWorkflowVersionDiagram(currentVersion); + + let mergedWorkflowDiagram = nextWorkflowDiagram; + if (isDefined(previousWorkflowDiagram)) { + mergedWorkflowDiagram = mergeWorkflowDiagrams( + previousWorkflowDiagram, + nextWorkflowDiagram, + ); + } + + const workflowDiagramWithCreateStepNodes = addCreateStepNodes( + mergedWorkflowDiagram, + ); + + set(workflowDiagramState, workflowDiagramWithCreateStepNodes); + }; + }, + [], + ); + + useEffect(() => { + const currentVersion = workflowWithCurrentVersion?.currentVersion; + if (!isDefined(currentVersion)) { + setWorkflowDiagram(undefined); + + return; + } + + computeAndMergeNewWorkflowDiagram(currentVersion); + }, [ + computeAndMergeNewWorkflowDiagram, + setWorkflowDiagram, + workflowWithCurrentVersion?.currentVersion, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNode.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx similarity index 53% rename from packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNode.tsx rename to packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx index 0c0e6ce945ea..0fc4d8591051 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNode.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeBase.tsx @@ -1,8 +1,9 @@ import { WorkflowDiagramBaseStepNode } from '@/workflow/components/WorkflowDiagramBaseStepNode'; import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram'; +import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { IconCode, IconPlaylistAdd } from 'twenty-ui'; +import { IconCode, IconMail, IconPlaylistAdd } from 'twenty-ui'; const StyledStepNodeLabelIconContainer = styled.div` align-items: center; @@ -13,10 +14,12 @@ const StyledStepNodeLabelIconContainer = styled.div` padding: ${({ theme }) => theme.spacing(1)}; `; -export const WorkflowDiagramStepNode = ({ +export const WorkflowDiagramStepNodeBase = ({ data, + RightFloatingElement, }: { data: WorkflowDiagramStepNodeData; + RightFloatingElement?: React.ReactNode; }) => { const theme = useTheme(); @@ -32,16 +35,33 @@ export const WorkflowDiagramStepNode = ({ </StyledStepNodeLabelIconContainer> ); } + case 'condition': { + return null; + } case 'action': { - return ( - <StyledStepNodeLabelIconContainer> - <IconCode size={theme.icon.size.sm} color={theme.color.orange} /> - </StyledStepNodeLabelIconContainer> - ); + switch (data.actionType) { + case 'CODE': { + return ( + <StyledStepNodeLabelIconContainer> + <IconCode + size={theme.icon.size.sm} + color={theme.color.orange} + /> + </StyledStepNodeLabelIconContainer> + ); + } + case 'SEND_EMAIL': { + return ( + <StyledStepNodeLabelIconContainer> + <IconMail size={theme.icon.size.sm} color={theme.color.blue} /> + </StyledStepNodeLabelIconContainer> + ); + } + } } } - return null; + return assertUnreachable(data); }; return ( @@ -49,6 +69,7 @@ export const WorkflowDiagramStepNode = ({ nodeType={data.nodeType} label={data.label} Icon={renderStepIcon()} + RightFloatingElement={RightFloatingElement} /> ); }; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeEditable.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeEditable.tsx new file mode 100644 index 000000000000..cb8290fd732f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeEditable.tsx @@ -0,0 +1,45 @@ +import { FloatingIconButton } from '@/ui/input/button/components/FloatingIconButton'; +import { WorkflowDiagramStepNodeBase } from '@/workflow/components/WorkflowDiagramStepNodeBase'; +import { useDeleteOneStep } from '@/workflow/hooks/useDeleteOneStep'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram'; +import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined'; +import { useRecoilValue } from 'recoil'; +import { IconTrash } from 'twenty-ui'; + +export const WorkflowDiagramStepNodeEditable = ({ + id, + data, + selected, +}: { + id: string; + data: WorkflowDiagramStepNodeData; + selected?: boolean; +}) => { + const workflowId = useRecoilValue(workflowIdState); + + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); + assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion); + + const { deleteOneStep } = useDeleteOneStep({ + workflow: workflowWithCurrentVersion, + stepId: id, + }); + + return ( + <WorkflowDiagramStepNodeBase + data={data} + RightFloatingElement={ + selected ? ( + <FloatingIconButton + Icon={IconTrash} + onClick={() => { + return deleteOneStep(); + }} + /> + ) : undefined + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeReadonly.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeReadonly.tsx new file mode 100644 index 000000000000..054a9b7a8d89 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowDiagramStepNodeReadonly.tsx @@ -0,0 +1,10 @@ +import { WorkflowDiagramStepNodeBase } from '@/workflow/components/WorkflowDiagramStepNodeBase'; +import { WorkflowDiagramStepNodeData } from '@/workflow/types/WorkflowDiagram'; + +export const WorkflowDiagramStepNodeReadonly = ({ + data, +}: { + data: WorkflowDiagramStepNodeData; +}) => { + return <WorkflowDiagramStepNodeBase data={data} />; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionForm.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionForm.tsx deleted file mode 100644 index 015952309d11..000000000000 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionForm.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions'; -import { Select, SelectOption } from '@/ui/input/components/Select'; -import { WorkflowAction } from '@/workflow/types/Workflow'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { IconCode, isDefined } from 'twenty-ui'; - -const StyledTriggerHeader = styled.div` - background-color: ${({ theme }) => theme.background.secondary}; - border-bottom: 1px solid ${({ theme }) => theme.border.color.medium}; - display: flex; - flex-direction: column; - padding: ${({ theme }) => theme.spacing(6)}; -`; - -const StyledTriggerHeaderTitle = styled.p` - color: ${({ theme }) => theme.font.color.primary}; - font-weight: ${({ theme }) => theme.font.weight.semiBold}; - font-size: ${({ theme }) => theme.font.size.xl}; - - margin: ${({ theme }) => theme.spacing(3)} 0; -`; - -const StyledTriggerHeaderType = styled.p` - color: ${({ theme }) => theme.font.color.tertiary}; - margin: 0; -`; - -const StyledTriggerHeaderIconContainer = styled.div` - align-self: flex-start; - display: flex; - justify-content: center; - align-items: center; - background-color: ${({ theme }) => theme.background.transparent.light}; - border-radius: ${({ theme }) => theme.border.radius.xs}; - padding: ${({ theme }) => theme.spacing(1)}; -`; - -const StyledTriggerSettings = styled.div` - padding: ${({ theme }) => theme.spacing(6)}; - display: flex; - flex-direction: column; - row-gap: ${({ theme }) => theme.spacing(4)}; -`; - -export const WorkflowEditActionForm = ({ - action, - onActionUpdate, -}: { - action: WorkflowAction; - onActionUpdate: (trigger: WorkflowAction) => void; -}) => { - const theme = useTheme(); - - const { serverlessFunctions } = useGetManyServerlessFunctions(); - - const availableFunctions: Array<SelectOption<string>> = [ - { label: 'None', value: '' }, - ...serverlessFunctions - .filter((serverlessFunction) => - isDefined(serverlessFunction.latestVersion), - ) - .map((serverlessFunction) => ({ - label: serverlessFunction.name, - value: serverlessFunction.id, - })), - ]; - - return ( - <> - <StyledTriggerHeader> - <StyledTriggerHeaderIconContainer> - <IconCode color={theme.color.orange} /> - </StyledTriggerHeaderIconContainer> - - <StyledTriggerHeaderTitle> - Code - Serverless Function - </StyledTriggerHeaderTitle> - - <StyledTriggerHeaderType>Code</StyledTriggerHeaderType> - </StyledTriggerHeader> - - <StyledTriggerSettings> - <Select - dropdownId="workflow-edit-action-function" - label="Function" - fullWidth - value={action.settings.serverlessFunctionId} - options={availableFunctions} - onChange={(updatedFunction) => { - onActionUpdate({ - ...action, - settings: { - ...action.settings, - serverlessFunctionId: updatedFunction, - }, - }); - }} - /> - </StyledTriggerSettings> - </> - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormBase.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormBase.tsx new file mode 100644 index 000000000000..77a50896c015 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormBase.tsx @@ -0,0 +1,61 @@ +import styled from '@emotion/styled'; +import React from 'react'; + +const StyledTriggerHeader = styled.div` + background-color: ${({ theme }) => theme.background.secondary}; + border-bottom: 1px solid ${({ theme }) => theme.border.color.medium}; + display: flex; + flex-direction: column; + padding: ${({ theme }) => theme.spacing(6)}; +`; + +const StyledTriggerHeaderTitle = styled.p` + color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + font-size: ${({ theme }) => theme.font.size.xl}; + + margin: ${({ theme }) => theme.spacing(3)} 0; +`; + +const StyledTriggerHeaderType = styled.p` + color: ${({ theme }) => theme.font.color.tertiary}; + margin: 0; +`; + +const StyledTriggerHeaderIconContainer = styled.div` + align-self: flex-start; + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.background.transparent.light}; + border-radius: ${({ theme }) => theme.border.radius.xs}; + padding: ${({ theme }) => theme.spacing(1)}; +`; + +export const WorkflowEditActionFormBase = ({ + ActionIcon, + actionTitle, + actionType, + children, +}: { + ActionIcon: React.ReactNode; + actionTitle: string; + actionType: string; + children: React.ReactNode; +}) => { + return ( + <> + <StyledTriggerHeader> + <StyledTriggerHeaderIconContainer> + {ActionIcon} + </StyledTriggerHeaderIconContainer> + + <StyledTriggerHeaderTitle>{actionTitle}</StyledTriggerHeaderTitle> + + <StyledTriggerHeaderType>{actionType}</StyledTriggerHeaderType> + </StyledTriggerHeader> + + {children} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormSendEmail.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormSendEmail.tsx new file mode 100644 index 000000000000..be2a495202ac --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormSendEmail.tsx @@ -0,0 +1,237 @@ +import { TextArea } from '@/ui/input/components/TextArea'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { WorkflowEditActionFormBase } from '@/workflow/components/WorkflowEditActionFormBase'; +import { WorkflowSendEmailStep } from '@/workflow/types/Workflow'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import React, { useEffect } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { IconMail, IconPlus, isDefined } from 'twenty-ui'; +import { useDebouncedCallback } from 'use-debounce'; +import { Select, SelectOption } from '@/ui/input/components/Select'; +import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { ConnectedAccount } from '@/accounts/types/ConnectedAccount'; +import { useRecoilValue } from 'recoil'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth'; +import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { GMAIL_SEND_SCOPE } from '@/accounts/constants/GmailSendScope'; + +const StyledTriggerSettings = styled.div` + padding: ${({ theme }) => theme.spacing(6)}; + display: flex; + flex-direction: column; + row-gap: ${({ theme }) => theme.spacing(4)}; +`; + +type WorkflowEditActionFormSendEmailProps = + | { + action: WorkflowSendEmailStep; + readonly: true; + } + | { + action: WorkflowSendEmailStep; + readonly?: false; + onActionUpdate: (action: WorkflowSendEmailStep) => void; + }; + +type SendEmailFormData = { + connectedAccountId: string; + subject: string; + body: string; +}; + +export const WorkflowEditActionFormSendEmail = ( + props: WorkflowEditActionFormSendEmailProps, +) => { + const theme = useTheme(); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth(); + const workflowId = useRecoilValue(workflowIdState); + const redirectUrl = `/object/workflow/${workflowId}`; + + const form = useForm<SendEmailFormData>({ + defaultValues: { + connectedAccountId: '', + subject: '', + body: '', + }, + disabled: props.readonly, + }); + + const checkConnectedAccountScopes = async ( + connectedAccountId: string | null, + ) => { + const connectedAccount = accounts.find( + (account) => account.id === connectedAccountId, + ); + if (!isDefined(connectedAccount)) { + return; + } + const scopes = connectedAccount.scopes; + if ( + !isDefined(scopes) || + !isDefined(scopes.find((scope) => scope === GMAIL_SEND_SCOPE)) + ) { + await triggerGoogleApisOAuth({ + redirectLocation: redirectUrl, + loginHint: connectedAccount.handle, + }); + } + }; + + useEffect(() => { + form.setValue( + 'connectedAccountId', + props.action.settings.connectedAccountId ?? '', + ); + form.setValue('subject', props.action.settings.subject ?? ''); + form.setValue('body', props.action.settings.body ?? ''); + }, [props.action.settings, form]); + + const saveAction = useDebouncedCallback( + async (formData: SendEmailFormData, checkScopes = false) => { + if (props.readonly === true) { + return; + } + + props.onActionUpdate({ + ...props.action, + settings: { + ...props.action.settings, + connectedAccountId: formData.connectedAccountId, + subject: formData.subject, + body: formData.body, + }, + }); + + if (checkScopes === true) { + await checkConnectedAccountScopes(formData.connectedAccountId); + } + }, + 1_000, + ); + + useEffect(() => { + return () => { + saveAction.flush(); + }; + }, [saveAction]); + + const handleSave = (checkScopes = false) => + form.handleSubmit((formData: SendEmailFormData) => + saveAction(formData, checkScopes), + )(); + + const filter: { or: object[] } = { + or: [ + { + accountOwnerId: { + eq: currentWorkspaceMember?.id, + }, + }, + ], + }; + + if ( + isDefined(props.action.settings.connectedAccountId) && + props.action.settings.connectedAccountId !== '' + ) { + filter.or.push({ + id: { + eq: props.action.settings.connectedAccountId, + }, + }); + } + + const { records: accounts, loading } = useFindManyRecords<ConnectedAccount>({ + objectNameSingular: 'connectedAccount', + filter, + }); + + let emptyOption: SelectOption<string | null> = { label: 'None', value: null }; + const connectedAccountOptions: SelectOption<string | null>[] = []; + + accounts.forEach((account) => { + const selectOption = { + label: account.handle, + value: account.id, + }; + if (account.accountOwnerId === currentWorkspaceMember?.id) { + connectedAccountOptions.push(selectOption); + } else { + // This handle the case when the current connected account does not belong to the currentWorkspaceMember + // In that case, current connected account email is displayed, but cannot be selected + emptyOption = selectOption; + } + }); + + return ( + !loading && ( + <WorkflowEditActionFormBase + ActionIcon={<IconMail color={theme.color.blue} />} + actionTitle="Send Email" + actionType="Email" + > + <StyledTriggerSettings> + <Controller + name="connectedAccountId" + control={form.control} + render={({ field }) => ( + <Select + dropdownId="select-connected-account-id" + label="Account" + fullWidth + emptyOption={emptyOption} + value={field.value} + options={connectedAccountOptions} + callToActionButton={{ + onClick: () => + triggerGoogleApisOAuth({ redirectLocation: redirectUrl }), + Icon: IconPlus, + text: 'Add account', + }} + onChange={(connectedAccountId) => { + field.onChange(connectedAccountId); + handleSave(true); + }} + /> + )} + /> + <Controller + name="subject" + control={form.control} + render={({ field }) => ( + <TextInput + label="Subject" + placeholder="Enter email subject (use {{variable}} for dynamic content)" + value={field.value} + onChange={(email) => { + field.onChange(email); + handleSave(); + }} + /> + )} + /> + + <Controller + name="body" + control={form.control} + render={({ field }) => ( + <TextArea + label="Body" + placeholder="Enter email body (use {{variable}} for dynamic content)" + value={field.value} + minRows={4} + onChange={(email) => { + field.onChange(email); + handleSave(); + }} + /> + )} + /> + </StyledTriggerSettings> + </WorkflowEditActionFormBase> + ) + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormServerlessFunction.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormServerlessFunction.tsx new file mode 100644 index 000000000000..2ff3847bfd83 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowEditActionFormServerlessFunction.tsx @@ -0,0 +1,77 @@ +import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions'; +import { Select, SelectOption } from '@/ui/input/components/Select'; +import { WorkflowEditActionFormBase } from '@/workflow/components/WorkflowEditActionFormBase'; +import { WorkflowCodeStep } from '@/workflow/types/Workflow'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { IconCode, isDefined } from 'twenty-ui'; + +const StyledTriggerSettings = styled.div` + padding: ${({ theme }) => theme.spacing(6)}; + display: flex; + flex-direction: column; + row-gap: ${({ theme }) => theme.spacing(4)}; +`; + +type WorkflowEditActionFormServerlessFunctionProps = + | { + action: WorkflowCodeStep; + readonly: true; + } + | { + action: WorkflowCodeStep; + readonly?: false; + onActionUpdate: (action: WorkflowCodeStep) => void; + }; + +export const WorkflowEditActionFormServerlessFunction = ( + props: WorkflowEditActionFormServerlessFunctionProps, +) => { + const theme = useTheme(); + + const { serverlessFunctions } = useGetManyServerlessFunctions(); + + const availableFunctions: Array<SelectOption<string>> = [ + { label: 'None', value: '' }, + ...serverlessFunctions + .filter((serverlessFunction) => + isDefined(serverlessFunction.latestVersion), + ) + .map((serverlessFunction) => ({ + label: serverlessFunction.name, + value: serverlessFunction.id, + })), + ]; + + return ( + <WorkflowEditActionFormBase + ActionIcon={<IconCode color={theme.color.orange} />} + actionTitle="Code - Serverless Function" + actionType="Code" + > + <StyledTriggerSettings> + <Select + dropdownId="workflow-edit-action-function" + label="Function" + fullWidth + value={props.action.settings.serverlessFunctionId} + options={availableFunctions} + disabled={props.readonly} + onChange={(updatedFunction) => { + if (props.readonly === true) { + return; + } + + props.onActionUpdate({ + ...props.action, + settings: { + ...props.action.settings, + serverlessFunctionId: updatedFunction, + }, + }); + }} + /> + </StyledTriggerSettings> + </WorkflowEditActionFormBase> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx index 9a3960428162..0a2d8eeb8749 100644 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowEditTriggerForm.tsx @@ -45,13 +45,23 @@ const StyledTriggerSettings = styled.div` row-gap: ${({ theme }) => theme.spacing(4)}; `; +type WorkflowEditTriggerFormProps = + | { + trigger: WorkflowTrigger | undefined; + readonly: true; + onTriggerUpdate?: undefined; + } + | { + trigger: WorkflowTrigger | undefined; + readonly?: false; + onTriggerUpdate: (trigger: WorkflowTrigger) => void; + }; + export const WorkflowEditTriggerForm = ({ trigger, + readonly, onTriggerUpdate, -}: { - trigger: WorkflowTrigger | undefined; - onTriggerUpdate: (trigger: WorkflowTrigger) => void; -}) => { +}: WorkflowEditTriggerFormProps) => { const theme = useTheme(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); @@ -102,9 +112,14 @@ export const WorkflowEditTriggerForm = ({ dropdownId="workflow-edit-trigger-record-type" label="Record Type" fullWidth + disabled={readonly} value={triggerEvent?.objectType} options={availableMetadata} onChange={(updatedRecordType) => { + if (readonly === true) { + return; + } + onTriggerUpdate( isDefined(trigger) && isDefined(triggerEvent) ? { @@ -129,7 +144,12 @@ export const WorkflowEditTriggerForm = ({ fullWidth value={triggerEvent?.event} options={OBJECT_EVENT_TRIGGERS} + disabled={readonly} onChange={(updatedEvent) => { + if (readonly === true) { + return; + } + onTriggerUpdate( isDefined(trigger) && isDefined(triggerEvent) ? { diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowEffect.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowEffect.tsx deleted file mode 100644 index 6171f2d64b15..000000000000 --- a/packages/twenty-front/src/modules/workflow/components/WorkflowEffect.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; -import { workflowIdState } from '@/workflow/states/workflowIdState'; -import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; -import { addCreateStepNodes } from '@/workflow/utils/addCreateStepNodes'; -import { getWorkflowVersionDiagram } from '@/workflow/utils/getWorkflowVersionDiagram'; -import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; -import { isDefined } from 'twenty-ui'; - -type WorkflowEffectProps = { - workflowId: string; - workflowWithCurrentVersion: WorkflowWithCurrentVersion | undefined; -}; - -export const WorkflowEffect = ({ - workflowId, - workflowWithCurrentVersion, -}: WorkflowEffectProps) => { - const setWorkflowId = useSetRecoilState(workflowIdState); - const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); - - useEffect(() => { - setWorkflowId(workflowId); - }, [setWorkflowId, workflowId]); - - useEffect(() => { - const currentVersion = workflowWithCurrentVersion?.currentVersion; - if (!isDefined(currentVersion)) { - setWorkflowDiagram(undefined); - - return; - } - - const lastWorkflowDiagram = getWorkflowVersionDiagram(currentVersion); - const workflowDiagramWithCreateStepNodes = - addCreateStepNodes(lastWorkflowDiagram); - - setWorkflowDiagram(workflowDiagramWithCreateStepNodes); - }, [setWorkflowDiagram, workflowWithCurrentVersion?.currentVersion]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx new file mode 100644 index 000000000000..fa6af9f7a47f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowStepDetail.tsx @@ -0,0 +1,80 @@ +import { WorkflowEditActionFormSendEmail } from '@/workflow/components/WorkflowEditActionFormSendEmail'; +import { WorkflowEditActionFormServerlessFunction } from '@/workflow/components/WorkflowEditActionFormServerlessFunction'; +import { WorkflowEditTriggerForm } from '@/workflow/components/WorkflowEditTriggerForm'; +import { + WorkflowAction, + WorkflowTrigger, + WorkflowVersion, +} from '@/workflow/types/Workflow'; +import { assertUnreachable } from '@/workflow/utils/assertUnreachable'; +import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow'; +import { isDefined } from 'twenty-ui'; + +type WorkflowStepDetailProps = + | { + stepId: string; + workflowVersion: WorkflowVersion; + readonly: true; + onTriggerUpdate?: undefined; + onActionUpdate?: undefined; + } + | { + stepId: string; + workflowVersion: WorkflowVersion; + readonly?: false; + onTriggerUpdate: (trigger: WorkflowTrigger) => void; + onActionUpdate: (action: WorkflowAction) => void; + }; + +export const WorkflowStepDetail = ({ + stepId, + workflowVersion, + ...props +}: WorkflowStepDetailProps) => { + const stepDefinition = getStepDefinitionOrThrow({ + stepId, + workflowVersion, + }); + if (!isDefined(stepDefinition)) { + return null; + } + + switch (stepDefinition.type) { + case 'trigger': { + return ( + <WorkflowEditTriggerForm + trigger={stepDefinition.definition} + // eslint-disable-next-line react/jsx-props-no-spreading + {...props} + /> + ); + } + case 'action': { + switch (stepDefinition.definition.type) { + case 'CODE': { + return ( + <WorkflowEditActionFormServerlessFunction + action={stepDefinition.definition} + // eslint-disable-next-line react/jsx-props-no-spreading + {...props} + /> + ); + } + case 'SEND_EMAIL': { + return ( + <WorkflowEditActionFormSendEmail + action={stepDefinition.definition} + // eslint-disable-next-line react/jsx-props-no-spreading + {...props} + /> + ); + } + } + } + } + + return assertUnreachable( + stepDefinition, + `Expected the step to have an handler; ${JSON.stringify(stepDefinition)}`, + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowVersionVisualizer.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowVersionVisualizer.tsx new file mode 100644 index 000000000000..966688a7fa44 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowVersionVisualizer.tsx @@ -0,0 +1,23 @@ +import { WorkflowDiagramCanvasReadonly } from '@/workflow/components/WorkflowDiagramCanvasReadonly'; +import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; +import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; +import '@xyflow/react/dist/style.css'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const WorkflowVersionVisualizer = ({ + workflowVersionId, +}: { + workflowVersionId: string; +}) => { + const workflowVersion = useWorkflowVersion(workflowVersionId); + + const workflowDiagram = useRecoilValue(workflowDiagramState); + + return isDefined(workflowDiagram) && isDefined(workflowVersion) ? ( + <WorkflowDiagramCanvasReadonly + diagram={workflowDiagram} + workflowVersion={workflowVersion} + /> + ) : null; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowVersionVisualizerEffect.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowVersionVisualizerEffect.tsx new file mode 100644 index 000000000000..270c5c944015 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowVersionVisualizerEffect.tsx @@ -0,0 +1,36 @@ +import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; +import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; +import { workflowVersionIdState } from '@/workflow/states/workflowVersionIdState'; +import { getWorkflowVersionDiagram } from '@/workflow/utils/getWorkflowVersionDiagram'; +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const WorkflowVersionVisualizerEffect = ({ + workflowVersionId, +}: { + workflowVersionId: string; +}) => { + const workflowVersion = useWorkflowVersion(workflowVersionId); + + const setWorkflowVersionId = useSetRecoilState(workflowVersionIdState); + const setWorkflowDiagram = useSetRecoilState(workflowDiagramState); + + useEffect(() => { + setWorkflowVersionId(workflowVersionId); + }, [setWorkflowVersionId, workflowVersionId]); + + useEffect(() => { + if (!isDefined(workflowVersion)) { + setWorkflowDiagram(undefined); + + return; + } + + const nextWorkflowDiagram = getWorkflowVersionDiagram(workflowVersion); + + setWorkflowDiagram(nextWorkflowDiagram); + }, [setWorkflowDiagram, workflowVersion]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowVisualizer.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowVisualizer.tsx new file mode 100644 index 000000000000..20099a25538d --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowVisualizer.tsx @@ -0,0 +1,34 @@ +import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; +import { WorkflowDiagramCanvasEditable } from '@/workflow/components/WorkflowDiagramCanvasEditable'; +import { WorkflowDiagramEffect } from '@/workflow/components/WorkflowDiagramEffect'; +import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { workflowDiagramState } from '@/workflow/states/workflowDiagramState'; +import '@xyflow/react/dist/style.css'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const WorkflowVisualizer = ({ + targetableObject, +}: { + targetableObject: ActivityTargetableObject; +}) => { + const workflowId = targetableObject.id; + + const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId); + const workflowDiagram = useRecoilValue(workflowDiagramState); + + return ( + <> + <WorkflowDiagramEffect + workflowWithCurrentVersion={workflowWithCurrentVersion} + /> + + {isDefined(workflowDiagram) && isDefined(workflowWithCurrentVersion) ? ( + <WorkflowDiagramCanvasEditable + diagram={workflowDiagram} + workflowWithCurrentVersion={workflowWithCurrentVersion} + /> + ) : null} + </> + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowVisualizerEffect.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowVisualizerEffect.tsx new file mode 100644 index 000000000000..f3f7ed097650 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/WorkflowVisualizerEffect.tsx @@ -0,0 +1,17 @@ +import { workflowIdState } from '@/workflow/states/workflowIdState'; +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; + +export const WorkflowVisualizerEffect = ({ + workflowId, +}: { + workflowId: string; +}) => { + const setWorkflowId = useSetRecoilState(workflowIdState); + + useEffect(() => { + setWorkflowId(workflowId); + }, [setWorkflowId, workflowId]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/workflow/components/__stories__/RecordShowPageWorkflowHeader.stories.tsx b/packages/twenty-front/src/modules/workflow/components/__stories__/RecordShowPageWorkflowHeader.stories.tsx new file mode 100644 index 000000000000..663924fb7cce --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/components/__stories__/RecordShowPageWorkflowHeader.stories.tsx @@ -0,0 +1,666 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { graphql, HttpResponse } from 'msw'; +import { ComponentDecorator } from 'twenty-ui'; + +import { RecordShowPageWorkflowHeader } from '@/workflow/components/RecordShowPageWorkflowHeader'; +import { expect, within } from '@storybook/test'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; +import { graphqlMocks } from '~/testing/graphqlMocks'; + +const meta: Meta<typeof RecordShowPageWorkflowHeader> = { + title: 'Modules/Workflow/RecordShowPageWorkflowHeader', + component: RecordShowPageWorkflowHeader, + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], + parameters: { + container: { width: 728 }, + }, +}; + +export default meta; +type Story = StoryObj<typeof RecordShowPageWorkflowHeader>; + +const blankInitialVersionWorkflowId = '78fd5184-08f4-47b7-bb60-adb541608f65'; + +export const BlankInitialVersion: Story = { + args: { + workflowId: blankInitialVersionWorkflowId, + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindManyWorkflows', () => { + return HttpResponse.json({ + data: { + workflows: { + __typename: 'WorkflowConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + endCursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + }, + edges: [ + { + __typename: 'WorkflowEdge', + cursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + node: { + __typename: 'Workflow', + id: blankInitialVersionWorkflowId, + }, + }, + ], + }, + }, + }); + }), + graphql.query('FindOneWorkflow', () => { + return HttpResponse.json({ + data: { + workflow: { + __typename: 'Workflow', + id: blankInitialVersionWorkflowId, + name: '1231 qqerrt', + statuses: null, + lastPublishedVersionId: '', + deletedAt: null, + updatedAt: '2024-09-19T10:10:04.505Z', + position: 0, + createdAt: '2024-09-19T10:10:04.505Z', + favorites: { + __typename: 'FavoriteConnection', + edges: [], + }, + eventListeners: { + __typename: 'WorkflowEventListenerConnection', + edges: [], + }, + runs: { + __typename: 'WorkflowRunConnection', + edges: [], + }, + versions: { + __typename: 'WorkflowVersionConnection', + edges: [ + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:04.725Z', + status: 'DRAFT', + name: 'v1', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f0', + trigger: null, + deletedAt: null, + workflowId: blankInitialVersionWorkflowId, + }, + }, + ], + }, + }, + }, + }); + }), + graphql.query('FindManyWorkflowVersions', () => { + return HttpResponse.json({ + data: { + workflowVersions: { + __typename: 'WorkflowVersionConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + endCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + }, + edges: [ + { + __typename: 'WorkflowVersionEdge', + cursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:04.725Z', + status: 'DRAFT', + name: 'v1', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f0', + trigger: null, + deletedAt: null, + workflowId: blankInitialVersionWorkflowId, + }, + }, + ], + }, + }, + }); + }), + ...graphqlMocks.handlers, + ], + }, + }, + play: async () => { + const canvas = within(document.body); + + expect(await canvas.findByText('Test')).toBeVisible(); + expect(await canvas.findByText('Activate')).toBeVisible(); + expect(canvas.queryByText('Discard Draft')).not.toBeInTheDocument(); + }, +}; + +const activeVersionWorkflowId = 'ca177fb1-7780-4911-8b1f-ef0a245fbd61'; + +export const ActiveVersion: Story = { + args: { + workflowId: activeVersionWorkflowId, + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindManyWorkflows', () => { + return HttpResponse.json({ + data: { + workflows: { + __typename: 'WorkflowConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJwb3NpdGlvbiI6LTEsImlkIjoiN2JlM2E4MmMtNDRiNy00MTUwLWEyZTgtNDA4ODcxNDZmNGQ0In0=', + endCursor: + 'eyJwb3NpdGlvbiI6LTEsImlkIjoiN2JlM2E4MmMtNDRiNy00MTUwLWEyZTgtNDA4ODcxNDZmNGQ0In0=', + }, + edges: [ + { + __typename: 'WorkflowEdge', + cursor: + 'eyJwb3NpdGlvbiI6LTEsImlkIjoiN2JlM2E4MmMtNDRiNy00MTUwLWEyZTgtNDA4ODcxNDZmNGQ0In0=', + node: { + __typename: 'Workflow', + id: activeVersionWorkflowId, + }, + }, + ], + }, + }, + }); + }), + graphql.query('FindOneWorkflow', () => { + return HttpResponse.json({ + data: { + workflow: { + __typename: 'Workflow', + name: 'test qqqq', + lastPublishedVersionId: 'b57e577a-ae55-4de2-ba08-fe361dcc1a57', + id: activeVersionWorkflowId, + deletedAt: null, + statuses: null, + createdAt: '2024-09-20T10:18:59.977Z', + updatedAt: '2024-09-20T16:59:37.212Z', + position: -1, + runs: { + __typename: 'WorkflowRunConnection', + edges: [], + }, + favorites: { + __typename: 'FavoriteConnection', + edges: [], + }, + eventListeners: { + __typename: 'WorkflowEventListenerConnection', + edges: [], + }, + versions: { + __typename: 'WorkflowVersionConnection', + edges: [ + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-20T16:59:37.212Z', + status: 'ARCHIVED', + deletedAt: null, + steps: [ + { + id: '93c41c1d-eff3-4c91-ac61-f56cc1a0df8a', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + ], + workflowId: activeVersionWorkflowId, + trigger: { + type: 'DATABASE_EVENT', + settings: { + eventName: 'note.created', + }, + }, + name: 'v1', + id: '394cd0b5-bd48-41d7-a110-a92cafaf171d', + createdAt: '2024-09-20T10:19:00.141Z', + }, + }, + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-20T17:01:15.637Z', + status: 'DRAFT', + deletedAt: null, + steps: [ + { + id: '93c41c1d-eff3-4c91-ac61-f56cc1a0df8a', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + { + id: '4177d57d-35dc-4eb1-a467-07e25cb31da0', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + { + id: '0cc392d9-5f28-4d92-90a0-08180f264e68', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + ], + workflowId: activeVersionWorkflowId, + trigger: { + type: 'DATABASE_EVENT', + settings: { + eventName: 'note.created', + }, + }, + name: 'v3', + id: '5eae34ef-9d62-4a9e-b827-3eb927481728', + createdAt: '2024-09-20T17:01:15.637Z', + }, + }, + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-20T17:00:16.097Z', + status: 'ACTIVE', + deletedAt: null, + steps: [ + { + id: '93c41c1d-eff3-4c91-ac61-f56cc1a0df8a', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + { + id: '4177d57d-35dc-4eb1-a467-07e25cb31da0', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + ], + workflowId: activeVersionWorkflowId, + trigger: { + type: 'DATABASE_EVENT', + settings: { + eventName: 'note.created', + }, + }, + name: 'v2', + id: 'b57e577a-ae55-4de2-ba08-fe361dcc1a57', + createdAt: '2024-09-20T16:59:35.755Z', + }, + }, + ], + }, + }, + }, + }); + }), + graphql.query('FindManyWorkflowVersions', () => { + return HttpResponse.json({ + data: { + workflowVersions: { + __typename: 'WorkflowVersionConnection', + totalCount: 3, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: true, + hasPreviousPage: false, + startCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTIwVDE3OjAxOjE1LjYzN1oiLCJpZCI6IjVlYWUzNGVmLTlkNjItNGE5ZS1iODI3LTNlYjkyNzQ4MTcyOCJ9', + endCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTIwVDE3OjAxOjE1LjYzN1oiLCJpZCI6IjVlYWUzNGVmLTlkNjItNGE5ZS1iODI3LTNlYjkyNzQ4MTcyOCJ9', + }, + edges: [ + { + __typename: 'WorkflowVersionEdge', + cursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTIwVDE3OjAxOjE1LjYzN1oiLCJpZCI6IjVlYWUzNGVmLTlkNjItNGE5ZS1iODI3LTNlYjkyNzQ4MTcyOCJ9', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-20T17:01:15.637Z', + status: 'ACTIVE', + deletedAt: null, + steps: [ + { + id: '93c41c1d-eff3-4c91-ac61-f56cc1a0df8a', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + { + id: '4177d57d-35dc-4eb1-a467-07e25cb31da0', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + { + id: '0cc392d9-5f28-4d92-90a0-08180f264e68', + name: 'Code', + type: 'CODE', + valid: false, + settings: { + errorHandlingOptions: { + retryOnFailure: { + value: false, + }, + continueOnFailure: { + value: false, + }, + }, + serverlessFunctionId: '', + }, + }, + ], + workflowId: activeVersionWorkflowId, + trigger: { + type: 'DATABASE_EVENT', + settings: { + eventName: 'note.created', + }, + }, + name: 'v3', + id: '5eae34ef-9d62-4a9e-b827-3eb927481728', + createdAt: '2024-09-20T17:01:15.637Z', + }, + }, + ], + }, + }, + }); + }), + ...graphqlMocks.handlers, + ], + }, + }, + play: async () => { + const canvas = within(document.body); + + expect(await canvas.findByText('Test')).toBeVisible(); + expect(await canvas.findByText('Deactivate')).toBeVisible(); + }, +}; + +const draftVersionWithPreviousActiveVersionWorkflowId = + '89c00f14-4ebd-4675-a098-cdf59eee372b'; + +export const DraftVersionWithPreviousActiveVersion: Story = { + args: { + workflowId: draftVersionWithPreviousActiveVersionWorkflowId, + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindManyWorkflows', () => { + return HttpResponse.json({ + data: { + workflows: { + __typename: 'WorkflowConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + endCursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + }, + edges: [ + { + __typename: 'WorkflowEdge', + cursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + node: { + __typename: 'Workflow', + id: draftVersionWithPreviousActiveVersionWorkflowId, + }, + }, + ], + }, + }, + }); + }), + graphql.query('FindOneWorkflow', () => { + return HttpResponse.json({ + data: { + workflow: { + __typename: 'Workflow', + id: draftVersionWithPreviousActiveVersionWorkflowId, + name: '1231 qqerrt', + statuses: null, + lastPublishedVersionId: '', + deletedAt: null, + updatedAt: '2024-09-19T10:10:04.505Z', + position: 0, + createdAt: '2024-09-19T10:10:04.505Z', + favorites: { + __typename: 'FavoriteConnection', + edges: [], + }, + eventListeners: { + __typename: 'WorkflowEventListenerConnection', + edges: [], + }, + runs: { + __typename: 'WorkflowRunConnection', + edges: [], + }, + versions: { + __typename: 'WorkflowVersionConnection', + edges: [ + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:04.725Z', + status: 'ACTIVE', + name: 'v1', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f0', + trigger: null, + deletedAt: null, + workflowId: + draftVersionWithPreviousActiveVersionWorkflowId, + }, + }, + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:05.725Z', + status: 'DRAFT', + name: 'v2', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f1', + trigger: null, + deletedAt: null, + workflowId: + draftVersionWithPreviousActiveVersionWorkflowId, + }, + }, + ], + }, + }, + }, + }); + }), + graphql.query('FindManyWorkflowVersions', () => { + return HttpResponse.json({ + data: { + workflowVersions: { + __typename: 'WorkflowVersionConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + endCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + }, + edges: [ + { + __typename: 'WorkflowVersionEdge', + cursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:05.725Z', + status: 'DRAFT', + name: 'v2', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f1', + trigger: null, + deletedAt: null, + workflowId: + draftVersionWithPreviousActiveVersionWorkflowId, + }, + }, + ], + }, + }, + }); + }), + ...graphqlMocks.handlers, + ], + }, + }, + play: async () => { + const canvas = within(document.body); + + expect(await canvas.findByText('Test')).toBeVisible(); + expect(await canvas.findByText('Discard Draft')).toBeVisible(); + }, +}; diff --git a/packages/twenty-front/src/modules/workflow/constants/Actions.ts b/packages/twenty-front/src/modules/workflow/constants/Actions.ts index 53c988420e59..b3415d391f98 100644 --- a/packages/twenty-front/src/modules/workflow/constants/Actions.ts +++ b/packages/twenty-front/src/modules/workflow/constants/Actions.ts @@ -11,4 +11,9 @@ export const ACTIONS: Array<{ type: 'CODE', icon: IconSettingsAutomation, }, + { + label: 'Send Email', + type: 'SEND_EMAIL', + icon: IconSettingsAutomation, + }, ]; diff --git a/packages/twenty-front/src/modules/workflow/graphql/activateWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/graphql/activateWorkflowVersion.ts new file mode 100644 index 000000000000..d81597546dea --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/graphql/activateWorkflowVersion.ts @@ -0,0 +1,7 @@ +import { gql } from '@apollo/client'; + +export const ACTIVATE_WORKFLOW_VERSION = gql` + mutation ActivateWorkflowVersion($workflowVersionId: String!) { + activateWorkflowVersion(workflowVersionId: $workflowVersionId) + } +`; diff --git a/packages/twenty-front/src/modules/workflow/graphql/deactivateWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/graphql/deactivateWorkflowVersion.ts new file mode 100644 index 000000000000..8cd8950d6d9f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/graphql/deactivateWorkflowVersion.ts @@ -0,0 +1,7 @@ +import { gql } from '@apollo/client'; + +export const DEACTIVATE_WORKFLOW_VERSION = gql` + mutation DeactivateWorkflowVersion($workflowVersionId: String!) { + deactivateWorkflowVersion(workflowVersionId: $workflowVersionId) + } +`; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.tsx new file mode 100644 index 000000000000..23bdf15a4138 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.tsx @@ -0,0 +1,77 @@ +import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; +import { ApolloClient, useApolloClient, useMutation } from '@apollo/client'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; +import { ACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/activateWorkflowVersion'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; +import { + ActivateWorkflowVersionMutation, + ActivateWorkflowVersionMutationVariables, +} from '~/generated/graphql'; + +export const useActivateWorkflowVersion = () => { + const apolloMetadataClient = useApolloMetadataClient(); + const apolloClient = useApolloClient(); + const [mutate] = useMutation< + ActivateWorkflowVersionMutation, + ActivateWorkflowVersionMutationVariables + >(ACTIVATE_WORKFLOW_VERSION, { + client: apolloMetadataClient ?? ({} as ApolloClient<any>), + }); + + const { objectMetadataItem: objectMetadataItemWorkflowVersion } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + const activateWorkflowVersion = async ({ + workflowVersionId, + workflowId, + }: { + workflowVersionId: string; + workflowId: string; + }) => { + await mutate({ + variables: { + workflowVersionId, + }, + update: () => { + modifyRecordFromCache({ + cache: apolloClient.cache, + recordId: workflowVersionId, + objectMetadataItem: objectMetadataItemWorkflowVersion, + fieldModifiers: { + status: () => 'ACTIVE', + }, + }); + + const cacheSnapshot = apolloClient.cache.extract(); + const allWorkflowVersions: Array<WorkflowVersion> = Object.values( + cacheSnapshot, + ).filter( + (item) => + item.__typename === 'WorkflowVersion' && + item.workflowId === workflowId, + ); + + for (const workflowVersion of allWorkflowVersions) { + apolloClient.cache.modify({ + id: apolloClient.cache.identify(workflowVersion), + fields: { + status: () => { + return workflowVersion.id !== workflowVersionId && + workflowVersion.status === 'ACTIVE' + ? 'ARCHIVED' + : workflowVersion.status; + }, + }, + }); + } + }, + }); + }; + + return { activateWorkflowVersion }; +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useCreateNewWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useCreateNewWorkflowVersion.tsx new file mode 100644 index 000000000000..5426c8a1bb9a --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useCreateNewWorkflowVersion.tsx @@ -0,0 +1,23 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; + +export const useCreateNewWorkflowVersion = () => { + const { createOneRecord: createOneWorkflowVersion } = + useCreateOneRecord<WorkflowVersion>({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + const createNewWorkflowVersion = ( + workflowVersionData: Pick< + WorkflowVersion, + 'workflowId' | 'name' | 'status' | 'trigger' | 'steps' + >, + ) => { + return createOneWorkflowVersion(workflowVersionData); + }; + + return { + createNewWorkflowVersion, + }; +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useCreateStep.tsx b/packages/twenty-front/src/modules/workflow/hooks/useCreateStep.tsx index e694e535ac44..e439edc2d883 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useCreateStep.tsx +++ b/packages/twenty-front/src/modules/workflow/hooks/useCreateStep.tsx @@ -1,5 +1,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; import { workflowCreateStepFromParentStepIdState } from '@/workflow/states/workflowCreateStepFromParentStepIdState'; import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/states/workflowDiagramTriggerNodeSelectionState'; import { @@ -31,7 +32,9 @@ export const useCreateStep = ({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, }); - const insertNodeAndSave = ({ + const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); + + const insertNodeAndSave = async ({ parentNodeId, nodeToAdd, }: { @@ -43,15 +46,29 @@ export const useCreateStep = ({ throw new Error("Can't add a node when there is no current version."); } - return updateOneWorkflowVersion({ - idToUpdate: currentVersion.id, - updateOneRecordInput: { - steps: insertStep({ - steps: currentVersion.steps ?? [], - parentStepId: parentNodeId, - stepToAdd: nodeToAdd, - }), - }, + const updatedSteps = insertStep({ + steps: currentVersion.steps ?? [], + parentStepId: parentNodeId, + stepToAdd: nodeToAdd, + }); + + if (workflow.currentVersion.status === 'DRAFT') { + await updateOneWorkflowVersion({ + idToUpdate: currentVersion.id, + updateOneRecordInput: { + steps: updatedSteps, + }, + }); + + return; + } + + await createNewWorkflowVersion({ + workflowId: workflow.id, + name: `v${workflow.versions.length + 1}`, + status: 'DRAFT', + trigger: workflow.currentVersion.trigger, + steps: updatedSteps, }); }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.tsx new file mode 100644 index 000000000000..ec71956e69dc --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.tsx @@ -0,0 +1,47 @@ +import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; +import { ApolloClient, useApolloClient, useMutation } from '@apollo/client'; + +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; +import { DEACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/deactivateWorkflowVersion'; +import { + ActivateWorkflowVersionMutation, + ActivateWorkflowVersionMutationVariables, +} from '~/generated/graphql'; + +export const useDeactivateWorkflowVersion = () => { + const apolloMetadataClient = useApolloMetadataClient(); + const apolloClient = useApolloClient(); + const [mutate] = useMutation< + ActivateWorkflowVersionMutation, + ActivateWorkflowVersionMutationVariables + >(DEACTIVATE_WORKFLOW_VERSION, { + client: apolloMetadataClient ?? ({} as ApolloClient<any>), + }); + + const { objectMetadataItem: objectMetadataItemWorkflowVersion } = + useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + const deactivateWorkflowVersion = async (workflowVersionId: string) => { + await mutate({ + variables: { + workflowVersionId, + }, + update: () => { + modifyRecordFromCache({ + cache: apolloClient.cache, + recordId: workflowVersionId, + objectMetadataItem: objectMetadataItemWorkflowVersion, + fieldModifiers: { + status: () => 'DEACTIVATED', + }, + }); + }, + }); + }; + + return { deactivateWorkflowVersion }; +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useDeleteOneStep.tsx b/packages/twenty-front/src/modules/workflow/hooks/useDeleteOneStep.tsx new file mode 100644 index 000000000000..159a12958be0 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useDeleteOneStep.tsx @@ -0,0 +1,76 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId'; +import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; +import { + WorkflowVersion, + WorkflowWithCurrentVersion, +} from '@/workflow/types/Workflow'; +import { removeStep } from '@/workflow/utils/removeStep'; + +export const useDeleteOneStep = ({ + stepId, + workflow, +}: { + stepId: string; + workflow: WorkflowWithCurrentVersion; +}) => { + const { updateOneRecord: updateOneWorkflowVersion } = + useUpdateOneRecord<WorkflowVersion>({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); + + const deleteOneStep = async () => { + if (workflow.currentVersion.status !== 'DRAFT') { + const newVersionName = `v${workflow.versions.length + 1}`; + + if (stepId === TRIGGER_STEP_ID) { + await createNewWorkflowVersion({ + workflowId: workflow.id, + name: newVersionName, + status: 'DRAFT', + trigger: null, + steps: workflow.currentVersion.steps, + }); + } else { + await createNewWorkflowVersion({ + workflowId: workflow.id, + name: newVersionName, + status: 'DRAFT', + trigger: workflow.currentVersion.trigger, + steps: removeStep({ + steps: workflow.currentVersion.steps ?? [], + stepId, + }), + }); + } + + return; + } + + if (stepId === TRIGGER_STEP_ID) { + await updateOneWorkflowVersion({ + idToUpdate: workflow.currentVersion.id, + updateOneRecordInput: { + trigger: null, + }, + }); + } else { + await updateOneWorkflowVersion({ + idToUpdate: workflow.currentVersion.id, + updateOneRecordInput: { + steps: removeStep({ + steps: workflow.currentVersion.steps ?? [], + stepId, + }), + }, + }); + } + }; + + return { + deleteOneStep, + }; +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useDeleteOneWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useDeleteOneWorkflowVersion.tsx new file mode 100644 index 000000000000..bd3cf0305664 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useDeleteOneWorkflowVersion.tsx @@ -0,0 +1,18 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; + +export const useDeleteOneWorkflowVersion = () => { + const { deleteOneRecord } = useDeleteOneRecord({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + const deleteOneWorkflowVersion = async ({ + workflowVersionId, + }: { + workflowVersionId: string; + }) => { + await deleteOneRecord(workflowVersionId); + }; + + return { deleteOneWorkflowVersion }; +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useTriggerNodeSelection.tsx b/packages/twenty-front/src/modules/workflow/hooks/useTriggerNodeSelection.tsx new file mode 100644 index 000000000000..3a2a80a27ce6 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useTriggerNodeSelection.tsx @@ -0,0 +1,36 @@ +import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/states/workflowDiagramTriggerNodeSelectionState'; +import { + WorkflowDiagramEdge, + WorkflowDiagramNode, +} from '@/workflow/types/WorkflowDiagram'; +import { useReactFlow } from '@xyflow/react'; +import { useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-ui'; + +export const useTriggerNodeSelection = () => { + const reactflow = useReactFlow<WorkflowDiagramNode, WorkflowDiagramEdge>(); + + const workflowDiagramTriggerNodeSelection = useRecoilValue( + workflowDiagramTriggerNodeSelectionState, + ); + const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState( + workflowDiagramTriggerNodeSelectionState, + ); + + useEffect(() => { + if (!isDefined(workflowDiagramTriggerNodeSelection)) { + return; + } + + reactflow.updateNode(workflowDiagramTriggerNodeSelection, { + selected: true, + }); + + setWorkflowDiagramTriggerNodeSelection(undefined); + }, [ + reactflow, + setWorkflowDiagramTriggerNodeSelection, + workflowDiagramTriggerNodeSelection, + ]); +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionStep.tsx b/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionStep.tsx index afae15459b82..a3696eb4114f 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionStep.tsx +++ b/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionStep.tsx @@ -1,5 +1,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; import { WorkflowStep, WorkflowVersion, @@ -20,20 +21,36 @@ export const useUpdateWorkflowVersionStep = ({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, }); - const updateStep = async (updatedStep: WorkflowStep) => { + const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); + + const updateStep = async <T extends WorkflowStep>(updatedStep: T) => { if (!isDefined(workflow.currentVersion)) { throw new Error('Can not update an undefined workflow version.'); } - await updateOneWorkflowVersion({ - idToUpdate: workflow.currentVersion.id, - updateOneRecordInput: { - steps: replaceStep({ - steps: workflow.currentVersion.steps ?? [], - stepId, - stepToReplace: updatedStep, - }), - }, + const updatedSteps = replaceStep({ + steps: workflow.currentVersion.steps ?? [], + stepId, + stepToReplace: updatedStep, + }); + + if (workflow.currentVersion.status === 'DRAFT') { + await updateOneWorkflowVersion({ + idToUpdate: workflow.currentVersion.id, + updateOneRecordInput: { + steps: updatedSteps, + }, + }); + + return; + } + + await createNewWorkflowVersion({ + workflowId: workflow.id, + name: `v${workflow.versions.length + 1}`, + status: 'DRAFT', + trigger: workflow.currentVersion.trigger, + steps: updatedSteps, }); }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionTrigger.tsx b/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionTrigger.tsx index 0ceeb61da77d..e6703dae839e 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionTrigger.tsx +++ b/packages/twenty-front/src/modules/workflow/hooks/useUpdateWorkflowVersionTrigger.tsx @@ -1,5 +1,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; import { WorkflowTrigger, WorkflowVersion, @@ -17,16 +18,30 @@ export const useUpdateWorkflowVersionTrigger = ({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, }); + const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); + const updateTrigger = async (updatedTrigger: WorkflowTrigger) => { if (!isDefined(workflow.currentVersion)) { throw new Error('Can not update an undefined workflow version.'); } - await updateOneWorkflowVersion({ - idToUpdate: workflow.currentVersion.id, - updateOneRecordInput: { - trigger: updatedTrigger, - }, + if (workflow.currentVersion.status === 'DRAFT') { + await updateOneWorkflowVersion({ + idToUpdate: workflow.currentVersion.id, + updateOneRecordInput: { + trigger: updatedTrigger, + }, + }); + + return; + } + + await createNewWorkflowVersion({ + workflowId: workflow.id, + name: `v${workflow.versions.length + 1}`, + status: 'DRAFT', + trigger: updatedTrigger, + steps: workflow.currentVersion.steps, }); }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowVersion.tsx new file mode 100644 index 000000000000..d16da69838d3 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowVersion.tsx @@ -0,0 +1,36 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow'; + +export const useWorkflowVersion = (workflowVersionId: string) => { + const { record: workflowVersion } = useFindOneRecord< + WorkflowVersion & { + workflow: Omit<Workflow, 'versions'> & { + versions: Array<{ __typename: string }>; + }; + } + >({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + objectRecordId: workflowVersionId, + recordGqlFields: { + id: true, + name: true, + createdAt: true, + updatedAt: true, + workflowId: true, + trigger: true, + steps: true, + status: true, + workflow: { + id: true, + name: true, + statuses: true, + versions: { + totalCount: true, + }, + }, + }, + }); + + return workflowVersion; +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.tsx index fca576a53690..d24da7d10f95 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.tsx +++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.tsx @@ -19,6 +19,9 @@ export const useWorkflowWithCurrentVersion = ( id: true, name: true, statuses: true, + versions: { + totalCount: true, + }, }, skip: !isDefined(workflowId), }); @@ -38,7 +41,6 @@ export const useWorkflowWithCurrentVersion = ( createdAt: 'DescNullsLast', }, ], - limit: 1, skip: !isDefined(workflowId), }); @@ -47,7 +49,16 @@ export const useWorkflowWithCurrentVersion = ( return undefined; } - const currentVersion = mostRecentWorkflowVersions?.[0]; + const draftVersion = mostRecentWorkflowVersions.find( + (workflowVersion) => workflowVersion.status === 'DRAFT', + ); + const latestVersion = mostRecentWorkflowVersions[0]; + + const currentVersion = draftVersion ?? latestVersion; + + if (!isDefined(currentVersion)) { + return undefined; + } return { ...workflow, diff --git a/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts new file mode 100644 index 000000000000..2894697965dd --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const workflowVersionIdState = createState<string | undefined>({ + key: 'workflowVersionIdState', + defaultValue: undefined, +}); diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index 45500e219e7f..0ed8422846b9 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -1,4 +1,4 @@ -type WorkflowBaseSettingsType = { +type BaseWorkflowStepSettings = { errorHandlingOptions: { retryOnFailure: { value: boolean; @@ -9,27 +9,38 @@ type WorkflowBaseSettingsType = { }; }; -export type WorkflowCodeSettingsType = WorkflowBaseSettingsType & { +export type WorkflowCodeStepSettings = BaseWorkflowStepSettings & { serverlessFunctionId: string; }; -export type WorkflowActionType = 'CODE'; +export type WorkflowSendEmailStepSettings = BaseWorkflowStepSettings & { + connectedAccountId: string; + subject?: string; + body?: string; +}; -type CommonWorkflowAction = { +type BaseWorkflowStep = { id: string; name: string; valid: boolean; }; -type WorkflowCodeAction = CommonWorkflowAction & { +export type WorkflowCodeStep = BaseWorkflowStep & { type: 'CODE'; - settings: WorkflowCodeSettingsType; + settings: WorkflowCodeStepSettings; +}; + +export type WorkflowSendEmailStep = BaseWorkflowStep & { + type: 'SEND_EMAIL'; + settings: WorkflowSendEmailStepSettings; }; -export type WorkflowAction = WorkflowCodeAction; +export type WorkflowAction = WorkflowCodeStep | WorkflowSendEmailStep; export type WorkflowStep = WorkflowAction; +export type WorkflowActionType = WorkflowAction['type']; + export type WorkflowStepType = WorkflowStep['type']; export type WorkflowTriggerType = 'DATABASE_EVENT'; diff --git a/packages/twenty-front/src/modules/workflow/types/WorkflowDiagram.ts b/packages/twenty-front/src/modules/workflow/types/WorkflowDiagram.ts index 237daab24b93..fdad5d19f785 100644 --- a/packages/twenty-front/src/modules/workflow/types/WorkflowDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/types/WorkflowDiagram.ts @@ -1,3 +1,4 @@ +import { WorkflowActionType } from '@/workflow/types/Workflow'; import { Edge, Node } from '@xyflow/react'; export type WorkflowDiagramNode = Node<WorkflowDiagramNodeData>; @@ -8,10 +9,16 @@ export type WorkflowDiagram = { edges: Array<WorkflowDiagramEdge>; }; -export type WorkflowDiagramStepNodeData = { - nodeType: 'trigger' | 'condition' | 'action'; - label: string; -}; +export type WorkflowDiagramStepNodeData = + | { + nodeType: 'trigger' | 'condition'; + label: string; + } + | { + nodeType: 'action'; + actionType: WorkflowActionType; + label: string; + }; export type WorkflowDiagramCreateStepNodeData = { nodeType: 'create-step'; @@ -21,3 +28,8 @@ export type WorkflowDiagramCreateStepNodeData = { export type WorkflowDiagramNodeData = | WorkflowDiagramStepNodeData | WorkflowDiagramCreateStepNodeData; + +export type WorkflowDiagramNodeType = + | 'default' + | 'empty-trigger' + | 'create-step'; diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/generateWorkflowDiagram.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/generateWorkflowDiagram.test.ts index ee4643fcb20b..ebf4f3c21020 100644 --- a/packages/twenty-front/src/modules/workflow/utils/__tests__/generateWorkflowDiagram.test.ts +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/generateWorkflowDiagram.test.ts @@ -72,6 +72,7 @@ describe('generateWorkflowDiagram', () => { for (const [index, step] of steps.entries()) { expect(stepNodes[index].data).toEqual({ nodeType: 'action', + actionType: 'CODE', label: step.name, }); } diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/mergeWorkflowDiagrams.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/mergeWorkflowDiagrams.test.ts new file mode 100644 index 000000000000..38e3baabdca1 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/mergeWorkflowDiagrams.test.ts @@ -0,0 +1,72 @@ +import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram'; +import { mergeWorkflowDiagrams } from '../mergeWorkflowDiagrams'; + +it('Preserves the properties defined in the previous version but not in the next one', () => { + const previousDiagram: WorkflowDiagram = { + nodes: [ + { + data: { nodeType: 'action', label: '', actionType: 'CODE' }, + id: '1', + position: { x: 0, y: 0 }, + selected: true, + }, + ], + edges: [], + }; + const nextDiagram: WorkflowDiagram = { + nodes: [ + { + data: { nodeType: 'action', label: '', actionType: 'CODE' }, + id: '1', + position: { x: 0, y: 0 }, + }, + ], + edges: [], + }; + + expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({ + nodes: [ + { + data: { nodeType: 'action', label: '', actionType: 'CODE' }, + id: '1', + position: { x: 0, y: 0 }, + selected: true, + }, + ], + edges: [], + }); +}); + +it('Replaces duplicated properties with the next value', () => { + const previousDiagram: WorkflowDiagram = { + nodes: [ + { + data: { nodeType: 'action', label: '', actionType: 'CODE' }, + id: '1', + position: { x: 0, y: 0 }, + }, + ], + edges: [], + }; + const nextDiagram: WorkflowDiagram = { + nodes: [ + { + data: { nodeType: 'action', label: '2', actionType: 'CODE' }, + id: '1', + position: { x: 0, y: 0 }, + }, + ], + edges: [], + }; + + expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({ + nodes: [ + { + data: { nodeType: 'action', label: '2', actionType: 'CODE' }, + id: '1', + position: { x: 0, y: 0 }, + }, + ], + edges: [], + }); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/removeStep.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/removeStep.test.ts new file mode 100644 index 000000000000..01385411bf02 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/removeStep.test.ts @@ -0,0 +1,108 @@ +import { WorkflowStep, WorkflowVersion } from '@/workflow/types/Workflow'; +import { removeStep } from '../removeStep'; + +it('returns a deep copy of the provided steps array instead of mutating it', () => { + const stepToBeRemoved = { + id: 'step-1', + name: '', + settings: { + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: false }, + }, + serverlessFunctionId: 'first', + }, + type: 'CODE', + valid: true, + } satisfies WorkflowStep; + const workflowVersionInitial = { + __typename: 'WorkflowVersion', + status: 'ACTIVE', + createdAt: '', + id: '1', + name: '', + steps: [stepToBeRemoved], + trigger: { + settings: { eventName: 'company.created' }, + type: 'DATABASE_EVENT', + }, + updatedAt: '', + workflowId: '', + } satisfies WorkflowVersion; + + const stepsUpdated = removeStep({ + steps: workflowVersionInitial.steps, + stepId: stepToBeRemoved.id, + }); + + expect(workflowVersionInitial.steps).not.toBe(stepsUpdated); +}); + +it('removes a step in a non-empty steps array', () => { + const stepToBeRemoved: WorkflowStep = { + id: 'step-2', + name: '', + settings: { + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: false }, + }, + serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997', + }, + type: 'CODE', + valid: true, + }; + const workflowVersionInitial = { + __typename: 'WorkflowVersion', + status: 'ACTIVE', + createdAt: '', + id: '1', + name: '', + steps: [ + { + id: 'step-1', + name: '', + settings: { + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: false }, + }, + serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997', + }, + type: 'CODE', + valid: true, + }, + stepToBeRemoved, + { + id: 'step-3', + name: '', + settings: { + errorHandlingOptions: { + retryOnFailure: { value: true }, + continueOnFailure: { value: false }, + }, + serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997', + }, + type: 'CODE', + valid: true, + }, + ], + trigger: { + settings: { eventName: 'company.created' }, + type: 'DATABASE_EVENT', + }, + updatedAt: '', + workflowId: '', + } satisfies WorkflowVersion; + + const stepsUpdated = removeStep({ + steps: workflowVersionInitial.steps, + stepId: stepToBeRemoved.id, + }); + + const expectedUpdatedSteps: Array<WorkflowStep> = [ + workflowVersionInitial.steps[0], + workflowVersionInitial.steps[2], + ]; + expect(stepsUpdated).toEqual(expectedUpdatedSteps); +}); diff --git a/packages/twenty-front/src/modules/workflow/utils/assertUnreachable.ts b/packages/twenty-front/src/modules/workflow/utils/assertUnreachable.ts new file mode 100644 index 000000000000..042337aeebf2 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/assertUnreachable.ts @@ -0,0 +1,3 @@ +export const assertUnreachable = (x: never, errorMessage?: string): never => { + throw new Error(errorMessage ?? "Didn't expect to get here."); +}; diff --git a/packages/twenty-front/src/modules/workflow/utils/assertWorkflowWithCurrentVersionIsDefined.tsx b/packages/twenty-front/src/modules/workflow/utils/assertWorkflowWithCurrentVersionIsDefined.tsx new file mode 100644 index 000000000000..c7849dd6cbd4 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/assertWorkflowWithCurrentVersionIsDefined.tsx @@ -0,0 +1,15 @@ +import { + Workflow, + WorkflowVersion, + WorkflowWithCurrentVersion, +} from '@/workflow/types/Workflow'; +import { isDefined } from 'twenty-ui'; + +// eslint-disable-next-line prefer-arrow/prefer-arrow-functions +export function assertWorkflowWithCurrentVersionIsDefined( + workflow: WorkflowWithCurrentVersion | undefined, +): asserts workflow is Workflow & { currentVersion: WorkflowVersion } { + if (!isDefined(workflow) || !isDefined(workflow.currentVersion)) { + throw new Error('Expected workflow and its current version to be defined'); + } +} diff --git a/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts b/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts new file mode 100644 index 000000000000..3ae23dff0283 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts @@ -0,0 +1,41 @@ +import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId'; +import { WorkflowStep } from '@/workflow/types/Workflow'; +import { isDefined } from 'twenty-ui'; + +/** + * This function returns the reference of the array where the step should be positioned + * and at which index. + */ +export const findStepPosition = ({ + steps, + stepId, +}: { + steps: Array<WorkflowStep>; + stepId: string | undefined; +}): { steps: Array<WorkflowStep>; index: number } | undefined => { + if (!isDefined(stepId) || stepId === TRIGGER_STEP_ID) { + return { + steps, + index: 0, + }; + } + + for (const [index, step] of steps.entries()) { + if (step.id === stepId) { + return { + steps, + index, + }; + } + + // TODO: When condition will have been implemented, put recursivity here. + // if (step.type === "CONDITION") { + // return findNodePosition({ + // workflowSteps: step.conditions, + // stepId, + // }) + // } + } + + return undefined; +}; diff --git a/packages/twenty-front/src/modules/workflow/utils/findStepPositionOrThrow.ts b/packages/twenty-front/src/modules/workflow/utils/findStepPositionOrThrow.ts index bf10df7783fc..c20ffbcd4a68 100644 --- a/packages/twenty-front/src/modules/workflow/utils/findStepPositionOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/utils/findStepPositionOrThrow.ts @@ -1,41 +1,21 @@ -import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId'; import { WorkflowStep } from '@/workflow/types/Workflow'; +import { findStepPosition } from '@/workflow/utils/findStepPosition'; import { isDefined } from 'twenty-ui'; /** * This function returns the reference of the array where the step should be positioned * and at which index. */ -export const findStepPositionOrThrow = ({ - steps, - stepId, -}: { +export const findStepPositionOrThrow = (props: { steps: Array<WorkflowStep>; stepId: string | undefined; }): { steps: Array<WorkflowStep>; index: number } => { - if (!isDefined(stepId) || stepId === TRIGGER_STEP_ID) { - return { - steps, - index: 0, - }; + const result = findStepPosition(props); + if (!isDefined(result)) { + throw new Error( + `Couldn't locate the step. Unreachable step id: ${props.stepId}.`, + ); } - for (const [index, step] of steps.entries()) { - if (step.id === stepId) { - return { - steps, - index, - }; - } - - // TODO: When condition will have been implemented, put recursivity here. - // if (step.type === "CONDITION") { - // return findNodePosition({ - // workflowSteps: step.conditions, - // stepId, - // }) - // } - } - - throw new Error(`Couldn't locate the step. Unreachable step id: ${stepId}.`); + return result; }; diff --git a/packages/twenty-front/src/modules/workflow/utils/generateWorkflowDiagram.ts b/packages/twenty-front/src/modules/workflow/utils/generateWorkflowDiagram.ts index 4d0aa8995ae5..ff5a211e64dd 100644 --- a/packages/twenty-front/src/modules/workflow/utils/generateWorkflowDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/utils/generateWorkflowDiagram.ts @@ -33,6 +33,7 @@ export const generateWorkflowDiagram = ({ id: nodeId, data: { nodeType: 'action', + actionType: step.type, label: step.name, }, position: { diff --git a/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts b/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts index 9cb8b27e86ff..48e8f9bd448f 100644 --- a/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts +++ b/packages/twenty-front/src/modules/workflow/utils/getStepDefaultDefinition.ts @@ -26,6 +26,27 @@ export const getStepDefaultDefinition = ( }, }; } + case 'SEND_EMAIL': { + return { + id: newStepId, + name: 'Send Email', + type: 'SEND_EMAIL', + valid: false, + settings: { + connectedAccountId: '', + subject: '', + body: '', + errorHandlingOptions: { + continueOnFailure: { + value: false, + }, + retryOnFailure: { + value: false, + }, + }, + }, + }; + } default: { throw new Error(`Unknown type: ${type}`); } diff --git a/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts b/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts new file mode 100644 index 000000000000..47ba8d4df886 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts @@ -0,0 +1,45 @@ +import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; +import { findStepPosition } from '@/workflow/utils/findStepPosition'; +import { isDefined } from 'twenty-ui'; + +export const getStepDefinitionOrThrow = ({ + stepId, + workflowVersion, +}: { + stepId: string; + workflowVersion: WorkflowVersion; +}) => { + if (stepId === TRIGGER_STEP_ID) { + if (!isDefined(workflowVersion.trigger)) { + return { + type: 'trigger', + definition: undefined, + } as const; + } + + return { + type: 'trigger', + definition: workflowVersion.trigger, + } as const; + } + + if (!isDefined(workflowVersion.steps)) { + throw new Error( + 'Malformed workflow version: missing steps information; be sure to create at least one step before trying to edit one', + ); + } + + const selectedNodePosition = findStepPosition({ + steps: workflowVersion.steps, + stepId: stepId, + }); + if (!isDefined(selectedNodePosition)) { + return undefined; + } + + return { + type: 'action', + definition: selectedNodePosition.steps[selectedNodePosition.index], + } as const; +}; diff --git a/packages/twenty-front/src/modules/workflow/utils/mergeWorkflowDiagrams.ts b/packages/twenty-front/src/modules/workflow/utils/mergeWorkflowDiagrams.ts new file mode 100644 index 000000000000..b52ca8f726eb --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/mergeWorkflowDiagrams.ts @@ -0,0 +1,33 @@ +import { + WorkflowDiagram, + WorkflowDiagramNode, +} from '@/workflow/types/WorkflowDiagram'; + +const nodePropertiesToPreserve: Array<keyof WorkflowDiagramNode> = ['selected']; + +export const mergeWorkflowDiagrams = ( + previousDiagram: WorkflowDiagram, + nextDiagram: WorkflowDiagram, +): WorkflowDiagram => { + const lastNodes = nextDiagram.nodes.map((nextNode) => { + const previousNode = previousDiagram.nodes.find( + (previousNode) => previousNode.id === nextNode.id, + ); + + const nodeWithPreservedProperties = nodePropertiesToPreserve.reduce( + (nodeToSet, propertyToPreserve) => { + return Object.assign(nodeToSet, { + [propertyToPreserve]: previousNode?.[propertyToPreserve], + }); + }, + {} as Partial<WorkflowDiagramNode>, + ); + + return Object.assign(nodeWithPreservedProperties, nextNode); + }); + + return { + nodes: lastNodes, + edges: nextDiagram.edges, + }; +}; diff --git a/packages/twenty-front/src/modules/workflow/utils/removeStep.ts b/packages/twenty-front/src/modules/workflow/utils/removeStep.ts new file mode 100644 index 000000000000..eb6fe41dda5b --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/utils/removeStep.ts @@ -0,0 +1,21 @@ +import { WorkflowStep } from '@/workflow/types/Workflow'; +import { findStepPositionOrThrow } from '@/workflow/utils/findStepPositionOrThrow'; + +export const removeStep = ({ + steps: stepsInitial, + stepId, +}: { + steps: Array<WorkflowStep>; + stepId: string | undefined; +}) => { + const steps = structuredClone(stepsInitial); + + const parentStepPosition = findStepPositionOrThrow({ + steps, + stepId, + }); + + parentStepPosition.steps.splice(parentStepPosition.index, 1); + + return steps; +}; diff --git a/packages/twenty-front/src/modules/workflow/utils/replaceStep.ts b/packages/twenty-front/src/modules/workflow/utils/replaceStep.ts index b08e2d33cf03..0d4d47977262 100644 --- a/packages/twenty-front/src/modules/workflow/utils/replaceStep.ts +++ b/packages/twenty-front/src/modules/workflow/utils/replaceStep.ts @@ -1,14 +1,14 @@ import { WorkflowStep } from '@/workflow/types/Workflow'; import { findStepPositionOrThrow } from '@/workflow/utils/findStepPositionOrThrow'; -export const replaceStep = ({ +export const replaceStep = <T extends WorkflowStep>({ steps: stepsInitial, stepId, stepToReplace, }: { steps: Array<WorkflowStep>; stepId: string; - stepToReplace: Partial<Omit<WorkflowStep, 'id'>>; + stepToReplace: Partial<Omit<T, 'id'>>; }) => { const steps = structuredClone(stepsInitial); diff --git a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts index 409723f1655e..cde44977047b 100644 --- a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts +++ b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts @@ -9,4 +9,10 @@ export type FeatureFlagKey = | 'IS_FREE_ACCESS_ENABLED' | 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED' | 'IS_WORKFLOW_ENABLED' - | 'IS_WORKSPACE_FAVORITE_ENABLED'; + | 'IS_WORKSPACE_FAVORITE_ENABLED' + | 'IS_SEARCH_ENABLED' + | 'IS_QUERY_RUNNER_TWENTY_ORM_ENABLED' + | 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED' + | 'IS_WORKSPACE_MIGRATED_FOR_SEARCH' + | 'IS_ANALYTICS_V2_ENABLED' + | 'IS_UNIQUE_INDEXES_ENABLED'; 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} > <Skeleton - height={32} + height={SKELETON_LOADER_HEIGHT_SIZES.standard.m} count={2} style={{ marginBottom: theme.spacing(2), diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 30e73dad46ea..0ecb2cb569de 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -1,16 +1,18 @@ import { useParams } from 'react-router-dom'; import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { PageBody } from '@/ui/layout/page/PageBody'; import { PageContainer } from '@/ui/layout/page/PageContainer'; -import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton'; -import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton'; -import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton'; import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; +import { RecordShowPageWorkflowHeader } from '@/workflow/components/RecordShowPageWorkflowHeader'; +import { RecordShowPageWorkflowVersionHeader } from '@/workflow/components/RecordShowPageWorkflowVersionHeader'; +import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader'; +import { RecordShowPageContextStoreEffect } from '~/pages/object-record/RecordShowPageContextStoreEffect'; import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader'; export const RecordShowPage = () => { @@ -38,6 +40,7 @@ export const RecordShowPage = () => { return ( <RecordFieldValueSelectorContextProvider> <RecordValueSetterEffect recordId={objectRecordId} /> + <RecordShowPageContextStoreEffect recordId={objectRecordId} /> <PageContainer> <PageTitle title={pageTitle} /> <RecordShowPageHeader @@ -46,22 +49,24 @@ export const RecordShowPage = () => { headerIcon={headerIcon} > <> - <PageFavoriteButton - isFavorite={isFavorite} - onClick={handleFavoriteButtonClick} - /> - <ShowPageAddButton - key="add" - activityTargetObject={{ - id: record?.id ?? '0', - targetObjectNameSingular: objectMetadataItem?.nameSingular, - }} - /> - <ShowPageMoreButton - key="more" - recordId={record?.id ?? '0'} - objectNameSingular={objectNameSingular} - /> + {objectNameSingular === CoreObjectNameSingular.Workflow ? ( + <RecordShowPageWorkflowHeader workflowId={objectRecordId} /> + ) : objectNameSingular === + CoreObjectNameSingular.WorkflowVersion ? ( + <RecordShowPageWorkflowVersionHeader + workflowVersionId={objectRecordId} + /> + ) : ( + <RecordShowPageBaseHeader + {...{ + isFavorite, + handleFavoriteButtonClick, + record, + objectMetadataItem, + objectNameSingular, + }} + /> + )} </> </RecordShowPageHeader> <PageBody> diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx new file mode 100644 index 000000000000..a7577114c092 --- /dev/null +++ b/packages/twenty-front/src/pages/object-record/RecordShowPageBaseHeader.tsx @@ -0,0 +1,40 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton'; +import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton'; +import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton'; + +export const RecordShowPageBaseHeader = ({ + isFavorite, + handleFavoriteButtonClick, + record, + objectMetadataItem, + objectNameSingular, +}: { + isFavorite: boolean; + handleFavoriteButtonClick: () => void; + record: ObjectRecord | undefined; + objectMetadataItem: ObjectMetadataItem; + objectNameSingular: string; +}) => { + return ( + <> + <PageFavoriteButton + isFavorite={isFavorite} + onClick={handleFavoriteButtonClick} + /> + <ShowPageAddButton + key="add" + activityTargetObject={{ + id: record?.id ?? '0', + targetObjectNameSingular: objectMetadataItem.nameSingular, + }} + /> + <ShowPageMoreButton + key="more" + recordId={record?.id ?? '0'} + objectNameSingular={objectNameSingular} + /> + </> + ); +}; diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPageContextStoreEffect.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPageContextStoreEffect.tsx new file mode 100644 index 000000000000..a9a5514e285d --- /dev/null +++ b/packages/twenty-front/src/pages/object-record/RecordShowPageContextStoreEffect.tsx @@ -0,0 +1,43 @@ +import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState'; +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { useSetRecoilState } from 'recoil'; + +export const RecordShowPageContextStoreEffect = ({ + recordId, +}: { + recordId: string; +}) => { + const setContextStoreTargetedRecordIds = useSetRecoilState( + contextStoreTargetedRecordIdsState, + ); + + const setContextStoreCurrentObjectMetadataId = useSetRecoilState( + contextStoreCurrentObjectMetadataIdState, + ); + + const { objectNameSingular } = useParams(); + + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular: objectNameSingular ?? '', + }); + + useEffect(() => { + setContextStoreTargetedRecordIds([recordId]); + setContextStoreCurrentObjectMetadataId(objectMetadataItem?.id); + + return () => { + setContextStoreTargetedRecordIds([]); + setContextStoreCurrentObjectMetadataId(null); + }; + }, [ + recordId, + setContextStoreTargetedRecordIds, + setContextStoreCurrentObjectMetadataId, + objectMetadataItem?.id, + ]); + + return null; +}; diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPageEffect.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPageEffect.tsx new file mode 100644 index 000000000000..e40a00da25ee --- /dev/null +++ b/packages/twenty-front/src/pages/object-record/RecordShowPageEffect.tsx @@ -0,0 +1,15 @@ +import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState'; +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; + +export const RecordShowPageEffect = ({ recordId }: { recordId: string }) => { + const setContextStoreTargetedRecordIds = useSetRecoilState( + contextStoreTargetedRecordIdsState, + ); + + useEffect(() => { + setContextStoreTargetedRecordIds([recordId]); + }, [recordId, setContextStoreTargetedRecordIds]); + + return null; +}; diff --git a/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx b/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx index 5f1b8c02129d..31d04e0c1edb 100644 --- a/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx +++ b/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx @@ -11,7 +11,6 @@ import { graphqlMocks } from '~/testing/graphqlMocks'; import { getPeopleMock, peopleQueryResult } from '~/testing/mock-data/people'; import { mockedWorkspaceMemberData } from '~/testing/mock-data/users'; -import { viewQueryResultMock } from '~/testing/mock-data/views'; import { RecordShowPage } from '../RecordShowPage'; const peopleMock = getPeopleMock(); @@ -63,13 +62,6 @@ const meta: Meta<PageDecoratorArgs> = { }, }); }), - graphql.query('FindManyViews', () => { - return HttpResponse.json({ - data: { - views: viewQueryResultMock, - }, - }); - }), graphqlMocks.handlers, ], }, diff --git a/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx b/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx index c6ddb99c82d6..c80eed892c75 100644 --- a/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx +++ b/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx @@ -54,11 +54,11 @@ export const SyncEmails = () => { ? CalendarChannelVisibility.ShareEverything : CalendarChannelVisibility.Metadata; - await triggerGoogleApisOAuth( - AppPath.Index, - visibility, - calendarChannelVisibility, - ); + await triggerGoogleApisOAuth({ + redirectLocation: AppPath.Index, + messageVisibility: visibility, + calendarVisibility: calendarChannelVisibility, + }); }; const continueWithoutSync = async () => { diff --git a/packages/twenty-front/src/pages/settings/Releases.tsx b/packages/twenty-front/src/pages/settings/Releases.tsx index 60845a1b8c5f..3429ac9893ac 100644 --- a/packages/twenty-front/src/pages/settings/Releases.tsx +++ b/packages/twenty-front/src/pages/settings/Releases.tsx @@ -3,7 +3,6 @@ import React, { useEffect, useState } from 'react'; import rehypeStringify from 'rehype-stringify'; import remarkParse from 'remark-parse'; import remarkRehype from 'remark-rehype'; -import { IconRocket } from 'twenty-ui'; import { unified } from 'unified'; import { visit } from 'unist-util-visit'; @@ -107,7 +106,6 @@ export const Releases = () => { return ( <SubMenuTopBarContainer - Icon={IconRocket} title="Releases" links={[ { diff --git a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx index ee057b7114a7..5a8c90a81a31 100644 --- a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx @@ -1,13 +1,10 @@ -import styled from '@emotion/styled'; import { useState } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { - H1Title, H2Title, IconCalendarEvent, IconCircleX, IconCreditCard, - IconCurrencyDollar, } from 'twenty-ui'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; @@ -34,10 +31,6 @@ import { } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; -const StyledH1Title = styled(H1Title)` - margin-bottom: 0; -`; - type SwitchInfo = { newInterval: SubscriptionInterval; to: string; @@ -143,7 +136,6 @@ export const SettingsBilling = () => { return ( <SubMenuTopBarContainer - Icon={IconCurrencyDollar} title="Billing" links={[ { @@ -154,7 +146,6 @@ export const SettingsBilling = () => { ]} > <SettingsPageContainer> - <StyledH1Title title="Billing" /> <SettingsBillingCoverImage /> {displayPaymentFailInfo && ( <Info diff --git a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx index 86b0e5637320..0ff302645941 100644 --- a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx @@ -1,4 +1,4 @@ -import { H2Title, IconUserCircle } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { ChangePassword } from '@/settings/profile/components/ChangePassword'; @@ -13,7 +13,6 @@ import { Section } from '@/ui/layout/section/components/Section'; export const SettingsProfile = () => ( <SubMenuTopBarContainer - Icon={IconUserCircle} title="Profile" links={[ { diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx index 359961d24d87..329c736f1834 100644 --- a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx @@ -1,4 +1,4 @@ -import { H2Title, IconSettings } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace'; @@ -9,10 +9,10 @@ import { WorkspaceLogoUploader } from '@/settings/workspace/components/Workspace import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; +import { GithubVersionLink } from '@/ui/navigation/link/components/GithubVersionLink'; export const SettingsWorkspace = () => ( <SubMenuTopBarContainer - Icon={IconSettings} title="General" links={[ { @@ -41,6 +41,9 @@ export const SettingsWorkspace = () => ( <Section> <DeleteWorkspace /> </Section> + <Section> + <GithubVersionLink /> + </Section> </SettingsPageContainer> </SubMenuTopBarContainer> ); diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx index 0079fb888501..3ba11b7f22fb 100644 --- a/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx @@ -1,17 +1,17 @@ +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { isNonEmptyArray } from '@sniptt/guards'; import { useState } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { + AppTooltip, + Avatar, H2Title, - IconTrash, - IconUsers, - IconReload, IconMail, - StyledText, - Avatar, + IconReload, + IconTrash, + TooltipDelay, } from 'twenty-ui'; -import { isNonEmptyArray } from '@sniptt/guards'; -import { useTheme } from '@emotion/react'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; @@ -21,26 +21,26 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; +import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { IconButton } from '@/ui/input/button/components/IconButton'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; +import { Table } from '@/ui/layout/table/components/Table'; +import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink'; import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam'; -import { useGetWorkspaceInvitationsQuery } from '~/generated/graphql'; -import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { Table } from '@/ui/layout/table/components/Table'; -import { TableHeader } from '@/ui/layout/table/components/TableHeader'; -import { workspaceInvitationsState } from '../../modules/workspace-invitation/states/workspaceInvitationsStates'; -import { TableRow } from '../../modules/ui/layout/table/components/TableRow'; -import { TableCell } from '../../modules/ui/layout/table/components/TableCell'; -import { Status } from '../../modules/ui/display/status/components/Status'; import { formatDistanceToNow } from 'date-fns'; -import { useResendWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useResendWorkspaceInvitation'; +import { useGetWorkspaceInvitationsQuery } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; +import { Status } from '../../modules/ui/display/status/components/Status'; +import { TableCell } from '../../modules/ui/layout/table/components/TableCell'; +import { TableRow } from '../../modules/ui/layout/table/components/TableRow'; import { useDeleteWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useDeleteWorkspaceInvitation'; +import { useResendWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useResendWorkspaceInvitation'; +import { workspaceInvitationsState } from '../../modules/workspace-invitation/states/workspaceInvitationsStates'; const StyledButtonContainer = styled.div` align-items: center; @@ -57,6 +57,18 @@ const StyledTableHeaderRow = styled(Table)` margin-bottom: ${({ theme }) => theme.spacing(1.5)}; `; +const StyledIconWrapper = styled.div` + display: flex; + align-items: center; + margin-right: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTextContainerWithEllipsis = styled.div` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + export const SettingsWorkspaceMembers = () => { const { enqueueSnackBar } = useSnackBar(); const theme = useTheme(); @@ -126,7 +138,6 @@ export const SettingsWorkspaceMembers = () => { return ( <SubMenuTopBarContainer - Icon={IconUsers} title="Members" links={[ { @@ -155,7 +166,10 @@ export const SettingsWorkspaceMembers = () => { /> <Table> <StyledTableHeaderRow> - <TableRow> + <TableRow + gridAutoColumns="150px 1fr 1fr" + mobileGridAutoColumns="100px 1fr 1fr" + > <TableHeader>Name</TableHeader> <TableHeader>Email</TableHeader> <TableHeader align={'right'}></TableHeader> @@ -163,30 +177,40 @@ export const SettingsWorkspaceMembers = () => { </StyledTableHeaderRow> {workspaceMembers?.map((workspaceMember) => ( <StyledTable key={workspaceMember.id}> - <TableRow> + <TableRow + gridAutoColumns="150px 1fr 1fr" + mobileGridAutoColumns="100px 1fr 1fr" + > <TableCell> - <StyledText - PrefixComponent={ - <Avatar - avatarUrl={workspaceMember.avatarUrl} - placeholderColorSeed={workspaceMember.id} - placeholder={workspaceMember.name.firstName ?? ''} - type="rounded" - size="sm" - /> - } - text={ - workspaceMember.name.firstName + + <StyledIconWrapper> + <Avatar + avatarUrl={workspaceMember.avatarUrl} + placeholderColorSeed={workspaceMember.id} + placeholder={workspaceMember.name.firstName ?? ''} + type="rounded" + size="sm" + /> + </StyledIconWrapper> + <StyledTextContainerWithEllipsis + id={`hover-text-${workspaceMember.id}`} + > + {workspaceMember.name.firstName + ' ' + - workspaceMember.name.lastName - } + workspaceMember.name.lastName} + </StyledTextContainerWithEllipsis> + <AppTooltip + anchorSelect={`#hover-text-${workspaceMember.id}`} + content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`} + noArrow + place="top" + positionStrategy="fixed" + delay={TooltipDelay.shortDelay} /> </TableCell> <TableCell> - <StyledText - text={workspaceMember.userEmail} - color={theme.font.color.secondary} - /> + <StyledTextContainerWithEllipsis> + {workspaceMember.userEmail} + </StyledTextContainerWithEllipsis> </TableCell> <TableCell align={'right'}> {currentWorkspaceMember?.id !== workspaceMember.id && ( @@ -217,7 +241,10 @@ export const SettingsWorkspaceMembers = () => { {isNonEmptyArray(workspaceInvitations) && ( <Table> <StyledTableHeaderRow> - <TableRow gridAutoColumns={`1fr 1fr ${theme.spacing(22)}`}> + <TableRow + gridAutoColumns="150px 1fr 1fr" + mobileGridAutoColumns="100px 1fr 1fr" + > <TableHeader>Email</TableHeader> <TableHeader align={'right'}>Expires in</TableHeader> <TableHeader></TableHeader> @@ -225,17 +252,20 @@ export const SettingsWorkspaceMembers = () => { </StyledTableHeaderRow> {workspaceInvitations?.map((workspaceInvitation) => ( <StyledTable key={workspaceInvitation.id}> - <TableRow gridAutoColumns={`1fr 1fr ${theme.spacing(22)}`}> + <TableRow + gridAutoColumns="150px 1fr 1fr" + mobileGridAutoColumns="100px 1fr 1fr" + > <TableCell> - <StyledText - PrefixComponent={ - <IconMail - size={theme.icon.size.md} - stroke={theme.icon.stroke.sm} - /> - } - text={workspaceInvitation.email} - /> + <StyledIconWrapper> + <IconMail + size={theme.icon.size.md} + stroke={theme.icon.stroke.sm} + /> + </StyledIconWrapper> + <StyledTextContainerWithEllipsis> + {workspaceInvitation.email} + </StyledTextContainerWithEllipsis> </TableCell> <TableCell align={'right'}> <Status 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/accounts/SettingsAccounts.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx index ed1ad2713231..5ff43e83219d 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx @@ -1,5 +1,5 @@ import { useRecoilValue } from 'recoil'; -import { H2Title, IconAt } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; @@ -36,7 +36,6 @@ export const SettingsAccounts = () => { return ( <SubMenuTopBarContainer - Icon={IconAt} title="Account" links={[ { diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx index ff4a6cd6ac29..2dc86e72e114 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx @@ -4,12 +4,10 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; -import { IconCalendarEvent } from 'twenty-ui'; export const SettingsAccountsCalendars = () => { return ( <SubMenuTopBarContainer - Icon={IconCalendarEvent} title="Calendars" links={[ { diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx index 6e2d574b95d4..4e5732ef81b4 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx @@ -4,11 +4,9 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; -import { IconMail } from 'twenty-ui'; export const SettingsAccountsEmails = () => ( <SubMenuTopBarContainer - Icon={IconMail} title="Emails" links={[ { diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx index 440ebedb93a9..35e90d9b2cf0 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx @@ -3,12 +3,10 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; -import { IconAt } from 'twenty-ui'; export const SettingsNewAccount = () => { return ( <SubMenuTopBarContainer - Icon={IconAt} title="New Account" links={[ { diff --git a/packages/twenty-front/src/pages/settings/crm-migration/SettingsCRMMigration.tsx b/packages/twenty-front/src/pages/settings/crm-migration/SettingsCRMMigration.tsx index 5794ee4ae4e0..4c4dbc807c64 100644 --- a/packages/twenty-front/src/pages/settings/crm-migration/SettingsCRMMigration.tsx +++ b/packages/twenty-front/src/pages/settings/crm-migration/SettingsCRMMigration.tsx @@ -1,6 +1,5 @@ // @ts-expect-error external library has a typing issue import { RevertConnect } from '@revertdotdev/revert-react'; -import { IconSettings } from 'twenty-ui'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; @@ -18,7 +17,6 @@ export const SettingsCRMMigration = () => { const currentWorkspace = useRecoilValue(currentWorkspaceState); return ( <SubMenuTopBarContainer - Icon={IconSettings} title="Migrate" links={[ { diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx index f3ddd5440865..136bad77026b 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx @@ -1,7 +1,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { H2Title, IconHierarchy2 } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { z } from 'zod'; import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem'; @@ -69,7 +69,6 @@ export const SettingsNewObject = () => { // eslint-disable-next-line react/jsx-props-no-spreading <FormProvider {...formConfig}> <SubMenuTopBarContainer - Icon={IconHierarchy2} title="New Object" links={[ { diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPageContent.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPageContent.tsx index c69c27507c54..37fb2d9c0cd2 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPageContent.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPageContent.tsx @@ -1,6 +1,5 @@ import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { getDisabledFieldMetadataItems } from '@/object-metadata/utils/getDisabledFieldMetadataItems'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsObjectSummaryCard } from '@/settings/data-model/object-details/components/SettingsObjectSummaryCard'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; @@ -9,11 +8,14 @@ import { Button } from '@/ui/input/button/components/Button'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; +import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; -import { isNonEmptyArray } from '@sniptt/guards'; import { useNavigate } from 'react-router-dom'; -import { H2Title, IconHierarchy2, IconPlus } from 'twenty-ui'; +import { useRecoilValue } from 'recoil'; +import { H2Title, IconPlus } from 'twenty-ui'; import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable'; +import { SettingsObjectIndexTable } from '~/pages/settings/data-model/SettingsObjectIndexTable'; const StyledDiv = styled.div` display: flex; @@ -40,14 +42,16 @@ export const SettingsObjectDetailPageContent = ({ navigate(getSettingsPagePath(SettingsPath.Objects)); }; - const disabledFieldMetadataItems = - getDisabledFieldMetadataItems(objectMetadataItem); - const shouldDisplayAddFieldButton = !objectMetadataItem.isRemote; + const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); + + const isUniqueIndexesEnabled = useIsFeatureEnabled( + 'IS_UNIQUE_INDEXES_ENABLED', + ); + return ( <SubMenuTopBarContainer - Icon={IconHierarchy2} title={objectMetadataItem.labelPlural} links={[ { @@ -80,13 +84,7 @@ export const SettingsObjectDetailPageContent = ({ /> {shouldDisplayAddFieldButton && ( <StyledDiv> - <UndecoratedLink - to={ - isNonEmptyArray(disabledFieldMetadataItems) - ? './new-field/step-1' - : './new-field/step-2' - } - > + <UndecoratedLink to={'./new-field/select'}> <Button Icon={IconPlus} title="Add Field" @@ -97,6 +95,15 @@ export const SettingsObjectDetailPageContent = ({ </StyledDiv> )} </Section> + {isAdvancedModeEnabled && isUniqueIndexesEnabled && ( + <Section> + <H2Title + title="Indexes" + description={`Advanced feature to improve the performance of queries and to enforce unicity constraints.`} + /> + <SettingsObjectIndexTable objectMetadataItem={objectMetadataItem} /> + </Section> + )} </SettingsPageContainer> </SubMenuTopBarContainer> ); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectEdit.tsx index 8782a7b75b2d..3cbdfe3bdc7e 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectEdit.tsx @@ -5,7 +5,7 @@ import pick from 'lodash.pick'; import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router-dom'; -import { H2Title, IconArchive, IconHierarchy2 } from 'twenty-ui'; +import { H2Title, IconArchive } from 'twenty-ui'; import { z } from 'zod'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; @@ -107,7 +107,6 @@ export const SettingsObjectEdit = () => { <RecordFieldValueSelectorContextProvider> <FormProvider {...formConfig}> <SubMenuTopBarContainer - Icon={IconHierarchy2} title="Edit" links={[ { diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 5e4769913b40..651243747a2e 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -5,12 +5,7 @@ import pick from 'lodash.pick'; import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router-dom'; -import { - H2Title, - IconArchive, - IconArchiveOff, - IconHierarchy2, -} from 'twenty-ui'; +import { H2Title, IconArchive, IconArchiveOff } from 'twenty-ui'; import { z } from 'zod'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; @@ -30,7 +25,7 @@ import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fie import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm'; import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { AppPath } from '@/types/AppPath'; import { SettingsPath } from '@/types/SettingsPath'; @@ -42,9 +37,11 @@ import { Section } from '@/ui/layout/section/components/Section'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; +//TODO: fix this type type SettingsDataModelFieldEditFormValues = z.infer< ReturnType<typeof settingsFieldFormSchema> ->; +> & + any; const canPersistFieldMetadataItemUpdate = ( fieldMetadataItem: FieldMetadataItem, @@ -94,7 +91,7 @@ export const SettingsObjectFieldEdit = () => { resolver: zodResolver(settingsFieldFormSchema()), values: { icon: fieldMetadataItem?.icon ?? 'Icon', - type: fieldMetadataItem?.type as SettingsSupportedFieldType, + type: fieldMetadataItem?.type as SettingsFieldType, label: fieldMetadataItem?.label ?? '', description: fieldMetadataItem?.description, }, @@ -184,7 +181,6 @@ export const SettingsObjectFieldEdit = () => { {/* eslint-disable-next-line react/jsx-props-no-spreading */} <FormProvider {...formConfig}> <SubMenuTopBarContainer - Icon={IconHierarchy2} title={fieldMetadataItem?.label} links={[ { @@ -227,9 +223,18 @@ export const SettingsObjectFieldEdit = () => { /> </Section> <Section> - <H2Title title="Values" description="The values of this field" /> + {fieldMetadataItem.isUnique ? ( + <H2Title + title="Values" + description="The values of this field must be unique" + /> + ) : ( + <H2Title + title="Values" + description="The values of this field" + /> + )} <SettingsDataModelFieldSettingsFormCard - disableCurrencyForm fieldMetadataItem={fieldMetadataItem} objectMetadataItem={objectMetadataItem} /> diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectIndexTable.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectIndexTable.tsx new file mode 100644 index 000000000000..f0e449ddb0d1 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectIndexTable.tsx @@ -0,0 +1,152 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +import { settingsObjectIndexesFamilyState } from '@/settings/data-model/object-details/states/settingsObjectIndexesFamilyState'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; +import { Table } from '@/ui/layout/table/components/Table'; +import { TableCell } from '@/ui/layout/table/components/TableCell'; +import { TableHeader } from '@/ui/layout/table/components/TableHeader'; +import { TableRow } from '@/ui/layout/table/components/TableRow'; +import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; +import { TableMetadata } from '@/ui/layout/table/types/TableMetadata'; +import styled from '@emotion/styled'; +import { isNonEmptyArray } from '@sniptt/guards'; +import { useEffect, useMemo, useState } from 'react'; +import { useRecoilState } from 'recoil'; +import { IconSearch, IconSquareKey } from 'twenty-ui'; +import { SettingsObjectIndexesTableItem } from '~/pages/settings/data-model/types/SettingsObjectIndexesTableItem'; + +export const StyledObjectIndexTableRow = styled(TableRow)` + grid-template-columns: 350px 70px 80px; +`; + +const SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD: TableMetadata<SettingsObjectIndexesTableItem> = + { + tableId: 'settingsObjectIndexs', + fields: [ + { + fieldLabel: 'Fields', + fieldName: 'indexFields', + fieldType: 'string', + align: 'left', + }, + { + fieldLabel: '', + FieldIcon: IconSquareKey, + fieldName: 'isUnique', + fieldType: 'string', + align: 'left', + }, + { + fieldLabel: 'Type', + fieldName: 'indexType', + fieldType: 'string', + align: 'right', + }, + ], + initialSort: { + fieldName: 'name', + orderBy: 'AscNullsLast', + }, + }; + +const StyledSearchInput = styled(TextInput)` + padding-bottom: ${({ theme }) => theme.spacing(2)}; + width: 100%; +`; +export type SettingsObjectIndexTableProps = { + objectMetadataItem: ObjectMetadataItem; +}; + +export const SettingsObjectIndexTable = ({ + objectMetadataItem, +}: SettingsObjectIndexTableProps) => { + const [searchTerm, setSearchTerm] = useState(''); + + const [settingsObjectIndexes, setSettingsObjectIndexes] = useRecoilState( + settingsObjectIndexesFamilyState({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + useEffect(() => { + setSettingsObjectIndexes(objectMetadataItem.indexMetadatas); + }, [objectMetadataItem, setSettingsObjectIndexes]); + + const objectSettingsDetailItems = useMemo(() => { + return ( + settingsObjectIndexes?.map((indexMetadataItem) => { + return { + name: indexMetadataItem.name, + isUnique: indexMetadataItem.isUnique, + indexType: indexMetadataItem.indexType, + indexFields: indexMetadataItem.indexFieldMetadatas + ?.map((indexField) => { + const fieldMetadataItem = objectMetadataItem.fields.find( + (field) => field.id === indexField.fieldMetadataId, + ); + return fieldMetadataItem?.label; + }) + .join(', '), + }; + }) ?? [] + ); + }, [settingsObjectIndexes, objectMetadataItem]); + + const sortedActiveObjectSettingsDetailItems = useSortedArray( + objectSettingsDetailItems, + SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD, + ); + + const filteredActiveItems = useMemo( + () => + sortedActiveObjectSettingsDetailItems.filter( + (item) => + item.name.toLowerCase().includes(searchTerm.toLowerCase()) || + item.indexType.toLowerCase().includes(searchTerm.toLowerCase()), + ), + [sortedActiveObjectSettingsDetailItems, searchTerm], + ); + + return ( + <> + <StyledSearchInput + LeftIcon={IconSearch} + placeholder="Search an index..." + value={searchTerm} + onChange={setSearchTerm} + /> + <Table> + <StyledObjectIndexTableRow> + {SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD.fields.map((item) => ( + <SortableTableHeader + key={item.fieldName} + fieldName={item.fieldName} + label={item.fieldLabel} + Icon={item.FieldIcon} + tableId={SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD.tableId} + initialSort={ + SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD.initialSort + } + /> + ))} + <TableHeader></TableHeader> + </StyledObjectIndexTableRow> + {isNonEmptyArray(filteredActiveItems) && + filteredActiveItems.map((objectSettingsIndex) => ( + <StyledObjectIndexTableRow key={objectSettingsIndex.name}> + <TableCell>{objectSettingsIndex.indexFields}</TableCell> + <TableCell> + {objectSettingsIndex.isUnique ? ( + <IconSquareKey size={14} /> + ) : ( + '' + )} + </TableCell> + <TableCell>{objectSettingsIndex.indexType}</TableCell> + </StyledObjectIndexTableRow> + ))} + </Table> + </> + ); +}; diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure.tsx similarity index 64% rename from packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx rename to packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure.tsx index b079019356ce..5e8893813fb3 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure.tsx @@ -11,9 +11,8 @@ import { FIELD_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/Field import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm'; import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm'; import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; -import { SettingsDataModelFieldTypeSelect } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect'; import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema'; -import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { AppPath } from '@/types/AppPath'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -22,40 +21,39 @@ import { Section } from '@/ui/layout/section/components/Section'; import { View } from '@/views/types/View'; import { ViewType } from '@/views/types/ViewType'; import { useApolloClient } from '@apollo/client'; -import styled from '@emotion/styled'; import { zodResolver } from '@hookform/resolvers/zod'; import pick from 'lodash.pick'; import { useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; -import { H1Title, H1TitleFontColor, H2Title, IconHierarchy2 } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { z } from 'zod'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { isDefined } from '~/utils/isDefined'; +import { DEFAULT_ICONS_BY_FIELD_TYPE } from '~/pages/settings/data-model/constants/DefaultIconsByFieldType'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type SettingsDataModelNewFieldFormValues = z.infer< ReturnType<typeof settingsFieldFormSchema> ->; +> & + any; -const StyledH1Title = styled(H1Title)` - margin-bottom: 0; - padding-top: ${({ theme }) => theme.spacing(3)}; -`; -export const SettingsObjectNewFieldStep2 = () => { +const DEFAULT_ICON_FOR_NEW_FIELD = 'IconUsers'; + +export const SettingsObjectNewFieldConfigure = () => { const navigate = useNavigate(); const { objectSlug = '' } = useParams(); const [searchParams] = useSearchParams(); - const fieldType = searchParams.get('fieldType') as SettingsSupportedFieldType; + const fieldType = + (searchParams.get('fieldType') as SettingsFieldType) || + FieldMetadataType.Text; const { enqueueSnackBar } = useSnackBar(); - const [isConfigureStep, setIsConfigureStep] = useState(false); const { findActiveObjectMetadataItemBySlug } = useFilteredObjectMetadataItems(); - const activeObjectMetadataItem = findActiveObjectMetadataItemBySlug(objectSlug); const { createMetadataField } = useFieldMetadataItem(); + const apolloClient = useApolloClient(); const formConfig = useForm<SettingsDataModelNewFieldFormValues>({ mode: 'onTouched', @@ -64,13 +62,21 @@ export const SettingsObjectNewFieldStep2 = () => { activeObjectMetadataItem?.fields.map((value) => value.name), ), ), + defaultValues: { + type: fieldType, + icon: + DEFAULT_ICONS_BY_FIELD_TYPE[fieldType] ?? DEFAULT_ICON_FOR_NEW_FIELD, + label: '', + description: '', + }, }); useEffect(() => { - if (!activeObjectMetadataItem) { - navigate(AppPath.NotFound); - } - }, [activeObjectMetadataItem, navigate]); + formConfig.setValue( + 'icon', + DEFAULT_ICONS_BY_FIELD_TYPE[fieldType] ?? DEFAULT_ICON_FOR_NEW_FIELD, + ); + }, [fieldType, formConfig]); const [, setObjectViews] = useState<View[]>([]); const [, setRelationObjectViews] = useState<View[]>([]); @@ -83,7 +89,6 @@ export const SettingsObjectNewFieldStep2 = () => { }, onCompleted: async (views) => { if (isUndefinedOrNull(views)) return; - setObjectViews(views); }, }); @@ -101,15 +106,17 @@ export const SettingsObjectNewFieldStep2 = () => { }, onCompleted: async (views) => { if (isUndefinedOrNull(views)) return; - setRelationObjectViews(views); }, }); - const { createOneRelationMetadataItem: createOneRelationMetadata } = useCreateOneRelationMetadataItem(); - const apolloClient = useApolloClient(); + useEffect(() => { + if (!activeObjectMetadataItem) { + navigate(AppPath.NotFound); + } + }, [activeObjectMetadataItem, navigate]); if (!activeObjectMetadataItem) return null; @@ -158,17 +165,7 @@ export const SettingsObjectNewFieldStep2 = () => { }); } }; - - const excludedFieldTypes: SettingsSupportedFieldType[] = ( - [ - FieldMetadataType.Link, - FieldMetadataType.Numeric, - FieldMetadataType.RichText, - FieldMetadataType.Actor, - FieldMetadataType.Email, - FieldMetadataType.Phone, - ] as const - ).filter(isDefined); + if (!activeObjectMetadataItem) return null; return ( <RecordFieldValueSelectorContextProvider> @@ -176,95 +173,59 @@ export const SettingsObjectNewFieldStep2 = () => { {...formConfig} > <SubMenuTopBarContainer - Icon={IconHierarchy2} + title="2. Configure field" links={[ - { - children: 'Objects', - href: '/settings/objects', - }, + { children: 'Workspace', href: '/settings/workspace' }, + { children: 'Objects', href: '/settings/objects' }, { children: activeObjectMetadataItem.labelPlural, href: `/settings/objects/${objectSlug}`, }, - { - children: ( - <SettingsDataModelNewFieldBreadcrumbDropDown - isConfigureStep={isConfigureStep} - onBreadcrumbClick={setIsConfigureStep} - /> - ), - }, + + { children: <SettingsDataModelNewFieldBreadcrumbDropDown /> }, ]} actionButton={ - !activeObjectMetadataItem.isRemote && ( - <SaveAndCancelButtons - isSaveDisabled={!canSave} - isCancelDisabled={isSubmitting} - onCancel={() => { - if (!isConfigureStep) { - navigate(`/settings/objects/${objectSlug}`); - } else { - setIsConfigureStep(false); - } - }} - onSave={formConfig.handleSubmit(handleSave)} - /> - ) + <SaveAndCancelButtons + isSaveDisabled={!canSave} + isCancelDisabled={isSubmitting} + onCancel={() => + navigate( + `/settings/objects/${objectSlug}/new-field/select?fieldType=${fieldType}`, + ) + } + onSave={formConfig.handleSubmit(handleSave)} + /> } > <SettingsPageContainer> - <StyledH1Title - title={ - !isConfigureStep - ? '1. Select a field type' - : '2. Configure field' - } - fontColor={H1TitleFontColor.Primary} - /> - - {!isConfigureStep ? ( - <SettingsDataModelFieldTypeSelect - excludedFieldTypes={excludedFieldTypes} + <Section> + <H2Title + title="Icon and Name" + description="The name and icon of this field" + /> + <SettingsDataModelFieldIconLabelForm + maxLength={FIELD_NAME_MAXIMUM_LENGTH} + /> + </Section> + <Section> + <H2Title title="Values" description="The values of this field" /> + <SettingsDataModelFieldSettingsFormCard + isCreatingField fieldMetadataItem={{ - type: fieldType, + icon: formConfig.watch('icon'), + label: formConfig.watch('label') || 'New Field', + type: fieldType as FieldMetadataType, }} - onFieldTypeSelect={() => setIsConfigureStep(true)} + objectMetadataItem={activeObjectMetadataItem} /> - ) : ( - <> - <Section> - <H2Title - title="Icon and Name" - description="The name and icon of this field" - /> - <SettingsDataModelFieldIconLabelForm - maxLength={FIELD_NAME_MAXIMUM_LENGTH} - /> - </Section> - <Section> - <H2Title - title="Values" - description="The values of this field" - /> - - <SettingsDataModelFieldSettingsFormCard - fieldMetadataItem={{ - icon: formConfig.watch('icon'), - label: formConfig.watch('label') || 'Employees', - type: formConfig.watch('type'), - }} - objectMetadataItem={activeObjectMetadataItem} - /> - </Section> - <Section> - <H2Title - title="Description" - description="The description of this field" - /> - <SettingsDataModelFieldDescriptionForm /> - </Section> - </> - )} + </Section> + <Section> + <H2Title + title="Description" + description="The description of this field" + /> + <SettingsDataModelFieldDescriptionForm /> + </Section> </SettingsPageContainer> </SubMenuTopBarContainer> </FormProvider> diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect.tsx new file mode 100644 index 000000000000..cfc683510006 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect.tsx @@ -0,0 +1,87 @@ +import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown'; +import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; +import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { AppPath } from '@/types/AppPath'; +import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useEffect } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { useNavigate, useParams } from 'react-router-dom'; +import { isDefined } from 'twenty-ui'; +import { z } from 'zod'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const settingsDataModelFieldTypeFormSchema = z.object({ + type: z.enum( + Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [ + SettingsFieldType, + ...SettingsFieldType[], + ], + ), +}); + +export type SettingsDataModelFieldTypeFormValues = z.infer< + typeof settingsDataModelFieldTypeFormSchema +>; + +export const SettingsObjectNewFieldSelect = () => { + const navigate = useNavigate(); + const { objectSlug = '' } = useParams(); + const { findActiveObjectMetadataItemBySlug } = + useFilteredObjectMetadataItems(); + const activeObjectMetadataItem = + findActiveObjectMetadataItemBySlug(objectSlug); + const formMethods = useForm({ + resolver: zodResolver(settingsDataModelFieldTypeFormSchema), + defaultValues: { + type: FieldMetadataType.Text, + }, + }); + const excludedFieldTypes: SettingsFieldType[] = ( + [ + FieldMetadataType.Numeric, + FieldMetadataType.RichText, + FieldMetadataType.Actor, + ] as const + ).filter(isDefined); + + useEffect(() => { + if (!activeObjectMetadataItem) { + navigate(AppPath.NotFound); + } + }, [activeObjectMetadataItem, navigate]); + + if (!activeObjectMetadataItem) return null; + + return ( + <RecordFieldValueSelectorContextProvider> + <FormProvider // eslint-disable-next-line react/jsx-props-no-spreading + {...formMethods} + > + <SubMenuTopBarContainer + title="1. Select a field type" + links={[ + { children: 'Workspace', href: '/settings/workspace' }, + { children: 'Objects', href: '/settings/objects' }, + { + children: activeObjectMetadataItem.labelPlural, + href: `/settings/objects/${objectSlug}`, + }, + { children: <SettingsDataModelNewFieldBreadcrumbDropDown /> }, + ]} + > + <SettingsPageContainer> + <SettingsObjectNewFieldSelector + objectSlug={objectSlug} + excludedFieldTypes={excludedFieldTypes} + /> + </SettingsPageContainer> + </SubMenuTopBarContainer> + </FormProvider> + </RecordFieldValueSelectorContextProvider> + ); +}; diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx deleted file mode 100644 index d608e79967b7..000000000000 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import styled from '@emotion/styled'; -import { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import { H2Title, IconHierarchy2, IconPlus } from 'twenty-ui'; - -import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; -import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; - -import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; -import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; -import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; -import { AppPath } from '@/types/AppPath'; -import { SettingsPath } from '@/types/SettingsPath'; -import { Button } from '@/ui/input/button/components/Button'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; -import { Section } from '@/ui/layout/section/components/Section'; -import { useRecoilState } from 'recoil'; -import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable'; - -const StyledSection = styled(Section)` - display: flex; - flex-direction: column; -`; - -const StyledAddCustomFieldButton = styled(Button)` - align-self: flex-end; - margin-top: ${({ theme }) => theme.spacing(2)}; -`; - -export const SettingsObjectNewFieldStep1 = () => { - const navigate = useNavigate(); - - const { objectSlug = '' } = useParams(); - const { findActiveObjectMetadataItemBySlug } = - useFilteredObjectMetadataItems(); - - const activeObjectMetadataItem = - findActiveObjectMetadataItemBySlug(objectSlug); - - const [settingsObjectFields] = useRecoilState( - settingsObjectFieldsFamilyState({ - objectMetadataItemId: activeObjectMetadataItem?.id, - }), - ); - - const { activateMetadataField, deactivateMetadataField } = - useFieldMetadataItem(); - - const canSave = settingsObjectFields?.some( - (field, index) => - field.isActive !== activeObjectMetadataItem?.fields[index].isActive, - ); - - const handleSave = async () => { - if (!activeObjectMetadataItem || !settingsObjectFields) { - return; - } - - await Promise.all( - settingsObjectFields.map((fieldMetadataItem, index) => { - if ( - fieldMetadataItem.isActive === - activeObjectMetadataItem.fields[index].isActive - ) { - return undefined; - } - - return fieldMetadataItem.isActive - ? activateMetadataField(fieldMetadataItem) - : deactivateMetadataField(fieldMetadataItem); - }), - ); - - navigate(`/settings/objects/${objectSlug}`); - }; - - useEffect(() => { - if (!activeObjectMetadataItem) { - navigate(AppPath.NotFound); - return; - } - }, [activeObjectMetadataItem, navigate]); - - if (!activeObjectMetadataItem) return null; - - return ( - <SubMenuTopBarContainer - Icon={IconHierarchy2} - links={[ - { - children: 'Workspace', - href: getSettingsPagePath(SettingsPath.Workspace), - }, - { children: 'Objects', href: '/settings/objects' }, - { - children: activeObjectMetadataItem.labelPlural, - href: `/settings/objects/${objectSlug}`, - }, - { children: 'New Field' }, - ]} - actionButton={ - !activeObjectMetadataItem.isRemote && ( - <SaveAndCancelButtons - isSaveDisabled={!canSave} - onCancel={() => navigate(`/settings/objects/${objectSlug}`)} - onSave={handleSave} - /> - ) - } - > - <SettingsPageContainer> - <StyledSection> - <H2Title - title="Check deactivated fields" - description="Before creating a custom field, check if it already exists in the deactivated section." - /> - <SettingsObjectFieldTable - objectMetadataItem={activeObjectMetadataItem} - mode="new-field" - /> - <StyledAddCustomFieldButton - Icon={IconPlus} - title="Add Custom Field" - size="small" - variant="secondary" - to={`/settings/objects/${objectSlug}/new-field/step-2`} - /> - </StyledSection> - </SettingsPageContainer> - </SubMenuTopBarContainer> - ); -}; diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx index 6adcb2a9db59..215ae748bc99 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx @@ -4,12 +4,10 @@ import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; -import { IconHierarchy2 } from 'twenty-ui'; export const SettingsObjectOverview = () => { return ( <SubMenuTopBarContainer - Icon={IconHierarchy2} links={[ { children: 'Workspace', diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx index c31af9513c48..7c7e280569d7 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx @@ -1,12 +1,6 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { - H2Title, - IconChevronRight, - IconHierarchy2, - IconPlus, - IconSearch, -} from 'twenty-ui'; +import { H2Title, IconChevronRight, IconPlus, IconSearch } from 'twenty-ui'; import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; @@ -133,7 +127,6 @@ export const SettingsObjects = () => { ); return ( <SubMenuTopBarContainer - Icon={IconHierarchy2} title="Data model" actionButton={ <UndecoratedLink to={getSettingsPagePath(SettingsPath.NewObject)}> diff --git a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx index a90160ea1301..8815ff456414 100644 --- a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx @@ -28,6 +28,9 @@ export type Story = StoryObj<typeof SettingsNewObject>; export const WithStandardSelected: Story = { play: async () => { const canvas = within(document.body); + + await canvas.findByText('New Object'); + const listingInput = await canvas.findByPlaceholderText('Listing'); const pluralInput = await canvas.findByPlaceholderText('Listings'); const descriptionInput = await canvas.findByPlaceholderText( diff --git a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldStep2.stories.tsx b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldConfigure.stories.tsx similarity index 63% rename from packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldStep2.stories.tsx rename to packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldConfigure.stories.tsx index ccd81beaf145..82a36add751a 100644 --- a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldStep2.stories.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldConfigure.stories.tsx @@ -1,5 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { userEvent, within } from '@storybook/test'; +import { SettingsObjectNewFieldConfigure } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure'; import { PageDecorator, @@ -7,15 +8,13 @@ import { } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { SettingsObjectNewFieldStep2 } from '../../SettingsObjectNewField/SettingsObjectNewFieldStep2'; - const meta: Meta<PageDecoratorArgs> = { title: - 'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldStep2', - component: SettingsObjectNewFieldStep2, + 'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldConfigure', + component: SettingsObjectNewFieldConfigure, decorators: [PageDecorator], args: { - routePath: '/settings/objects/:objectSlug/new-field/step-2', + routePath: '/settings/objects/:objectSlug/new-field/configure', routeParams: { ':objectSlug': 'companies' }, }, parameters: { @@ -25,21 +24,11 @@ const meta: Meta<PageDecoratorArgs> = { export default meta; -export type Story = StoryObj<typeof SettingsObjectNewFieldStep2>; +export type Story = StoryObj<typeof SettingsObjectNewFieldConfigure>; export const Default: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - await canvas.findByText('Objects'); - await canvas.findByText('1. Select a field type'); - - const searchInput = await canvas.findByPlaceholderText('Search a type'); - - await userEvent.type(searchInput, 'Num'); - - const numberTypeButton = await canvas.findByText('Number'); - - await userEvent.click(numberTypeButton); await canvas.findByText('2. Configure field'); @@ -49,11 +38,10 @@ export const Default: Story = { const descriptionInput = await canvas.findByPlaceholderText( 'Write a description', ); - await userEvent.type(descriptionInput, 'Test description'); const saveButton = await canvas.findByText('Save'); - + await new Promise((resolve) => setTimeout(resolve, 5000)); await userEvent.click(saveButton); }, }; diff --git a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldSelect.stories.tsx b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldSelect.stories.tsx new file mode 100644 index 000000000000..90114698c3b1 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldSelect.stories.tsx @@ -0,0 +1,41 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/test'; +import { SettingsObjectNewFieldSelect } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect'; + +import { + PageDecorator, + PageDecoratorArgs, +} from '~/testing/decorators/PageDecorator'; +import { graphqlMocks } from '~/testing/graphqlMocks'; + +const meta: Meta<PageDecoratorArgs> = { + title: + 'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldSelect', + component: SettingsObjectNewFieldSelect, + decorators: [PageDecorator], + args: { + routePath: '/settings/objects/:objectSlug/new-field/select', + routeParams: { ':objectSlug': 'companies' }, + }, + parameters: { + msw: graphqlMocks, + }, +}; + +export default meta; + +export type Story = StoryObj<typeof SettingsObjectNewFieldSelect>; + +export const Default: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByText('Objects'); + await canvas.findByText('1. Select a field type'); + const searchInput = await canvas.findByPlaceholderText('Search a type'); + await userEvent.type(searchInput, 'Rela'); + await new Promise((resolve) => setTimeout(resolve, 1500)); + await userEvent.clear(searchInput); + await userEvent.type(searchInput, 'Num'); + await new Promise((resolve) => setTimeout(resolve, 1500)); + }, +}; diff --git a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldStep1.stories.tsx b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldStep1.stories.tsx deleted file mode 100644 index 752bfcc713d4..000000000000 --- a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsObjectNewField/SettingsObjectNewFieldStep1.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; - -import { - PageDecorator, - PageDecoratorArgs, -} from '~/testing/decorators/PageDecorator'; -import { graphqlMocks } from '~/testing/graphqlMocks'; - -import { SettingsObjectNewFieldStep1 } from '../../SettingsObjectNewField/SettingsObjectNewFieldStep1'; - -const meta: Meta<PageDecoratorArgs> = { - title: - 'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldStep1', - component: SettingsObjectNewFieldStep1, - decorators: [PageDecorator], - args: { - routePath: '/settings/objects/:objectSlug/new-field/step-1', - routeParams: { ':objectSlug': 'companies' }, - }, - parameters: { - msw: graphqlMocks, - }, -}; - -export default meta; - -export type Story = StoryObj<typeof SettingsObjectNewFieldStep1>; - -export const Default: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - await canvas.findByText('Objects'); - await canvas.findByText('Companies'); - await canvas.findByText('Check deactivated fields'); - await canvas.findByText('Add Custom Field'); - }, -}; diff --git a/packages/twenty-front/src/pages/settings/data-model/constants/DefaultIconsByFieldType.ts b/packages/twenty-front/src/pages/settings/data-model/constants/DefaultIconsByFieldType.ts new file mode 100644 index 000000000000..3b8f482b17fa --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/constants/DefaultIconsByFieldType.ts @@ -0,0 +1,27 @@ +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const DEFAULT_ICONS_BY_FIELD_TYPE: Record<FieldMetadataType, string> = { + [FieldMetadataType.Address]: 'IconMap', + [FieldMetadataType.Boolean]: 'IconToggleLeft', + [FieldMetadataType.Currency]: 'IconMoneybag', + [FieldMetadataType.Date]: 'IconCalendarEvent', + [FieldMetadataType.DateTime]: 'IconCalendarClock', + [FieldMetadataType.FullName]: 'IconUserCircle', + [FieldMetadataType.MultiSelect]: 'IconTags', + [FieldMetadataType.Number]: 'IconNumber9', + [FieldMetadataType.Rating]: 'IconStar', + [FieldMetadataType.RawJson]: 'IconBraces', + [FieldMetadataType.Relation]: 'IconRelationOneToMany', + [FieldMetadataType.Select]: 'IconTag', + [FieldMetadataType.Text]: 'IconTypography', + [FieldMetadataType.Uuid]: 'IconId', + [FieldMetadataType.Array]: 'IconBracketsContain', + [FieldMetadataType.Emails]: 'IconMail', + [FieldMetadataType.Links]: 'IconWorld', + [FieldMetadataType.Phones]: 'IconPhone', + [FieldMetadataType.Actor]: 'IconUsers', + [FieldMetadataType.Numeric]: 'IconUsers', + [FieldMetadataType.Position]: 'IconUsers', + [FieldMetadataType.RichText]: 'IconUsers', + [FieldMetadataType.TsVector]: 'IconUsers', +}; diff --git a/packages/twenty-front/src/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem.ts b/packages/twenty-front/src/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem.ts index 2a965c0195b5..d221be0b6ccc 100644 --- a/packages/twenty-front/src/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem.ts +++ b/packages/twenty-front/src/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem.ts @@ -1,8 +1,11 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { FieldType } from '@/settings/data-model/types/FieldType'; +import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType'; import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig'; +import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings'; import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem'; import { getSettingsObjectFieldType } from '~/pages/settings/data-model/utils/getSettingsObjectFieldType'; @@ -29,13 +32,17 @@ export const useMapFieldMetadataItemToSettingsObjectDetailTableItem = ( objectMetadataItem, ); + const fieldMetadataType = fieldMetadataItem.type as FieldType; + return { fieldMetadataItem, fieldType: fieldType ?? '', dataType: - relationObjectMetadataItem?.labelPlural ?? - getSettingsFieldTypeConfig(fieldMetadataItem.type)?.label ?? - '', + (relationObjectMetadataItem?.labelPlural ?? + isFieldTypeSupportedInSettings(fieldMetadataType)) + ? getSettingsFieldTypeConfig(fieldMetadataType as SettingsFieldType) + ?.label + : '', label: fieldMetadataItem.label, identifierType: identifierType, objectMetadataItem, diff --git a/packages/twenty-front/src/pages/settings/data-model/types/SettingsObjectIndexesTableItem.ts b/packages/twenty-front/src/pages/settings/data-model/types/SettingsObjectIndexesTableItem.ts new file mode 100644 index 000000000000..eccc260585cb --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/types/SettingsObjectIndexesTableItem.ts @@ -0,0 +1,9 @@ +import { IndexType } from '~/generated-metadata/graphql'; + +export type SettingsObjectIndexesTableItem = { + name: string; + indexType: IndexType; + isUnique: boolean; + indexWhereClause?: string | null; + indexFields: string; +}; diff --git a/packages/twenty-front/src/pages/settings/developers/SettingsDevelopers.tsx b/packages/twenty-front/src/pages/settings/developers/SettingsDevelopers.tsx index 4cb6cf888713..35ce036694e1 100644 --- a/packages/twenty-front/src/pages/settings/developers/SettingsDevelopers.tsx +++ b/packages/twenty-front/src/pages/settings/developers/SettingsDevelopers.tsx @@ -1,5 +1,6 @@ +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import styled from '@emotion/styled'; -import { H2Title, IconCode, IconPlus } from 'twenty-ui'; +import { H2Title, IconPlus, MOBILE_VIEWPORT } from 'twenty-ui'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable'; @@ -15,12 +16,22 @@ const StyledButtonContainer = styled.div` display: flex; justify-content: flex-end; padding-top: ${({ theme }) => theme.spacing(2)}; + @media (max-width: ${MOBILE_VIEWPORT}px) { + padding-top: ${({ theme }) => theme.spacing(5)}; + } +`; + +const StyledContainer = styled.div<{ isMobile: boolean }>` + display: flex; + flex-direction: column; + overflow: hidden; + gap: ${({ theme }) => theme.spacing(2)}; `; export const SettingsDevelopers = () => { + const isMobile = useIsMobile(); return ( <SubMenuTopBarContainer - Icon={IconCode} title="Developers" actionButton={<SettingsReadDocumentationButton />} links={[ @@ -32,38 +43,40 @@ export const SettingsDevelopers = () => { ]} > <SettingsPageContainer> - <Section> - <H2Title - title="API keys" - description="Active APIs keys created by you or your team." - /> - <SettingsApiKeysTable /> - <StyledButtonContainer> - <Button - Icon={IconPlus} - title="Create API key" - size="small" - variant="secondary" - to={'/settings/developers/api-keys/new'} + <StyledContainer isMobile={isMobile}> + <Section> + <H2Title + title="API keys" + description="Active APIs keys created by you or your team." /> - </StyledButtonContainer> - </Section> - <Section> - <H2Title - title="Webhooks" - description="Establish Webhook endpoints for notifications on asynchronous events." - /> - <SettingsWebhooksTable /> - <StyledButtonContainer> - <Button - Icon={IconPlus} - title="Create Webhook" - size="small" - variant="secondary" - to={'/settings/developers/webhooks/new'} + <SettingsApiKeysTable /> + <StyledButtonContainer> + <Button + Icon={IconPlus} + title="Create API key" + size="small" + variant="secondary" + to={'/settings/developers/api-keys/new'} + /> + </StyledButtonContainer> + </Section> + <Section> + <H2Title + title="Webhooks" + description="Establish Webhook endpoints for notifications on asynchronous events." /> - </StyledButtonContainer> - </Section> + <SettingsWebhooksTable /> + <StyledButtonContainer> + <Button + Icon={IconPlus} + title="Create Webhook" + size="small" + variant="secondary" + to={'/settings/developers/webhooks/new'} + /> + </StyledButtonContainer> + </Section> + </StyledContainer> </SettingsPageContainer> </SubMenuTopBarContainer> ); diff --git a/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksDetail.stories.tsx b/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksDetail.stories.tsx index ec430fcf3058..a77e38d73a5b 100644 --- a/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksDetail.stories.tsx +++ b/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksDetail.stories.tsx @@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { graphql, HttpResponse } from 'msw'; -import { SettingsDevelopersWebhooksDetail } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail'; +import { SettingsDevelopersWebhooksDetail } from '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail'; import { PageDecorator, PageDecoratorArgs, diff --git a/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksNew.stories.tsx b/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksNew.stories.tsx index dd4adfb71305..430349183258 100644 --- a/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksNew.stories.tsx +++ b/packages/twenty-front/src/pages/settings/developers/__stories__/webhooks/SettingsDevelopersWebhooksNew.stories.tsx @@ -1,7 +1,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; -import { SettingsDevelopersWebhooksNew } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew'; +import { SettingsDevelopersWebhooksNew } from '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew'; import { PageDecorator, PageDecoratorArgs, diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx index 107ce698d43d..7a9651b6893a 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx @@ -4,7 +4,7 @@ import { DateTime } from 'luxon'; import { useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useRecoilState } from 'recoil'; -import { H2Title, IconCode, IconRepeat, IconTrash } from 'twenty-ui'; +import { H2Title, IconRepeat, IconTrash } from 'twenty-ui'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; @@ -145,7 +145,6 @@ export const SettingsDevelopersApiKeyDetail = () => { <> {apiKeyData?.name && ( <SubMenuTopBarContainer - Icon={IconCode} title={apiKeyData?.name} links={[ { diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx index 5e4ebe5cf740..92951f0a5b43 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx @@ -1,7 +1,7 @@ import { DateTime } from 'luxon'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { H2Title, IconCode } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; @@ -65,7 +65,6 @@ export const SettingsDevelopersApiKeysNew = () => { const canSave = !!formValues.name && createOneApiKey; return ( <SubMenuTopBarContainer - Icon={IconCode} title="New key" links={[ { diff --git a/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx similarity index 90% rename from packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx rename to packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx index a45227408fb5..f88ba926c6b8 100644 --- a/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { H2Title, IconCode, IconTrash } from 'twenty-ui'; +import { H2Title, IconTrash } from 'twenty-ui'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -11,6 +11,8 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { Webhook } from '@/settings/developers/types/webhook/Webhook'; +import { SettingsDeveloppersWebhookUsageGraph } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph'; +import { SettingsDevelopersWebhookUsageGraphEffect } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraphEffect'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { Button } from '@/ui/input/button/components/Button'; @@ -20,6 +22,7 @@ import { TextInput } from '@/ui/input/components/TextInput'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; const StyledFilterRow = styled.div` display: flex; @@ -63,6 +66,8 @@ export const SettingsDevelopersWebhooksDetail = () => { navigate(developerPath); }; + const isAnalyticsV2Enabled = useIsFeatureEnabled('IS_ANALYTICS_V2_ENABLED'); + const fieldTypeOptions = [ { value: '*', label: 'All Objects' }, ...objectMetadataItems.map((item) => ({ @@ -93,7 +98,6 @@ export const SettingsDevelopersWebhooksDetail = () => { return ( <SubMenuTopBarContainer - Icon={IconCode} title={webhookData.targetUrl} links={[ { @@ -174,6 +178,14 @@ export const SettingsDevelopersWebhooksDetail = () => { /> </StyledFilterRow> </Section> + {isAnalyticsV2Enabled ? ( + <> + <SettingsDevelopersWebhookUsageGraphEffect webhookId={webhookId} /> + <SettingsDeveloppersWebhookUsageGraph /> + </> + ) : ( + <></> + )} <Section> <H2Title title="Danger zone" description="Delete this integration" /> <Button diff --git a/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew.tsx b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew.tsx similarity index 97% rename from packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew.tsx rename to packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew.tsx index 7b75bd09f0fe..b473d3f28c76 100644 --- a/packages/twenty-front/src/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew.tsx +++ b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { H2Title, IconCode, isDefined } from 'twenty-ui'; +import { H2Title, isDefined } from 'twenty-ui'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; @@ -64,7 +64,6 @@ export const SettingsDevelopersWebhooksNew = () => { return ( <SubMenuTopBarContainer - Icon={IconCode} title="New Webhook" links={[ { diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx index 2136ae1ae858..eb8ddd11f2cf 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { H2Title, IconSettings } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { useGetDatabaseConnections } from '@/databases/hooks/useGetDatabaseConnections'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; @@ -42,7 +42,6 @@ export const SettingsIntegrationDatabase = () => { return ( <SubMenuTopBarContainer - Icon={IconSettings} title={integration.text} links={[ { diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx index 5203f01de89e..92de8008e048 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx @@ -1,5 +1,3 @@ -import { IconSettings } from 'twenty-ui'; - import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsIntegrationEditDatabaseConnectionContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; @@ -9,7 +7,6 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer' export const SettingsIntegrationEditDatabaseConnection = () => { return ( <SubMenuTopBarContainer - Icon={IconSettings} title="Edit connection" links={[ { diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx index 1440bd66100b..e10f696446f6 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx @@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router-dom'; -import { H2Title, IconSettings } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { z } from 'zod'; import { useCreateOneDatabaseConnection } from '@/databases/hooks/useCreateOneDatabaseConnection'; @@ -131,7 +131,6 @@ export const SettingsIntegrationNewDatabaseConnection = () => { return ( <SubMenuTopBarContainer - Icon={IconSettings} title="New" links={[ { diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx index 04fe78e64422..76f1aefa24c8 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx @@ -1,5 +1,3 @@ -import { IconSettings } from 'twenty-ui'; - import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsIntegrationDatabaseConnectionShowContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; @@ -9,7 +7,6 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer' export const SettingsIntegrationShowDatabaseConnection = () => { return ( <SubMenuTopBarContainer - Icon={IconSettings} title="Database Connection" links={[ { diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx index acc3c5cc6777..bfd5db517c93 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx @@ -4,14 +4,12 @@ import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; -import { IconApps } from 'twenty-ui'; export const SettingsIntegrations = () => { const integrationCategories = useSettingsIntegrationCategories(); return ( <SubMenuTopBarContainer - Icon={IconApps} title="Integrations" links={[ { 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/profile/appearance/components/DateTimeSettingsDateFormatSelect.tsx b/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsDateFormatSelect.tsx index c17f7a8e712c..dcadd5ba9460 100644 --- a/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsDateFormatSelect.tsx +++ b/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsDateFormatSelect.tsx @@ -1,6 +1,7 @@ import { formatInTimeZone } from 'date-fns-tz'; import { DateFormat } from '@/localization/constants/DateFormat'; +import { detectDateFormat } from '@/localization/utils/detectDateFormat'; import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { Select } from '@/ui/input/components/Select'; @@ -15,7 +16,12 @@ export const DateTimeSettingsDateFormatSelect = ({ timeZone, value, }: DateTimeSettingsDateFormatSelectProps) => { - const setTimeZone = timeZone === 'system' ? detectTimeZone() : timeZone; + const systemTimeZone = detectTimeZone(); + + const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone; + + const systemDateFormat = detectDateFormat(); + return ( <Select dropdownId="datetime-settings-date-format" @@ -25,13 +31,17 @@ export const DateTimeSettingsDateFormatSelect = ({ value={value} options={[ { - label: `System settings`, + label: `System settings - ${formatInTimeZone( + Date.now(), + usedTimeZone, + systemDateFormat, + )}`, value: DateFormat.SYSTEM, }, { label: `${formatInTimeZone( Date.now(), - setTimeZone, + usedTimeZone, DateFormat.MONTH_FIRST, )}`, value: DateFormat.MONTH_FIRST, @@ -39,7 +49,7 @@ export const DateTimeSettingsDateFormatSelect = ({ { label: `${formatInTimeZone( Date.now(), - setTimeZone, + usedTimeZone, DateFormat.DAY_FIRST, )}`, value: DateFormat.DAY_FIRST, @@ -47,7 +57,7 @@ export const DateTimeSettingsDateFormatSelect = ({ { label: `${formatInTimeZone( Date.now(), - setTimeZone, + usedTimeZone, DateFormat.YEAR_FIRST, )}`, value: DateFormat.YEAR_FIRST, diff --git a/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeFormatSelect.tsx b/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeFormatSelect.tsx index 9aa24254b4b1..e06cde4cc0ff 100644 --- a/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeFormatSelect.tsx +++ b/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeFormatSelect.tsx @@ -1,6 +1,7 @@ import { formatInTimeZone } from 'date-fns-tz'; import { TimeFormat } from '@/localization/constants/TimeFormat'; +import { detectTimeFormat } from '@/localization/utils/detectTimeFormat'; import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { Select } from '@/ui/input/components/Select'; @@ -15,7 +16,12 @@ export const DateTimeSettingsTimeFormatSelect = ({ timeZone, value, }: DateTimeSettingsTimeFormatSelectProps) => { - const setTimeZone = timeZone === 'system' ? detectTimeZone() : timeZone; + const systemTimeZone = detectTimeZone(); + + const usedTimeZone = timeZone === 'system' ? systemTimeZone : timeZone; + + const systemTimeFormat = detectTimeFormat(); + return ( <Select dropdownId="datetime-settings-time-format" @@ -25,13 +31,17 @@ export const DateTimeSettingsTimeFormatSelect = ({ value={value} options={[ { - label: 'System settings', + label: `System Settings - ${formatInTimeZone( + Date.now(), + usedTimeZone, + systemTimeFormat, + )}`, value: TimeFormat.SYSTEM, }, { label: `24h (${formatInTimeZone( Date.now(), - setTimeZone, + usedTimeZone, TimeFormat.HOUR_24, )})`, value: TimeFormat.HOUR_24, @@ -39,7 +49,7 @@ export const DateTimeSettingsTimeFormatSelect = ({ { label: `12h (${formatInTimeZone( Date.now(), - setTimeZone, + usedTimeZone, TimeFormat.HOUR_12, )})`, value: TimeFormat.HOUR_12, diff --git a/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeZoneSelect.tsx b/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeZoneSelect.tsx index a48dea5db8de..348cfded0779 100644 --- a/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeZoneSelect.tsx +++ b/packages/twenty-front/src/pages/settings/profile/appearance/components/DateTimeSettingsTimeZoneSelect.tsx @@ -12,19 +12,22 @@ export const DateTimeSettingsTimeZoneSelect = ({ value = detectTimeZone(), onChange, }: DateTimeSettingsTimeZoneSelectProps) => { + const systemTimeZone = detectTimeZone(); + + const systemTimeZoneOption = findAvailableTimeZoneOption(systemTimeZone); + return ( <Select dropdownId="settings-accounts-calendar-time-zone" dropdownWidth={416} label="Time zone" fullWidth - value={ - value === 'system' - ? 'System settings' - : findAvailableTimeZoneOption(value)?.value - } + value={value} options={[ - { label: 'System settings', value: 'system' }, + { + label: `System settings - ${systemTimeZoneOption.label}`, + value: 'system', + }, ...AVAILABLE_TIMEZONE_OPTIONS, ]} onChange={onChange} diff --git a/packages/twenty-front/src/pages/settings/profile/appearance/components/SettingsAppearance.tsx b/packages/twenty-front/src/pages/settings/profile/appearance/components/SettingsAppearance.tsx index f3fe094bcb15..85ca252abfba 100644 --- a/packages/twenty-front/src/pages/settings/profile/appearance/components/SettingsAppearance.tsx +++ b/packages/twenty-front/src/pages/settings/profile/appearance/components/SettingsAppearance.tsx @@ -1,4 +1,4 @@ -import { H2Title, IconColorSwatch } from 'twenty-ui'; +import { H2Title } from 'twenty-ui'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; @@ -14,7 +14,6 @@ export const SettingsAppearance = () => { return ( <SubMenuTopBarContainer - Icon={IconColorSwatch} title="Experience" links={[ { diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx index efd06b555139..2934066fd1a4 100644 --- a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx +++ b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionDetail.tsx @@ -21,9 +21,10 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { IconCode, IconFunction, IconSettings, IconTestPipe } from 'twenty-ui'; +import { IconCode, IconSettings, IconTestPipe } from 'twenty-ui'; import { usePreventOverlapCallback } from '~/hooks/usePreventOverlapCallback'; import { isDefined } from '~/utils/isDefined'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; const TAB_LIST_COMPONENT_ID = 'serverless-function-detail'; @@ -81,14 +82,24 @@ export const SettingsServerlessFunctionDetail = () => { }; }; + const onCodeChange = async (filePath: string, value: string) => { + setFormValues((prevState) => ({ + ...prevState, + code: { ...prevState.code, [filePath]: value }, + })); + await handleSave(); + }; + const resetDisabled = - !isDefined(latestVersionCode) || latestVersionCode === formValues.code; - const publishDisabled = !isCodeValid || latestVersionCode === formValues.code; + !isDefined(latestVersionCode) || + isDeeplyEqual(latestVersionCode, formValues.code); + const publishDisabled = + !isCodeValid || isDeeplyEqual(latestVersionCode, formValues.code); const handleReset = async () => { try { const newState = { - code: latestVersionCode || '', + code: latestVersionCode || {}, }; setFormValues((prevState) => ({ ...prevState, @@ -166,18 +177,30 @@ export const SettingsServerlessFunctionDetail = () => { { id: 'settings', title: 'Settings', Icon: IconSettings }, ]; + const files = formValues.code + ? Object.keys(formValues.code) + .map((key) => { + return { + path: key, + language: key === '.env' ? 'ini' : 'typescript', + content: formValues.code?.[key] || '', + }; + }) + .reverse() + : []; + const renderActiveTabContent = () => { switch (activeTabId) { case 'editor': return ( <SettingsServerlessFunctionCodeEditorTab - formValues={formValues} + files={files} handleExecute={handleExecute} handlePublish={handlePublish} handleReset={handleReset} resetDisabled={resetDisabled} publishDisabled={publishDisabled} - onChange={onChange} + onChange={onCodeChange} setIsCodeValid={setIsCodeValid} /> ); @@ -204,7 +227,6 @@ export const SettingsServerlessFunctionDetail = () => { return ( !loading && ( <SubMenuTopBarContainer - Icon={IconFunction} title={formValues.name} links={[ { 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..9cfd9b04ca42 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'; @@ -6,12 +5,11 @@ import { Button } from '@/ui/input/button/components/Button'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; -import { IconFunction, IconPlus } from 'twenty-ui'; +import { IconPlus } from 'twenty-ui'; export const SettingsServerlessFunctions = () => { return ( <SubMenuTopBarContainer - Icon={IconFunction} title="Functions" actionButton={ <UndecoratedLink @@ -35,11 +33,9 @@ export const SettingsServerlessFunctions = () => { }, ]} > - <SettingsPageContainer> - <Section> - <SettingsServerlessFunctionsTable /> - </Section> - </SettingsPageContainer> + <Section> + <SettingsServerlessFunctionsTable /> + </Section> </SubMenuTopBarContainer> ); }; diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionsNew.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionsNew.tsx index 24308d53a3d1..3e43717544ce 100644 --- a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionsNew.tsx +++ b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctionsNew.tsx @@ -9,11 +9,9 @@ import { ServerlessFunctionNewFormValues } from '@/settings/serverless-functions import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; -import { DEFAULT_CODE } from '@/ui/input/code-editor/components/CodeEditor'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useState } from 'react'; import { Key } from 'ts-key-enum'; -import { IconFunction } from 'twenty-ui'; import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount'; import { isDefined } from '~/utils/isDefined'; @@ -31,7 +29,6 @@ export const SettingsServerlessFunctionsNew = () => { const newServerlessFunction = await createOneServerlessFunction({ name: formValues.name, description: formValues.description, - code: DEFAULT_CODE, }); if (!isDefined(newServerlessFunction?.data)) { @@ -79,7 +76,6 @@ export const SettingsServerlessFunctionsNew = () => { return ( <SubMenuTopBarContainer - Icon={IconFunction} title="New Function" links={[ { diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx index 8543e0376a6e..f99523f7d5d9 100644 --- a/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx +++ b/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx @@ -1,4 +1,3 @@ -import { DEFAULT_CODE } from '@/ui/input/code-editor/components/CodeEditor'; import { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { graphql, http, HttpResponse } from 'msw'; @@ -38,7 +37,6 @@ const meta: Meta<PageDecoratorArgs> = { description: '', syncStatus: 'READY', runtime: 'nodejs18.x', - sourceCodeHash: '42d2734b3dc8a7b45a16803ed7f417bc', updatedAt: '2024-02-24T10:23:10.673Z', createdAt: '2024-02-24T10:23:10.673Z', }, @@ -46,7 +44,7 @@ const meta: Meta<PageDecoratorArgs> = { }); }), http.get(getImageAbsoluteURI(SOURCE_CODE_FULL_PATH) || '', () => { - return HttpResponse.text(DEFAULT_CODE); + return HttpResponse.text('export const handler = () => {}'); }), ], }, 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 2381241c4a06..244772c809e6 100644 --- a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx @@ -12,8 +12,7 @@ import { import { RecoilRoot } from 'recoil'; import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; -import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; -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'; @@ -22,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'; @@ -65,31 +65,33 @@ const ApolloStorybookDevLogEffect = () => { const Providers = () => { return ( <RecoilRoot> - <RecoilDebugObserverEffect /> - <ApolloProvider client={mockedApolloClient}> - <ApolloStorybookDevLogEffect /> - <ApolloMetadataClientMockedProvider> - <UserProviderEffect /> - <UserProvider> - <ClientConfigProviderEffect /> - <ClientConfigProvider> - <FullHeightStorybookLayout> - <HelmetProvider> - <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> - <IconsProvider> - <ObjectMetadataItemsProvider> - <PrefetchDataProvider> - <Outlet /> - </PrefetchDataProvider> - </ObjectMetadataItemsProvider> - </IconsProvider> - </SnackBarProviderScope> - </HelmetProvider> - </FullHeightStorybookLayout> - </ClientConfigProvider> - </UserProvider> - </ApolloMetadataClientMockedProvider> - </ApolloProvider> + <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> + <RecoilDebugObserverEffect /> + <ApolloProvider client={mockedApolloClient}> + <ApolloStorybookDevLogEffect /> + <ClientConfigProviderEffect /> + <ClientConfigProvider> + <UserProviderEffect /> + <UserProvider> + <ApolloMetadataClientMockedProvider> + <ObjectMetadataItemsProvider> + <FullHeightStorybookLayout> + <HelmetProvider> + <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> + <IconsProvider> + <PrefetchDataProvider> + <Outlet /> + </PrefetchDataProvider> + </IconsProvider> + </SnackBarProviderScope> + </HelmetProvider> + </FullHeightStorybookLayout> + </ObjectMetadataItemsProvider> + </ApolloMetadataClientMockedProvider> + </UserProvider> + </ClientConfigProvider> + </ApolloProvider> + </SnackBarProviderScope> </RecoilRoot> ); }; diff --git a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx index 11774aba44bf..ba9c9dfeb224 100644 --- a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx @@ -24,7 +24,7 @@ export const RecordTableDecorator: Decorator = (Story) => { onCellMouseEnter: () => {}, onCloseTableCell: () => {}, onOpenTableCell: () => {}, - onContextMenu: () => {}, + onActionMenuDropdownOpened: () => {}, onMoveFocus: () => {}, onMoveSoftFocusToCell: () => {}, onUpsertRecord: () => {}, 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 7c16b15a04e9..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: { @@ -416,6 +436,133 @@ export const graphqlMocks = { }, }); }), + graphql.query('FindManyWorkflows', () => { + return HttpResponse.json({ + data: { + workflows: { + __typename: 'WorkflowConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + endCursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + }, + edges: [ + { + __typename: 'WorkflowEdge', + cursor: + 'eyJpZCI6IjIwMGMxNTA4LWYxMDItNGJiOS1hZjMyLWVkYTU1MjM5YWU2MSJ9', + node: { + __typename: 'Workflow', + id: '200c1508-f102-4bb9-af32-eda55239ae61', + }, + }, + ], + }, + }, + }); + }), + graphql.query('FindOneWorkflow', () => { + return HttpResponse.json({ + data: { + workflow: { + __typename: 'Workflow', + id: '200c1508-f102-4bb9-af32-eda55239ae61', + name: '1231 qqerrt', + statuses: null, + lastPublishedVersionId: '', + deletedAt: null, + updatedAt: '2024-09-19T10:10:04.505Z', + position: 0, + createdAt: '2024-09-19T10:10:04.505Z', + favorites: { + __typename: 'FavoriteConnection', + edges: [], + }, + eventListeners: { + __typename: 'WorkflowEventListenerConnection', + edges: [], + }, + runs: { + __typename: 'WorkflowRunConnection', + edges: [], + }, + versions: { + __typename: 'WorkflowVersionConnection', + edges: [ + { + __typename: 'WorkflowVersionEdge', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:04.725Z', + status: 'DRAFT', + name: 'v1', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f0', + trigger: { + type: 'DATABASE_EVENT', + settings: { + eventName: 'note.created', + }, + }, + deletedAt: null, + workflowId: '200c1508-f102-4bb9-af32-eda55239ae61', + }, + }, + ], + }, + }, + }, + }); + }), + graphql.query('FindManyWorkflowVersions', () => { + return HttpResponse.json({ + data: { + workflowVersions: { + __typename: 'WorkflowVersionConnection', + totalCount: 1, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + endCursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + }, + edges: [ + { + __typename: 'WorkflowVersionEdge', + cursor: + 'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9', + node: { + __typename: 'WorkflowVersion', + updatedAt: '2024-09-19T10:13:12.075Z', + steps: null, + createdAt: '2024-09-19T10:10:04.725Z', + status: 'DRAFT', + name: 'v1', + id: 'f618843a-26be-4a54-a60f-f4ce88a594f0', + trigger: { + type: 'DATABASE_EVENT', + settings: { + eventName: 'note.created', + }, + }, + deletedAt: null, + workflowId: '200c1508-f102-4bb9-af32-eda55239ae61', + }, + }, + ], + }, + }, + }); + }), http.get('https://chat-assets.frontapp.com/v1/chat.bundle.js', () => { return HttpResponse.text( ` diff --git a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx index 35d4a861f5b6..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 { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; export const JestObjectMetadataItemSetter = ({ children, @@ -12,7 +12,7 @@ export const JestObjectMetadataItemSetter = ({ const setObjectMetadataItems = useSetRecoilState(objectMetadataItemsState); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { - setObjectMetadataItems(getObjectMetadataItemsMock()); + setObjectMetadataItems(generatedMockObjectMetadataItems); setIsLoaded(true); }, [setObjectMetadataItems]); 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<string, unknown>; + 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/config.ts b/packages/twenty-front/src/testing/mock-data/config.ts index 656dcbb80b76..1ed65869a7ca 100644 --- a/packages/twenty-front/src/testing/mock-data/config.ts +++ b/packages/twenty-front/src/testing/mock-data/config.ts @@ -13,10 +13,6 @@ export const mockedClientConfig: ClientConfig = { microsoft: false, __typename: 'AuthProviders', }, - telemetry: { - enabled: false, - __typename: 'Telemetry', - }, support: { supportDriver: 'front', supportFrontChatId: null, 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 new file mode 100644 index 000000000000..45cf893bae41 --- /dev/null +++ b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts @@ -0,0 +1,21079 @@ +import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql'; + +// This file is not designed to be manually edited. +// It's an extract from the dev seeded environment metadata call +// TODO: automate the generation of this file +// โš ๏ธ WARNING โš ๏ธ: Be sure to activate the workflow feature flag (IsWorkflowEnabled) before updating that mock. +export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = + { + "objects": { + "__typename": "ObjectConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjM2" + }, + "edges": [ + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "noteTarget", + "namePlural": "noteTargets", + "labelSingular": "Note Target", + "labelPlural": "Note Targets", + "description": "A note target", + "icon": "IconCheckbox", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "fec280d4-58dd-410f-addf-be34060d9f90", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "e0cacb65-c2ea-44b7-8d5c-508176b44ec7", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_19ea95ddb39f610f7dcad4c4336", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "15d4514a-0c1f-461d-bd8c-cf57010c6979", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "f7c177a2-a730-4571-8c06-b16b26476d69" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "9eb89e9b-422d-4f81-83a9-0395e5916c8e", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "8d01445d-36e3-45b1-8804-a3e27e177cbc" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "9974574d-2f7c-4666-88b5-40982e1fa0bb", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_241f0cca089399c8c5954086b8d", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "7e45c242-ba08-4ce7-8521-ddaa16ac1387", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_68bce49f4de05facd5365a3a797", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "838dc5e9-c28e-4530-b380-525df2ebbbff", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "b152c814-d0f9-44da-8bf0-b8772e7fe4f8" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "cfbcdd2e-63cd-41c4-b1b1-e90b9aad7078", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_56454973bce16e65ee1ae3d2e40", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "cd268504-7b0c-48da-902d-0b4935be93b5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "78a0334d-e28e-4b6e-a529-da28c4e73d89" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "2f776bc0-22cc-463b-a1a7-2986b1691b30", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "8d01445d-36e3-45b1-8804-a3e27e177cbc" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a87c2280-8913-4e89-b6a3-4403b70087d4", + "type": "RELATION", + "name": "rocket", + "label": "Rocket", + "description": "NoteTarget Rocket", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.725Z", + "updatedAt": "2024-10-10T15:05:42.725Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "bd9974b4-9210-4ef3-892d-4adc2d40feb6", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "a87c2280-8913-4e89-b6a3-4403b70087d4", + "name": "rocket" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "b3186db8-8ea1-49b6-8922-82e4bdc06eb9", + "name": "noteTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "78a0334d-e28e-4b6e-a529-da28c4e73d89", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "NoteTarget person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4f3ed77c-73e0-41ba-9519-dbaba2e5f571", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b152c814-d0f9-44da-8bf0-b8772e7fe4f8", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "NoteTarget company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "63ae8928-a35e-4053-bf6c-ee548e3614c4", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "NoteTarget person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "9eac5248-f58a-49d3-ab69-376c12f71680", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "63ae8928-a35e-4053-bf6c-ee548e3614c4", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "b0200630-5587-4c10-b5ca-1d1344ee2343", + "name": "noteTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "145a910b-bd70-42f6-bb62-2fd1424af4e8", + "type": "UUID", + "name": "rocketId", + "label": "Rocket ID (foreign key)", + "description": "NoteTarget Rocket id foreign key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.724Z", + "updatedAt": "2024-10-10T15:05:42.724Z", + "defaultValue": null, + "options": null, + "settings": { + "isForeignKey": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f7c177a2-a730-4571-8c06-b16b26476d69", + "type": "UUID", + "name": "opportunityId", + "label": "Opportunity id (foreign key)", + "description": "NoteTarget opportunity id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "67bfa361-4269-4494-b2eb-3a9f26f992fd", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "NoteTarget company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "99a8cc42-5f5d-41f2-9d5a-44e18f8412e4", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "67bfa361-4269-4494-b2eb-3a9f26f992fd", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "408a7fce-1980-48b2-9c0e-9e23b58b5e07", + "name": "noteTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fa189561-0de7-478f-8673-9f6b68527bed", + "type": "RELATION", + "name": "opportunity", + "label": "Opportunity", + "description": "NoteTarget opportunity", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "48b33f6e-6f79-4990-9d3a-7bc3ae6020bb", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "fa189561-0de7-478f-8673-9f6b68527bed", + "name": "opportunity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "9637bff7-b622-48f7-8f6c-2ea7ee18edef", + "name": "noteTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f23b5bd0-ceff-4741-a250-2fa1c10bb315", + "type": "UUID", + "name": "noteId", + "label": "Note id (foreign key)", + "description": "NoteTarget note id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8d01445d-36e3-45b1-8804-a3e27e177cbc", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fec280d4-58dd-410f-addf-be34060d9f90", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3f1365a0-a273-4d82-9c58-0095c8590541", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5ea37b6c-76b5-4cc3-981e-fefebaa91607", + "type": "RELATION", + "name": "note", + "label": "Note", + "description": "NoteTarget note", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7be32a73-9b29-422b-b5aa-1933cf5ad254", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "5ea37b6c-76b5-4cc3-981e-fefebaa91607", + "name": "note" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "45d38878-cca8-4c8c-b7e4-539adf09c5b1", + "name": "noteTargets" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "f98ea433-1b70-46d3-aefa-43eb369925d2", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "messageThread", + "namePlural": "messageThreads", + "labelSingular": "Message Thread", + "labelPlural": "Message Threads", + "description": "Message Thread", + "icon": "IconMessage", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "063a1df3-99e1-478e-9be4-7935cad6dc84", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjQ=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "45323df4-a610-484e-a9cf-bac16dd4f085", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "68d0646f-9477-4c8e-8d60-22ac5d665f91", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "84b1f586-7867-4cd9-b793-4826d4d99cf5", + "type": "RELATION", + "name": "messages", + "label": "Messages", + "description": "Messages from the thread.", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "fcbbe4ce-01e3-4332-8424-16709c40d819", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f98ea433-1b70-46d3-aefa-43eb369925d2", + "nameSingular": "messageThread", + "namePlural": "messageThreads" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "84b1f586-7867-4cd9-b793-4826d4d99cf5", + "name": "messages" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "nameSingular": "message", + "namePlural": "messages" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "efdfbd70-a365-4e96-9fb0-095eb91e061a", + "name": "messageThread" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e521c5e0-67d6-4a3c-b4e3-3a767f4237dd", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "063a1df3-99e1-478e-9be4-7935cad6dc84", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers", + "labelSingular": "Workspace Member", + "labelPlural": "Workspace Members", + "description": "A workspace member", + "icon": "IconUserCircle", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "948959ff-3aee-4617-aa03-042285cd342b", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjI1" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bae3fa70-47b5-495b-9f52-6724f5823bca", + "type": "RELATION", + "name": "connectedAccounts", + "label": "Connected accounts", + "description": "Connected accounts", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1ba150ca-e985-4ac5-9f0a-055022bab4eb", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "bae3fa70-47b5-495b-9f52-6724f5823bca", + "name": "connectedAccounts" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "94ca1303-d7ef-4ee0-a661-14e64f16d8d0", + "name": "accountOwner" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "948959ff-3aee-4617-aa03-042285cd342b", + "type": "FULL_NAME", + "name": "name", + "label": "Name", + "description": "Workspace member name", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "lastName": "''", + "firstName": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5f482f83-ad70-448c-b612-a4bcc25cfc2c", + "type": "TEXT", + "name": "timeZone", + "label": "Time zone", + "description": "User time zone", + "icon": "IconTimezone", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'system'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f2012359-cf46-43e7-8577-0dfc2615eb4e", + "type": "SELECT", + "name": "timeFormat", + "label": "Time format", + "description": "User's preferred time format", + "icon": "IconClock2", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'SYSTEM'", + "options": [ + { + "id": "d8aac04a-a1c6-4660-9557-950dbe0ebfe0", + "color": "sky", + "label": "System", + "value": "SYSTEM", + "position": 0 + }, + { + "id": "221dbdac-72a5-4dc0-99aa-07fd656963f6", + "color": "red", + "label": "24HRS", + "value": "HOUR_24", + "position": 1 + }, + { + "id": "77a20632-42f6-4d82-a296-7de278743bdc", + "color": "purple", + "label": "12HRS", + "value": "HOUR_12", + "position": 2 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0d89e67d-e021-4fb7-9022-5b91d22706d4", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "95aa4a80-fab0-4ffa-ab6b-a0213b94cdbd", + "type": "RELATION", + "name": "authoredComments", + "label": "Authored comments", + "description": "Authored comments", + "icon": "IconComment", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "45553fd2-1f4d-4b1b-8536-e41307b80cf7", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "95aa4a80-fab0-4ffa-ab6b-a0213b94cdbd", + "name": "authoredComments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "eda936a5-97b9-4b9f-986a-d8e19e8ea882", + "nameSingular": "comment", + "namePlural": "comments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "50d416ef-6abd-4329-b51e-7dae0193ce33", + "name": "author" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8e4da134-4b27-4f18-9fae-dddee6c6f3e9", + "type": "RELATION", + "name": "authoredActivities", + "label": "Authored activities", + "description": "Activities created by the workspace member", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c07499e3-5511-4e95-82de-5d2490c89470", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8e4da134-4b27-4f18-9fae-dddee6c6f3e9", + "name": "authoredActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1b094a8b-20f7-4402-8fdc-40af2405186f", + "name": "author" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "792d296b-22ae-4f57-a2a0-f7dad5cfd4a2", + "type": "TEXT", + "name": "avatarUrl", + "label": "Avatar Url", + "description": "Workspace member avatar", + "icon": "IconFileUpload", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "cf9c06a1-f820-476d-95ad-ebc12598a5f0", + "type": "UUID", + "name": "userId", + "label": "User Id", + "description": "Associated User Id", + "icon": "IconCircleUsers", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "174dc37b-4a28-4789-8f72-5d3782b32b06", + "type": "TEXT", + "name": "userEmail", + "label": "User Email", + "description": "Related user email address", + "icon": "IconMail", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4af78cad-69c5-4190-a23f-6db322f80f27", + "type": "RELATION", + "name": "auditLogs", + "label": "Audit Logs", + "description": "Audit Logs linked to the workspace member", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1aef1a4d-f090-4f5a-8e39-f7d21d465199", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "4af78cad-69c5-4190-a23f-6db322f80f27", + "name": "auditLogs" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "2590029a-05d7-4908-8b7a-a253967068a1", + "nameSingular": "auditLog", + "namePlural": "auditLogs" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1475304d-4734-4b66-96a8-ed7d84727fe6", + "name": "workspaceMember" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "07667783-a2b1-4eb5-8e3b-86ec786993fa", + "type": "RELATION", + "name": "messageParticipants", + "label": "Message Participants", + "description": "Message Participants", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1c4db1ed-209f-4274-bf95-004e0cb74404", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "07667783-a2b1-4eb5-8e3b-86ec786993fa", + "name": "messageParticipants" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "edc0cbe6-9c05-48a7-8cf5-6ff782ad055d", + "name": "workspaceMember" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c5797fa5-1ff4-403f-896a-eb45447b468f", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b391761f-fedd-4a1d-acd1-497c21a615ba", + "type": "RELATION", + "name": "blocklist", + "label": "Blocklist", + "description": "Blocklisted handles", + "icon": "IconForbid2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1185289f-ef64-4b23-a7ef-c16303bea50f", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "b391761f-fedd-4a1d-acd1-497c21a615ba", + "name": "blocklist" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "8ae98b12-2ef6-4c20-adc6-240857dd7343", + "nameSingular": "blocklist", + "namePlural": "blocklists" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "84b616f8-2db3-472d-93dd-a8e89b9db810", + "name": "workspaceMember" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5233906b-4d12-4e40-9081-436ff5c6cefe", + "type": "RELATION", + "name": "assignedTasks", + "label": "Assigned tasks", + "description": "Tasks assigned to the workspace member", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "970f0f7f-a30d-4a6f-b023-c3bc5a1b412c", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "5233906b-4d12-4e40-9081-436ff5c6cefe", + "name": "assignedTasks" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "0400e2ae-05e4-46f5-b7e1-3fd8e7c15731", + "name": "assignee" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5ef8d68c-516d-42b4-a727-612b1619ae55", + "type": "TEXT", + "name": "locale", + "label": "Language", + "description": "Preferred language", + "icon": "IconLanguage", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'en'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3687c43a-dab2-4043-b2c5-ba090cb9f46d", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the workspace member", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "09d87091-a73a-440d-9ef0-11f4639dbd64", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "3687c43a-dab2-4043-b2c5-ba090cb9f46d", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "37bb5979-9521-434c-8fca-84a84a545314", + "name": "workspaceMember" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6d033705-c48b-43e0-9cc1-5191c0f9ecd2", + "type": "RELATION", + "name": "timelineActivities", + "label": "Events", + "description": "Events linked to the workspace member", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "5622d0fd-cbf2-4baa-8be9-bc5ea20d321b", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "6d033705-c48b-43e0-9cc1-5191c0f9ecd2", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "93e24db5-c860-420d-90eb-782c3855a36d", + "name": "workspaceMember" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a308f6ca-1a9b-46f2-9215-f184cd7d53d6", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "be802643-0a4b-42d1-a87c-606fce69f9f7", + "type": "RELATION", + "name": "assignedActivities", + "label": "Assigned activities", + "description": "Activities assigned to the workspace member", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1756bbce-2212-4ec2-b1e3-5053810abdb1", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "be802643-0a4b-42d1-a87c-606fce69f9f7", + "name": "assignedActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "d2f57ffe-0cbd-40a0-b83a-1939aaeac560", + "name": "assignee" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e167ca8f-66e1-4d49-8a2f-eb8c96c2285d", + "type": "RELATION", + "name": "calendarEventParticipants", + "label": "Calendar Event Participants", + "description": "Calendar Event Participants", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "15a3fcda-90b1-4599-a31c-bd0807184401", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "e167ca8f-66e1-4d49-8a2f-eb8c96c2285d", + "name": "calendarEventParticipants" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "409c079f-742a-4bf5-a710-c782544fa21b", + "name": "workspaceMember" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "80303ac9-b0f9-4f44-8e31-3aa4fec4b0ed", + "type": "SELECT", + "name": "dateFormat", + "label": "Date format", + "description": "User's preferred date format", + "icon": "IconCalendarEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'SYSTEM'", + "options": [ + { + "id": "fc002c8e-9bde-4057-b5aa-61d40e5d90e6", + "color": "turquoise", + "label": "System", + "value": "SYSTEM", + "position": 0 + }, + { + "id": "1c89f46a-edbf-4c72-b796-8c04caad4a5c", + "color": "red", + "label": "Month First", + "value": "MONTH_FIRST", + "position": 1 + }, + { + "id": "1ccb080d-9d1e-47ba-affa-ceade664bc83", + "color": "purple", + "label": "Day First", + "value": "DAY_FIRST", + "position": 2 + }, + { + "id": "39d1d19a-2788-427a-ba61-0be5bea94e63", + "color": "sky", + "label": "Year First", + "value": "YEAR_FIRST", + "position": 3 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "579d2711-4edc-4645-99bb-3ecbb45ebf06", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8f1ea273-7d99-442e-94cc-1f77d494edf7", + "type": "TEXT", + "name": "colorScheme", + "label": "Color Scheme", + "description": "Preferred color scheme", + "icon": "IconColorSwatch", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'Light'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b6b66a3c-6c39-4e93-b940-836aced4de12", + "type": "RELATION", + "name": "accountOwnerForCompanies", + "label": "Account Owner For Companies", + "description": "Account owner for companies", + "icon": "IconBriefcase", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "9b71c7ff-5d2e-43c4-a524-611e309e6f45", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "b6b66a3c-6c39-4e93-b940-836aced4de12", + "name": "accountOwnerForCompanies" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "09e066b4-d809-49c8-908b-51b8b8724a4c", + "name": "accountOwner" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4c6c8adf-6958-48d6-b7e6-0bbab57e2dd1", + "type": "RELATION", + "name": "authoredAttachments", + "label": "Authored attachments", + "description": "Attachments created by the workspace member", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7a297555-158b-4bcb-a751-18e30aac5f3d", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "4c6c8adf-6958-48d6-b7e6-0bbab57e2dd1", + "name": "authoredAttachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "3faea476-e6f7-450d-a699-5f58da0f0a09", + "name": "author" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "f04a7171-564a-44ec-a061-63938e29f0c5", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "apiKey", + "namePlural": "apiKeys", + "labelSingular": "API Key", + "labelPlural": "API Keys", + "description": "An API key", + "icon": "IconRobot", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "51abf943-f933-446f-932e-3c7b2dc4c8c6", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjY=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "51abf943-f933-446f-932e-3c7b2dc4c8c6", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "ApiKey name", + "icon": "IconLink", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "19346f2e-1c93-4d28-bb1b-85f232a0a65a", + "type": "DATE_TIME", + "name": "expiresAt", + "label": "Expiration date", + "description": "ApiKey expiration date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b9268707-467d-42b0-875e-dbccd5974263", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c41f695a-568b-43de-a41c-4f617b51e4e2", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "18649d38-e8ea-47ff-8109-89920b2f8fc5", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ad8dc420-e7a8-4bf6-9d31-01f43c21ff53", + "type": "DATE_TIME", + "name": "revokedAt", + "label": "Revocation date", + "description": "ApiKey revocation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d0d69076-3369-4672-923c-736b71263a1c", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "taskTarget", + "namePlural": "taskTargets", + "labelSingular": "Task Target", + "labelPlural": "Task Targets", + "description": "An task target", + "icon": "IconCheckbox", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "94e86beb-7c83-4779-b723-c9696eca0c4c", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "195e34dd-24b9-48bc-99ae-66ef1a239ea2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_4e929e3af362914c41035c4d438", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "670e311e-a705-4e3d-a599-d44826329501", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "81ac8880-d8ea-4794-b87b-77b89b5e5141" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "16f945fd-63b1-4609-a31d-de06265893a2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "28ca5b60-d78e-4cb3-a142-742ef928631c" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "e34fc97b-d91f-4aa3-b353-a36806a4fa51", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_627d4437c96f22d5d46cc9a85bb", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d76441f4-8236-45eb-b1f2-40ed4a738439", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "f933b4e1-d1d3-404e-8ef8-ab666fba9d35" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "93be2dfb-e46f-42dc-a37d-5d550a2658ff", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_cf12e6c92058f11b59852ffdfe3", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "4de510a8-25b7-4435-b248-79a5df020a52", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "81ac8880-d8ea-4794-b87b-77b89b5e5141" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "887ecf80-a073-4d15-b42e-8f4290537fd0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "1e2d2291-460a-4ed8-94e8-d3c1f2bbd74f" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "cf162c2b-2ec8-4385-98ff-6049e7e968d0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_b0ba7efcd8c529922bf6e858bc1", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "be0c4484-eb8b-47e3-ad6d-4a0663d81af7", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "81ac8880-d8ea-4794-b87b-77b89b5e5141" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "80b77505-9340-45f2-8c9e-931754da1192", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "TaskTarget person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7fd53175-abb0-470e-a394-9c1de590b24e", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "80b77505-9340-45f2-8c9e-931754da1192", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "80092f7e-405b-4a94-b767-cdf877b2ef41", + "name": "taskTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f0adc079-73ce-4316-80e0-90295a7192f1", + "type": "RELATION", + "name": "opportunity", + "label": "Opportunity", + "description": "TaskTarget opportunity", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "3edc0e3a-d735-4b9c-ac34-352b48a6e705", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f0adc079-73ce-4316-80e0-90295a7192f1", + "name": "opportunity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "db2d038b-73d8-4c99-a709-8737e7e6658c", + "name": "taskTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4c42e3b9-26a9-4ce1-a37a-9606da0bc12a", + "type": "RELATION", + "name": "rocket", + "label": "Rocket", + "description": "TaskTarget Rocket", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.731Z", + "updatedAt": "2024-10-10T15:05:42.731Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7df72299-c1f4-4575-98bf-24156cb3e5b8", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "4c42e3b9-26a9-4ce1-a37a-9606da0bc12a", + "name": "rocket" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f3e444fc-afa9-45d7-b885-5000c2fa2b7d", + "name": "taskTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1e2d2291-460a-4ed8-94e8-d3c1f2bbd74f", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "TaskTarget person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "298b6f3b-4c03-4583-bbcc-8d43b742903e", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "94e86beb-7c83-4779-b723-c9696eca0c4c", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f933b4e1-d1d3-404e-8ef8-ab666fba9d35", + "type": "UUID", + "name": "taskId", + "label": "Task id (foreign key)", + "description": "TaskTarget task id foreign key", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "24a17b5b-e5cd-43c9-bcd8-422a00b0ebf6", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "TaskTarget company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "e38b7482-42bb-43b3-a36f-9c7eec06d59e", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "24a17b5b-e5cd-43c9-bcd8-422a00b0ebf6", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "eafdfdda-7cda-4fe4-bcaa-be49232fbfd4", + "name": "taskTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "81ac8880-d8ea-4794-b87b-77b89b5e5141", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "61963eea-7040-4cba-be90-031464ad1e69", + "type": "RELATION", + "name": "task", + "label": "Task", + "description": "TaskTarget task", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "2cb467d4-4e91-459c-a145-5f276f6186e1", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "61963eea-7040-4cba-be90-031464ad1e69", + "name": "task" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "74bf0e2d-9fc5-4609-b180-a50e18f5f9ca", + "name": "taskTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "28ca5b60-d78e-4cb3-a142-742ef928631c", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "TaskTarget company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9338cb5b-62e3-4636-a544-c872350ef691", + "type": "UUID", + "name": "rocketId", + "label": "Rocket ID (foreign key)", + "description": "TaskTarget Rocket id foreign key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.730Z", + "updatedAt": "2024-10-10T15:05:42.730Z", + "defaultValue": null, + "options": null, + "settings": { + "isForeignKey": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ae4f0b40-33c6-429b-bfca-84812311bfbe", + "type": "UUID", + "name": "opportunityId", + "label": "Opportunity id (foreign key)", + "description": "TaskTarget opportunity id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9fcd7cb0-7f2f-473b-ab03-8f604f9c5d69", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "eda936a5-97b9-4b9f-986a-d8e19e8ea882", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "comment", + "namePlural": "comments", + "labelSingular": "Comment", + "labelPlural": "Comments", + "description": "A comment", + "icon": "IconMessageCircle", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "bf3832d9-cdfd-4d88-bd77-2058fbd81447", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "7ee50853-aacd-49da-8d15-cba48b38a353", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_13404a209dc268d64d59e458f86", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "1d8f2dac-d481-4ac1-9e14-ee2e901b5a6f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "506fa74d-51eb-4160-8dba-3166aa4d2301" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "127f01e8-a06c-479e-8aab-129b21eea852", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "c91d7ffc-5a65-4e57-b5e7-1de26022617b" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "19dbdfa2-259f-4aaf-b4ee-3d22231ff3fe", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_59b3acef02e58676e983e724ae1", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "f9a1e7a6-7cc0-48dd-84bb-226e6959b4a3", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "e36ca5db-8217-4aff-8c62-eeefcfac1691" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjg=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c91d7ffc-5a65-4e57-b5e7-1de26022617b", + "type": "UUID", + "name": "activityId", + "label": "Activity id (foreign key)", + "description": "Comment activity id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "506fa74d-51eb-4160-8dba-3166aa4d2301", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e36ca5db-8217-4aff-8c62-eeefcfac1691", + "type": "UUID", + "name": "authorId", + "label": "Author id (foreign key)", + "description": "Comment author id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "87d27084-f6b5-420d-b17e-0cd16628437f", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4bfa0272-145c-4f0e-8c38-54ce1e01fea6", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d010344a-e348-47ef-b8c3-a7da39b3285d", + "type": "TEXT", + "name": "body", + "label": "Body", + "description": "Comment body", + "icon": "IconLink", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "50d416ef-6abd-4329-b51e-7dae0193ce33", + "type": "RELATION", + "name": "author", + "label": "Author", + "description": "Comment author", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "45553fd2-1f4d-4b1b-8536-e41307b80cf7", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "eda936a5-97b9-4b9f-986a-d8e19e8ea882", + "nameSingular": "comment", + "namePlural": "comments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "50d416ef-6abd-4329-b51e-7dae0193ce33", + "name": "author" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "95aa4a80-fab0-4ffa-ab6b-a0213b94cdbd", + "name": "authoredComments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "04e07930-a3f1-4f57-9265-7364f18f2651", + "type": "RELATION", + "name": "activity", + "label": "Activity", + "description": "Comment activity", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "ed3d480f-cc49-4fee-ae36-94e6edd64940", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "eda936a5-97b9-4b9f-986a-d8e19e8ea882", + "nameSingular": "comment", + "namePlural": "comments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "04e07930-a3f1-4f57-9265-7364f18f2651", + "name": "activity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "ccd81283-2fcb-445c-af6f-c2ac27a42824", + "name": "comments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bf3832d9-cdfd-4d88-bd77-2058fbd81447", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "e5915d30-4425-4c4c-a9c4-1b4bff20c469", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "workflowVersion", + "namePlural": "workflowVersions", + "labelSingular": "Workflow Version", + "labelPlural": "Workflow Versions", + "description": "A workflow version", + "icon": "IconVersions", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "01a5ead6-66ab-4188-87b7-55662b90b318", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "093a3d04-734a-4461-93ad-39cc75e79c9e", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_a362c5eff4a28fcdffdd3bdff16", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "475f6fe5-3025-4ba7-a243-2e0cffa82820", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "c7a5fd23-5b6f-4f84-bae6-e51aeb6ce3b2" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8882dd98-35e5-441c-8f1d-f83496078d73", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "0f5844fe-f16b-4218-9ece-a4631efcc4a9" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d067b570-d85f-4c24-9341-70d0fd8534e9", + "type": "RAW_JSON", + "name": "trigger", + "label": "Version trigger", + "description": "Json object to provide trigger", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "edfd48ca-31da-4872-8d23-f18671916ae4", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Workflow version position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0f5844fe-f16b-4218-9ece-a4631efcc4a9", + "type": "UUID", + "name": "workflowId", + "label": "Workflow id (foreign key)", + "description": "WorkflowVersion workflow id foreign key", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "54150a7d-80b4-4deb-a09e-e5b57f57fda1", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "928bb407-6191-4cb3-95df-a75cd34d11c3", + "type": "SELECT", + "name": "status", + "label": "Version status", + "description": "The workflow version status", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'DRAFT'", + "options": [ + { + "id": "70cd7090-c978-4428-88e3-b03269310eaf", + "color": "yellow", + "label": "Draft", + "value": "DRAFT", + "position": 0 + }, + { + "id": "1185beb6-82c3-4429-b12e-a6831ae52c4e", + "color": "green", + "label": "Active", + "value": "ACTIVE", + "position": 1 + }, + { + "id": "15def951-8fd6-41bb-8a6f-d1c3ecc55e15", + "color": "red", + "label": "Deactivated", + "value": "DEACTIVATED", + "position": 2 + }, + { + "id": "bc1f10f9-b0b5-4b76-bc20-e8d7fbfac7a1", + "color": "grey", + "label": "Archived", + "value": "ARCHIVED", + "position": 3 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2f2b9ae4-4755-4dd3-a465-1e5ca585df8a", + "type": "RAW_JSON", + "name": "steps", + "label": "Version steps", + "description": "Json object to provide steps", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "754a62dc-6d46-41f6-9a61-603e19b44ff8", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5c088faa-7d48-44e0-bc61-a338fe597395", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c7a5fd23-5b6f-4f84-bae6-e51aeb6ce3b2", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8afee0ef-f4da-41bf-824a-e7cb357d8fc7", + "type": "RELATION", + "name": "runs", + "label": "Runs", + "description": "Workflow runs linked to the version.", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "5a8466a5-1aa6-48e2-a415-878bcb28eb0e", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "e5915d30-4425-4c4c-a9c4-1b4bff20c469", + "nameSingular": "workflowVersion", + "namePlural": "workflowVersions" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8afee0ef-f4da-41bf-824a-e7cb357d8fc7", + "name": "runs" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "45b7e1cf-792c-45fa-8d6a-0d5e67e1fa42", + "nameSingular": "workflowRun", + "namePlural": "workflowRuns" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "ff72aee0-1e66-4493-a057-4f4455f6b738", + "name": "workflowVersion" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "69e06184-8fa4-4df5-95dc-7563936ed95f", + "type": "RELATION", + "name": "workflow", + "label": "Workflow", + "description": "WorkflowVersion workflow", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "5e09f62f-3f3a-4b9a-a2f8-434e7ac69969", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "e5915d30-4425-4c4c-a9c4-1b4bff20c469", + "nameSingular": "workflowVersion", + "namePlural": "workflowVersions" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "69e06184-8fa4-4df5-95dc-7563936ed95f", + "name": "workflow" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "46f260da-54b5-4739-90c3-b6aab057e442", + "name": "versions" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "01a5ead6-66ab-4188-87b7-55662b90b318", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "The workflow version name", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts", + "labelSingular": "Connected Account", + "labelPlural": "Connected Accounts", + "description": "A connected account", + "icon": "IconAt", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "ab45908f-63c2-4158-b89e-13cec2cfa8fb", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "9dafdc07-412c-49ab-9fb3-c22755bf1002", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_7d1b454b2a538273bdb947e848f", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "04fb0407-046b-4412-a4c6-6392e979dabf", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "76f53ec3-f021-4a75-8886-3916234ada48" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "e84dbe39-c202-438b-ba33-9688cfc96ca6", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "7ae566f5-11c4-447a-ba00-dab85eda1d09" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE1" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7ae566f5-11c4-447a-ba00-dab85eda1d09", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ab45908f-63c2-4158-b89e-13cec2cfa8fb", + "type": "TEXT", + "name": "handle", + "label": "handle", + "description": "The account handle (email, username, phone number, etc.)", + "icon": "IconMail", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "94ca1303-d7ef-4ee0-a661-14e64f16d8d0", + "type": "RELATION", + "name": "accountOwner", + "label": "Account Owner", + "description": "Account Owner", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1ba150ca-e985-4ac5-9f0a-055022bab4eb", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "94ca1303-d7ef-4ee0-a661-14e64f16d8d0", + "name": "accountOwner" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "bae3fa70-47b5-495b-9f52-6724f5823bca", + "name": "connectedAccounts" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6f5b80bc-d041-4b93-902c-cf5bc3429a82", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5d207989-c76c-49db-add7-a988ba0cd9f6", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7eedbbe1-3395-436a-8409-b8b95e5c5bea", + "type": "TEXT", + "name": "lastSyncHistoryId", + "label": "Last sync history ID", + "description": "Last sync history ID", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "76f53ec3-f021-4a75-8886-3916234ada48", + "type": "UUID", + "name": "accountOwnerId", + "label": "Account Owner id (foreign key)", + "description": "Account Owner id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "44aa8b95-c247-4507-bf76-17ff3449b4cb", + "type": "RELATION", + "name": "calendarChannels", + "label": "Calendar Channels", + "description": "Calendar Channels", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "38ead7c5-b8c5-40a1-9db4-088a93b3c798", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "44aa8b95-c247-4507-bf76-17ff3449b4cb", + "name": "calendarChannels" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "8cceadc4-de6b-4ecf-8324-82c6b4eec077", + "nameSingular": "calendarChannel", + "namePlural": "calendarChannels" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "fe2812bf-cf28-4ab1-9c52-1e8598224947", + "name": "connectedAccount" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7036df84-657d-4e56-bb7b-48a8c72df2be", + "type": "RELATION", + "name": "messageChannels", + "label": "Message Channels", + "description": "Message Channels", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "4b072b41-d4e4-4bb0-b64b-51b17887b5a3", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7036df84-657d-4e56-bb7b-48a8c72df2be", + "name": "messageChannels" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "92b529f1-b82b-4352-a0d5-18f32f8e47ab", + "nameSingular": "messageChannel", + "namePlural": "messageChannels" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "4a315b5d-ec77-47cf-9467-cc3d86328f02", + "name": "connectedAccount" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e760de08-161b-4772-8261-a4f13234bd46", + "type": "TEXT", + "name": "accessToken", + "label": "Access Token", + "description": "Messaging provider access token", + "icon": "IconKey", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4e191733-69a2-496c-9c1c-2b8f164c86fe", + "type": "TEXT", + "name": "handleAliases", + "label": "Handle Aliases", + "description": "Handle Aliases", + "icon": "IconMail", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4290f6df-ce65-49e4-a428-246422f12ac7", + "type": "ARRAY", + "name": "scopes", + "label": "Scopes", + "description": "Scopes", + "icon": "IconSettings", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "cdfa99d0-fb44-4527-9851-874e3a0da145", + "type": "TEXT", + "name": "provider", + "label": "provider", + "description": "The account provider", + "icon": "IconSettings", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "384762e8-f9ee-4a98-8990-dfcf89293d60", + "type": "DATE_TIME", + "name": "authFailedAt", + "label": "Auth failed at", + "description": "Auth failed at", + "icon": "IconX", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5d845a06-0f8b-4ffd-9abe-d277a2eaf367", + "type": "TEXT", + "name": "refreshToken", + "label": "Refresh Token", + "description": "Messaging provider refresh token", + "icon": "IconKey", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "20848b35-b160-470f-a467-8fda3e7f28ef", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "attachment", + "namePlural": "attachments", + "labelSingular": "Attachment", + "labelPlural": "Attachments", + "description": "An attachment", + "icon": "IconFileImport", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "7ea64783-b660-4ade-b155-012c66810d45", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "c2b4689a-1592-4ec9-963a-9f646b709e92", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_30f969e0ec549acca94396d3efe", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "63ec8514-11f6-48fe-85cd-c81b5ffbec0a", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "48681c53-aac1-4971-bb56-58bbdd97e7b1" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "07bed533-2603-40bc-bb3f-1f4c6ee46863", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_2055e4e583e9a2e5b4c239fd992", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "3c035e56-b3a8-42c2-bc54-1a2ec53e9b23", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "8fad0250-f884-4a20-9bbc-03f95a213e06" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "4fe9fa6f-110a-4f43-a470-d179d91cd1ad", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "48681c53-aac1-4971-bb56-58bbdd97e7b1" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f2338a67-5927-4dec-9baa-eb1b0f9b112b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_0698fed0e67005b7051b5d353b6", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8760c9f0-03be-4d4a-9277-7494329a5d3a", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "48681c53-aac1-4971-bb56-58bbdd97e7b1" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "a47bad55-3b7d-48ba-9c63-4948e06a8a1d", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "e2a8c344-0c1e-4f49-afdc-7b2cc4d09a54" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "ccfda9d5-7d59-46a1-b5ba-0579fbbab49b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_3ca1d5243ff67f58c7c65c9a8a2", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d0d55a3a-a721-40ae-8074-69e922e9bc24", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "c524abb7-af5f-4ac7-9add-a944b6765a0f" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "92b95079-df52-4840-bfe1-f0bf8a729066", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "48681c53-aac1-4971-bb56-58bbdd97e7b1" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "eb88ae97-325a-4182-bfff-451285fc8599", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_63f7d3dda8101b0fde171835da6", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "45fbb56c-9349-494f-851e-96bd210b809e", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "61793dd1-8f80-4b0b-bea7-d59bd26667a1" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "fe574c74-845f-4ebc-8b8a-e04fa66b599b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "48681c53-aac1-4971-bb56-58bbdd97e7b1" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "130c2f7f-be71-44d5-876b-8d38cc7998b0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_73615a6bdc972b013956b19c59e", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "7c6e3992-e68e-4ebb-884f-d685c958a712", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "48681c53-aac1-4971-bb56-58bbdd97e7b1" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "433e113f-de68-4662-992b-fe6b126408ba", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "a6656d59-63e1-4ec0-afbc-3fd5c509b74e" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f014e3b3-5b7d-4b23-a5ec-9a3516afaec1", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_91e687ea21123af4e02c9a07a43", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjIy" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a6656d59-63e1-4ec0-afbc-3fd5c509b74e", + "type": "UUID", + "name": "opportunityId", + "label": "Opportunity id (foreign key)", + "description": "Attachment opportunity id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9907dc47-5e02-4966-91f8-5df22fafaaa4", + "type": "RELATION", + "name": "task", + "label": "Task", + "description": "Attachment task", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "4a95e7b4-8bdc-49ca-b6d3-ae3d86a6e040", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "9907dc47-5e02-4966-91f8-5df22fafaaa4", + "name": "task" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1bb59b31-1c7d-4697-98d5-9f984cc9ea9c", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5c2e25df-c9e1-4f06-81ca-7b64251ee6e0", + "type": "UUID", + "name": "rocketId", + "label": "Rocket ID (foreign key)", + "description": "Attachment Rocket id foreign key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.721Z", + "updatedAt": "2024-10-10T15:05:42.721Z", + "defaultValue": null, + "options": null, + "settings": { + "isForeignKey": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "61793dd1-8f80-4b0b-bea7-d59bd26667a1", + "type": "UUID", + "name": "activityId", + "label": "Activity id (foreign key)", + "description": "Attachment activity id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c524abb7-af5f-4ac7-9add-a944b6765a0f", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "Attachment person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3faea476-e6f7-450d-a699-5f58da0f0a09", + "type": "RELATION", + "name": "author", + "label": "Author", + "description": "Attachment author", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7a297555-158b-4bcb-a751-18e30aac5f3d", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "3faea476-e6f7-450d-a699-5f58da0f0a09", + "name": "author" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "4c6c8adf-6958-48d6-b7e6-0bbab57e2dd1", + "name": "authoredAttachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8fad0250-f884-4a20-9bbc-03f95a213e06", + "type": "UUID", + "name": "taskId", + "label": "Task id (foreign key)", + "description": "Attachment task id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "144087ac-d621-4897-8172-14578d80a07b", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "Attachment person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0a89048f-6e64-4a8d-ad24-51ab63af3a85", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "144087ac-d621-4897-8172-14578d80a07b", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "561cbb88-39b8-47e2-9f48-6ed929f6ca2a", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7592ab0d-a3bd-459f-a2cb-8220148b7cc6", + "type": "RELATION", + "name": "activity", + "label": "Activity", + "description": "Attachment activity", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "58616cac-8d7a-4148-9051-a6878ca7e361", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7592ab0d-a3bd-459f-a2cb-8220148b7cc6", + "name": "activity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "ab39f722-059b-4804-8683-a75f58db81c3", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "619eb737-7e83-4f2b-958d-8098bf95c91e", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "Attachment company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2f907f04-3122-4b7b-bd83-c192cacc4a83", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "Attachment company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c9ce54d1-cb4a-4492-879f-be86056fcce5", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "2f907f04-3122-4b7b-bd83-c192cacc4a83", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1b548bb0-c4fc-4232-a18f-b7882b6a1ddf", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "99ab5575-ac5d-4284-8621-7e4ac98a5e51", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "01b00bde-0e1f-4e47-ad06-9df00dd1c7a3", + "type": "RELATION", + "name": "rocket", + "label": "Rocket", + "description": "Attachment Rocket", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.722Z", + "updatedAt": "2024-10-10T15:05:42.722Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "07793950-4b19-4d72-afda-f1815ec8e5e4", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "01b00bde-0e1f-4e47-ad06-9df00dd1c7a3", + "name": "rocket" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "c2721d06-ae7f-40e4-b061-d96179f0be97", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f84f5b1b-70b1-4d06-9093-d4be29917d3e", + "type": "RELATION", + "name": "opportunity", + "label": "Opportunity", + "description": "Attachment opportunity", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0984e2b5-1ba8-46d8-ade2-11bf79f52557", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f84f5b1b-70b1-4d06-9093-d4be29917d3e", + "name": "opportunity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f2b2e717-0c19-417c-b81a-98d95cadcc67", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "10689971-5c26-4b38-ace8-bab4bca531a7", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "48681c53-aac1-4971-bb56-58bbdd97e7b1", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e2a8c344-0c1e-4f49-afdc-7b2cc4d09a54", + "type": "UUID", + "name": "noteId", + "label": "Note id (foreign key)", + "description": "Attachment note id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c3bd28b7-06b4-448a-9a25-9fffcc4c856f", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "052cd77c-3540-4c77-a571-e0144b944351", + "type": "RELATION", + "name": "note", + "label": "Note", + "description": "Attachment note", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "fc78e196-db67-41d8-8d95-b1da605f98d5", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "052cd77c-3540-4c77-a571-e0144b944351", + "name": "note" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "20646869-ca0e-49a0-9344-fe9272d64f6d", + "name": "attachments" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2df006ae-0867-475d-b5db-15b8152c5e87", + "type": "TEXT", + "name": "fullPath", + "label": "Full path", + "description": "Attachment full path", + "icon": "IconLink", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a0e86fca-ff82-4d04-be4b-13918686feba", + "type": "UUID", + "name": "authorId", + "label": "Author id (foreign key)", + "description": "Attachment author id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "35ff7d4f-78f1-4ac7-b6f6-c1f76c177648", + "type": "TEXT", + "name": "type", + "label": "Type", + "description": "Attachment type", + "icon": "IconList", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7ea64783-b660-4ade-b155-012c66810d45", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "Attachment name", + "icon": "IconFileUpload", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "favorite", + "namePlural": "favorites", + "labelSingular": "Favorite", + "labelPlural": "Favorites", + "description": "A favorite", + "icon": "IconHeart", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "4cf1af4a-e7c7-40c9-88e5-c1bbb79776d0", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d71f5aa2-0433-4b1e-a5f5-001efc908018", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_505a1fccd2804f2472bd92e8720", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "666cb283-0e48-4982-9a10-b8bd813e93da", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "66cbb849-1798-424d-8924-701491da5955" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "bf689f43-2536-4bda-9e44-49794722c47e", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_e14b3424016bea8b7fe220f7761", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "3aadce0a-04ce-4e5e-8ca3-6ed6534fd449", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "31d02158-ff64-4da7-adc8-0b170418bae5" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8c32e5f8-c510-4400-a298-9225d7c16274", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "66cbb849-1798-424d-8924-701491da5955" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "6f30b5f2-f039-4e58-840d-daaa1b14d6eb", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_21d905e0adf19e835f6059a9f3d", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "b13cd64b-486f-4dcd-9bcd-06632dee78a5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "eea55a50-4866-4e40-b4ee-55cfc8f24f37" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "941a0458-02b1-46e7-97b8-df46b23da17e", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "66cbb849-1798-424d-8924-701491da5955" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "3830e581-4114-4914-9858-06cdaf072777", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_eecddc968e93b9b8ebbfd85dad3", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "f747d40f-bf6e-465f-92b8-c0cd8384109f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "66cbb849-1798-424d-8924-701491da5955" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "4b9718e7-4bf4-4c18-b77d-3982bc066c0b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "e3d4db69-e331-4db1-b26c-5714900c2996" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "23f93471-7dd4-418f-8ba6-566493294692", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_a900d9f809273abe54dc5e166fa", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "f67e1247-7831-4b70-b656-faeac8e86d34", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "de785063-55a9-433d-866f-7b219c8a605e" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "4cb8c460-5fd2-47f6-8023-058e1efbf59f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_1f7e4cb168e77496349c8cefed6", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "ae8f9213-825e-4a9f-9b00-704c338d9e58", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "66cbb849-1798-424d-8924-701491da5955" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "3b43ed22-9ff3-4752-b865-1362201acd96", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_c3ee83d51bc99ba99fe1998c508", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "373a9183-07ca-47e6-a43e-870fe50f30c5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "66cbb849-1798-424d-8924-701491da5955" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "8baea079-e5c6-4ec3-afa1-821e8b4a8eca", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_85f024f9ec673d530d14cf75fe5", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "ef971ebb-ad07-4d92-8667-b51318ce4b3b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "7da1d1c6-8ea5-4aac-abea-831483bd204a" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjIy" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f7905ef7-a551-41eb-877f-bd2feef547e0", + "type": "RELATION", + "name": "view", + "label": "View", + "description": "Favorite view", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "415982de-24e3-4428-a0cc-784d91a4a41c", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f7905ef7-a551-41eb-877f-bd2feef547e0", + "name": "view" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f82b5a4c-e41c-4d92-b36a-c18b5387d373", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2c985750-bb37-4974-ace6-0f3d65846524", + "type": "UUID", + "name": "rocketId", + "label": "Rocket ID (foreign key)", + "description": "Favorite Rocket id foreign key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.717Z", + "updatedAt": "2024-10-10T15:05:42.717Z", + "defaultValue": null, + "options": null, + "settings": { + "isForeignKey": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "23e21a62-5a1f-4fe5-b5f0-1a5949d622f1", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c19bfc7c-f7a6-47ac-bd5b-a352ce7ff74b", + "type": "UUID", + "name": "workspaceMemberId", + "label": "Workspace Member id (foreign key)", + "description": "Favorite workspace member id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "eea55a50-4866-4e40-b4ee-55cfc8f24f37", + "type": "UUID", + "name": "noteId", + "label": "Note id (foreign key)", + "description": "Favorite note id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7da1d1c6-8ea5-4aac-abea-831483bd204a", + "type": "UUID", + "name": "opportunityId", + "label": "Opportunity id (foreign key)", + "description": "Favorite opportunity id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "cfcd1a8f-e40e-49cb-a711-c92aab1e4158", + "type": "NUMBER", + "name": "position", + "label": "Position", + "description": "Favorite position", + "icon": "IconList", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": 0, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0d9f922f-d7db-485e-b5ff-1293a0b51dc9", + "type": "RELATION", + "name": "opportunity", + "label": "Opportunity", + "description": "Favorite opportunity", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "85086085-d6fb-4f1c-9f77-1edd0b07adcb", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "0d9f922f-d7db-485e-b5ff-1293a0b51dc9", + "name": "opportunity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "30756ebf-d27c-4ffb-a30f-8a04b909839f", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "31d02158-ff64-4da7-adc8-0b170418bae5", + "type": "UUID", + "name": "taskId", + "label": "Task id (foreign key)", + "description": "Favorite task id foreign key", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4cf1af4a-e7c7-40c9-88e5-c1bbb79776d0", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "71e4555a-9fcc-41bd-a8f0-248d1605567e", + "type": "RELATION", + "name": "workflow", + "label": "Workflow", + "description": "Favorite workflow", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "fa6c3b55-5fe4-4406-89a8-9474ca539769", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "71e4555a-9fcc-41bd-a8f0-248d1605567e", + "name": "workflow" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "05d6227a-bc2d-4e08-8390-cfcec921e4a4", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5569602d-cf0d-4b49-ac90-f5d1fc18bde7", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3f7749a5-7d4e-4b3c-b5ae-12b8ea631676", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "Favorite company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "3e2f75b9-76aa-436f-8d24-129798ca4090", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "3f7749a5-7d4e-4b3c-b5ae-12b8ea631676", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "134b368a-cf99-438e-8105-9150a2827fd4", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "de785063-55a9-433d-866f-7b219c8a605e", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "Favorite company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "89fe6ca9-b01e-4f73-8fe2-f51ba3a67024", + "type": "RELATION", + "name": "rocket", + "label": "Rocket", + "description": "Favorite Rocket", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.718Z", + "updatedAt": "2024-10-10T15:05:42.718Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "9a12826d-783a-4411-89ac-a2a2369e5eb2", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "89fe6ca9-b01e-4f73-8fe2-f51ba3a67024", + "name": "rocket" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "4d64de07-68da-42c9-b169-d5adf91ae282", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ea2ca48b-3fa6-4b65-9fb2-eb0a194385f5", + "type": "UUID", + "name": "viewId", + "label": "View id (foreign key)", + "description": "Favorite view id foreign key", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "60db3d95-7824-4335-9a49-eec97888c068", + "type": "RELATION", + "name": "task", + "label": "Task", + "description": "Favorite task", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "14f171b6-2a7e-4ae3-adc6-1721e91b791f", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "60db3d95-7824-4335-9a49-eec97888c068", + "name": "task" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "0111a4d1-f387-4f21-bc99-94b80139ff7f", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "85779625-b591-4436-bcee-4e69f937a2ea", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "Favorite person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e3d4db69-e331-4db1-b26c-5714900c2996", + "type": "UUID", + "name": "workflowId", + "label": "Workflow id (foreign key)", + "description": "Favorite workflow id foreign key", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7626da40-ddb4-4cbc-8554-441f9785ef5e", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "Favorite person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "033e99d2-7a1a-405c-950d-06031b93a773", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7626da40-ddb4-4cbc-8554-441f9785ef5e", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "42052d18-6fab-4d08-bb69-667683392617", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "53790b28-fd71-4f0f-994d-72e9cb8b018b", + "type": "RELATION", + "name": "note", + "label": "Note", + "description": "Favorite note", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "8206c6dd-98d8-4526-9836-bad9f2b065bb", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "53790b28-fd71-4f0f-994d-72e9cb8b018b", + "name": "note" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f86335f9-07e3-4108-a2a2-cafa6820e85e", + "name": "favorites" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "66cbb849-1798-424d-8924-701491da5955", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "37bb5979-9521-434c-8fca-84a84a545314", + "type": "RELATION", + "name": "workspaceMember", + "label": "Workspace Member", + "description": "Favorite workspace member", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "09d87091-a73a-440d-9ef0-11f4639dbd64", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "37bb5979-9521-434c-8fca-84a84a545314", + "name": "workspaceMember" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "3687c43a-dab2-4043-b2c5-ba090cb9f46d", + "name": "favorites" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "c55193eb-042d-42d5-a6a7-8263fd1433a2", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "viewSort", + "namePlural": "viewSorts", + "labelSingular": "View Sort", + "labelPlural": "View Sorts", + "description": "(System) View Sorts", + "icon": "IconArrowsSort", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "2416e698-40fa-4a3f-9190-fed51819cd7e", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "a4e03ada-488b-4c7b-bcbb-ed2f76d4f82f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_a01889a3e5b30d56447736329aa", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "a7c3045f-a5b7-4409-9d85-0d71e1e7ee1d", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "5505a8ee-ac5f-4b0a-bf90-87f80307d8d8" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "5512c691-fa4f-4d75-a0bc-06ca74dd5666", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "6aeb1851-3091-4fb9-9aa5-c2a3e3d3aea3" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "274f0fa6-16c3-49e2-a033-72b0e7fa4310", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_9564690e029f3f186dff29c9c88", + "indexWhereClause": "\"deletedAt\" IS NULL", + "indexType": "BTREE", + "isUnique": true, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "a6632868-3a90-4dcb-909d-905deee50405", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "5505a8ee-ac5f-4b0a-bf90-87f80307d8d8" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "674e69ba-749c-4fd1-925a-9a9498177af6", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "9dd7a1fc-bda5-4452-810c-6a966757e1f1" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjc=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "dc1b5993-36b2-45eb-be43-506965789902", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "aa5a8453-0d51-4f10-98b2-0a15fcc723c1", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6aeb1851-3091-4fb9-9aa5-c2a3e3d3aea3", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9dd7a1fc-bda5-4452-810c-6a966757e1f1", + "type": "UUID", + "name": "fieldMetadataId", + "label": "Field Metadata Id", + "description": "View Sort target field", + "icon": "IconTag", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d0bce78c-57eb-48c1-9a66-5e0de3d63a61", + "type": "RELATION", + "name": "view", + "label": "View", + "description": "View Sort related view", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "05ae03d1-1a38-4c4e-bbe1-f46ca40ece76", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "c55193eb-042d-42d5-a6a7-8263fd1433a2", + "nameSingular": "viewSort", + "namePlural": "viewSorts" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "d0bce78c-57eb-48c1-9a66-5e0de3d63a61", + "name": "view" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "db666f56-a9ac-41a3-9a41-bb172dbc9cae", + "name": "viewSorts" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1e5c3440-e396-44ed-9616-c603c514a96e", + "type": "TEXT", + "name": "direction", + "label": "Direction", + "description": "View Sort direction", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'asc'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5505a8ee-ac5f-4b0a-bf90-87f80307d8d8", + "type": "UUID", + "name": "viewId", + "label": "View id (foreign key)", + "description": "View Sort related view id foreign key", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2416e698-40fa-4a3f-9190-fed51819cd7e", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "view", + "namePlural": "views", + "labelSingular": "View", + "labelPlural": "Views", + "description": "(System) Views", + "icon": "IconLayoutCollage", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "6cefd31f-49fd-4e11-b306-3017ca5a653d", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE1" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bbb3ca4e-a73c-45a4-9040-d062c94dd8fa", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "712a82cd-43fd-429d-83c5-8b1b2c8e90c0", + "type": "TEXT", + "name": "kanbanFieldMetadataId", + "label": "kanbanfieldMetadataId", + "description": "View Kanban column field", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1c2ddd98-f8e7-4c61-ba6a-5508383d4702", + "type": "BOOLEAN", + "name": "isCompact", + "label": "Compact View", + "description": "Describes if the view is in compact mode", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": false, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "312163f0-9b2f-4c59-8473-9eb1906b8f41", + "type": "RELATION", + "name": "viewFilters", + "label": "View Filters", + "description": "View Filters", + "icon": "IconFilterBolt", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "678c1f91-1825-4eb5-9960-bea05bd2ca87", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "312163f0-9b2f-4c59-8473-9eb1906b8f41", + "name": "viewFilters" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "88f29168-a15b-4330-89a1-680581a2e86b", + "nameSingular": "viewFilter", + "namePlural": "viewFilters" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "d3db3f5c-49f7-4008-b8a4-cec85ac3505e", + "name": "view" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4f39ff46-1298-4408-8db7-47ac99750ff9", + "type": "TEXT", + "name": "icon", + "label": "Icon", + "description": "View icon", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "90203b84-6898-4ced-976f-cfdb2c3aa691", + "type": "TEXT", + "name": "type", + "label": "Type", + "description": "View type", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'table'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f82b5a4c-e41c-4d92-b36a-c18b5387d373", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the view", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "415982de-24e3-4428-a0cc-784d91a4a41c", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f82b5a4c-e41c-4d92-b36a-c18b5387d373", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f7905ef7-a551-41eb-877f-bd2feef547e0", + "name": "view" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2833387e-799c-44b9-a751-87acdefbd502", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "View position", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "763a455b-2f04-41be-b4d6-cc0c381bc7e9", + "type": "SELECT", + "name": "key", + "label": "Key", + "description": "View key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'INDEX'", + "options": [ + { + "id": "f425b635-b0f5-461b-a321-96e621c25a5d", + "color": "red", + "label": "Index", + "value": "INDEX", + "position": 0 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8c46a404-3ed7-4a94-8a02-e9b03197de5e", + "type": "RELATION", + "name": "viewFields", + "label": "View Fields", + "description": "View Fields", + "icon": "IconTag", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7184d260-316e-4e2c-af72-431957b1af98", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8c46a404-3ed7-4a94-8a02-e9b03197de5e", + "name": "viewFields" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "50f61b05-868d-425b-ab3f-c085e1652d82", + "nameSingular": "viewField", + "namePlural": "viewFields" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "911faf74-aef1-4b31-a5a3-f1ed80496f18", + "name": "view" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "db666f56-a9ac-41a3-9a41-bb172dbc9cae", + "type": "RELATION", + "name": "viewSorts", + "label": "View Sorts", + "description": "View Sorts", + "icon": "IconArrowsSort", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "05ae03d1-1a38-4c4e-bbe1-f46ca40ece76", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "db666f56-a9ac-41a3-9a41-bb172dbc9cae", + "name": "viewSorts" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "c55193eb-042d-42d5-a6a7-8263fd1433a2", + "nameSingular": "viewSort", + "namePlural": "viewSorts" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "d0bce78c-57eb-48c1-9a66-5e0de3d63a61", + "name": "view" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "46ed3f5f-46da-4475-acae-0d91a634d7e2", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3bc56d31-4180-49d8-9aab-a5de97a6d180", + "type": "UUID", + "name": "objectMetadataId", + "label": "Object Metadata Id", + "description": "View target object", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6cefd31f-49fd-4e11-b306-3017ca5a653d", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "View name", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "196ec541-c7a6-49d3-bc76-138511d47263", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9802ccbc-bfab-42eb-b8f3-1f4cb61d3a26", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "opportunity", + "namePlural": "opportunities", + "labelSingular": "Opportunity", + "labelPlural": "Opportunities", + "description": "An opportunity", + "icon": "IconTargetArrow", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "08f7b793-78cc-4cc8-b0d3-19d9b7f61370", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "5c9d3acc-4705-4cc5-960e-73e436a0fad2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_9f96d65260c4676faac27cb6bf3", + "indexWhereClause": null, + "indexType": "GIN", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "7535eeab-940b-4f4f-a541-8a32da4b80a2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_425ac6c73ecb993cf9cbc2c2b00", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "27564152-a4a9-4e1e-804e-541cfb4318a7", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_82cdf247553f960093baa7c6635", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "defacb32-eb1f-4538-a17b-6032425ad870", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "a6152400-37e6-4cb1-91e0-50d2e9f88801" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d269e2cd-5b30-4252-90ff-ee583e2be728", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "665675f5-e856-489e-8f05-0233a3c40507" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "0d3d7f9e-cce7-4eef-8c7d-381d12fc4606", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_4f469d3a7ee08aefdc099836364", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "06af04f3-4899-460b-be19-2a821c327eec", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "368d7023-7d0d-4c18-98f5-b83b2633e689" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "ac9fa71b-6751-4f9d-93b1-8321aff03e6f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "665675f5-e856-489e-8f05-0233a3c40507" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjIw" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9637bff7-b622-48f7-8f6c-2ea7ee18edef", + "type": "RELATION", + "name": "noteTargets", + "label": "Notes", + "description": "Notes tied to the opportunity", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "48b33f6e-6f79-4990-9d3a-7bc3ae6020bb", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "9637bff7-b622-48f7-8f6c-2ea7ee18edef", + "name": "noteTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "fa189561-0de7-478f-8673-9f6b68527bed", + "name": "opportunity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6e66cbc4-5e29-4bea-85ce-2dd4eda73a73", + "type": "TS_VECTOR", + "name": "searchVector", + "label": "Search vector", + "description": "Field used for full-text search", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a6152400-37e6-4cb1-91e0-50d2e9f88801", + "type": "UUID", + "name": "pointOfContactId", + "label": "Point of Contact id (foreign key)", + "description": "Opportunity point of contact id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "788977f3-a7c3-4686-8d46-a915649d524d", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "23df41ee-2db8-4901-bab4-7ebbc763f5b7", + "type": "CURRENCY", + "name": "amount", + "label": "Amount", + "description": "Opportunity amount", + "icon": "IconCurrencyDollar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "amountMicros": null, + "currencyCode": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c7d6d50c-3677-4639-8c2e-801ac13be879", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a679e0f1-1766-4814-ac8e-64b82329cdb9", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "Opportunity company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c2fcfb71-1f23-47b7-a818-27371a165214", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "a679e0f1-1766-4814-ac8e-64b82329cdb9", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8323137c-6b37-4ec2-9977-accefa773841", + "name": "opportunities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "30756ebf-d27c-4ffb-a30f-8a04b909839f", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the opportunity", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "85086085-d6fb-4f1c-9f77-1edd0b07adcb", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "30756ebf-d27c-4ffb-a30f-8a04b909839f", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "0d9f922f-d7db-485e-b5ff-1293a0b51dc9", + "name": "opportunity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ed057590-4d06-4159-a153-48f124dfed73", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "08f7b793-78cc-4cc8-b0d3-19d9b7f61370", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "The opportunity name", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "665675f5-e856-489e-8f05-0233a3c40507", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d560e379-92da-454c-9f4a-1be6d5543816", + "type": "RELATION", + "name": "timelineActivities", + "label": "Timeline Activities", + "description": "Timeline Activities linked to the opportunity.", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0f840679-a025-4d20-9bc0-102e5865dda0", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "d560e379-92da-454c-9f4a-1be6d5543816", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "58dbba6f-4a0c-4db7-883a-8a99618be069", + "name": "opportunity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3d67a957-e029-4f7d-b04d-8fedb77a4c57", + "type": "DATE_TIME", + "name": "closeDate", + "label": "Close date", + "description": "Opportunity close date", + "icon": "IconCalendarEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d9ca2742-9b92-47b2-8af3-63121919d3e6", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Opportunity record position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ba574a70-ed4f-450b-be6b-71907d7264cd", + "type": "RELATION", + "name": "activityTargets", + "label": "Activities", + "description": "Activities tied to the opportunity", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "a0f78a63-3342-48c2-966d-2de9111ef8ca", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "ba574a70-ed4f-450b-be6b-71907d7264cd", + "name": "activityTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "36de1418-2c3d-4444-b88f-606993e3d796", + "name": "opportunity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "db2d038b-73d8-4c99-a709-8737e7e6658c", + "type": "RELATION", + "name": "taskTargets", + "label": "Tasks", + "description": "Tasks tied to the opportunity", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "3edc0e3a-d735-4b9c-ac34-352b48a6e705", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "db2d038b-73d8-4c99-a709-8737e7e6658c", + "name": "taskTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f0adc079-73ce-4316-80e0-90295a7192f1", + "name": "opportunity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "368d7023-7d0d-4c18-98f5-b83b2633e689", + "type": "SELECT", + "name": "stage", + "label": "Stage", + "description": "Opportunity stage", + "icon": "IconProgressCheck", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'NEW'", + "options": [ + { + "id": "1127a5a1-03ee-4cfd-8319-4b33603f93d7", + "color": "red", + "label": "New", + "value": "NEW", + "position": 0 + }, + { + "id": "5d04982d-f01c-4ad4-8487-d0a12998fbb5", + "color": "purple", + "label": "Screening", + "value": "SCREENING", + "position": 1 + }, + { + "id": "ec927e11-84b1-4d43-a948-cb6ed0642a52", + "color": "sky", + "label": "Meeting", + "value": "MEETING", + "position": 2 + }, + { + "id": "f92507db-1c45-496f-9647-ca3d9014d1f1", + "color": "turquoise", + "label": "Proposal", + "value": "PROPOSAL", + "position": 3 + }, + { + "id": "ca5dea89-f5d5-42e2-b623-c43663338c0a", + "color": "yellow", + "label": "Customer", + "value": "CUSTOMER", + "position": 4 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f2b2e717-0c19-417c-b81a-98d95cadcc67", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Attachments linked to the opportunity", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0984e2b5-1ba8-46d8-ade2-11bf79f52557", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f2b2e717-0c19-417c-b81a-98d95cadcc67", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f84f5b1b-70b1-4d06-9093-d4be29917d3e", + "name": "opportunity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "24a6beff-2f9f-4dd6-8d77-728080e72adb", + "type": "RELATION", + "name": "pointOfContact", + "label": "Point of Contact", + "description": "Opportunity point of contact", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "cdcedc94-c874-441c-a3ee-9d787cc67cce", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "24a6beff-2f9f-4dd6-8d77-728080e72adb", + "name": "pointOfContact" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "fd058bb2-7fee-4d8c-8cce-031cb50e7bfe", + "name": "pointOfContactForOpportunities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5defb8ea-aeae-4f30-a5de-86a67f285c8e", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "20d9ac99-1460-4382-ba48-1a2fd242b4f9", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "Opportunity company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "person", + "namePlural": "people", + "labelSingular": "Person", + "labelPlural": "People", + "description": "A person", + "icon": "IconUser", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "7da10df3-f210-421f-9003-533fe5a19dee", + "imageIdentifierFieldMetadataId": "4c3340cd-5177-41e3-a76a-12bd85f413b8", + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "88deeb4f-02da-4415-9b86-c706e1f9018f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_UNIQUE_87914cd3ce963115f8cb943e2ac", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": true, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "c691cb88-ca83-418f-8b47-a0781ef6be3c", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_6a862a788ac6ce967afa06df812", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d26f789a-9291-4e84-8e9e-4d8cb2cb0a8e", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "9733d9e6-cfea-45ca-b8e3-2368c72c6c22" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f021ef99-96fc-46aa-8c92-0fea7900e780", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_bbd7aec1976fc684a0a5e4816c9", + "indexWhereClause": null, + "indexType": "GIN", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d80aa4d1-5fbe-4921-8dda-b69d6fbe4a38", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "7b699b17-5d81-41cd-a16c-a9e678a6ab40" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjI5" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0dec9acf-ed41-4b67-8722-a16296bd79aa", + "type": "RELATION", + "name": "calendarEventParticipants", + "label": "Calendar Event Participants", + "description": "Calendar Event Participants", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "d0aca517-d1b2-4923-a374-65c899bf677d", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "0dec9acf-ed41-4b67-8722-a16296bd79aa", + "name": "calendarEventParticipants" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "3ffb6e9f-600b-40bd-90b8-d042607b3c64", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "561cbb88-39b8-47e2-9f48-6ed929f6ca2a", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Attachments linked to the contact.", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0a89048f-6e64-4a8d-ad24-51ab63af3a85", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "561cbb88-39b8-47e2-9f48-6ed929f6ca2a", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "144087ac-d621-4897-8172-14578d80a07b", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b0200630-5587-4c10-b5ca-1d1344ee2343", + "type": "RELATION", + "name": "noteTargets", + "label": "Notes", + "description": "Notes tied to the contact", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "9eac5248-f58a-49d3-ab69-376c12f71680", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "b0200630-5587-4c10-b5ca-1d1344ee2343", + "name": "noteTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "63ae8928-a35e-4053-bf6c-ee548e3614c4", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6eed0523-94de-4b4b-8c11-8173b1efbd84", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "81f8a863-1a7a-40ed-a6bc-c1217122a9e4", + "type": "RATING", + "name": "performanceRating", + "label": "Performance Rating", + "description": "Person's Performance Rating", + "icon": "IconStars", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.600Z", + "updatedAt": "2024-10-10T15:05:42.600Z", + "defaultValue": null, + "options": [ + { + "id": "1a96cbbe-4d42-4796-b1cc-d7d57f64af0d", + "label": "1", + "value": "RATING_1", + "position": 0 + }, + { + "id": "4357a50c-5ce4-4b21-b667-cf330053dd2b", + "label": "2", + "value": "RATING_2", + "position": 1 + }, + { + "id": "a884885a-1eba-4c22-84f2-28d4b60387af", + "label": "3", + "value": "RATING_3", + "position": 2 + }, + { + "id": "4336f387-6651-4be8-8c3d-5e4e21091087", + "label": "4", + "value": "RATING_4", + "position": 3 + }, + { + "id": "b7a661cc-67b1-4c40-a8bf-bcab4912d086", + "label": "5", + "value": "RATING_5", + "position": 4 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "40d5dd09-08b0-4b6b-af56-2983d6437941", + "type": "TEXT", + "name": "city", + "label": "City", + "description": "Contactโ€™s city", + "icon": "IconMap", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "418c54ba-a0da-4feb-8198-9287bf38e31c", + "type": "RELATION", + "name": "activityTargets", + "label": "Activities", + "description": "Activities tied to the contact", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "85634196-b66b-4921-b30f-c4e078b58926", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "418c54ba-a0da-4feb-8198-9287bf38e31c", + "name": "activityTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "54a9d278-cf83-4152-bdfb-f26245d39d65", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2b4a6b30-b62d-4c77-a0ee-c4107d999451", + "type": "EMAILS", + "name": "emails", + "label": "Emails", + "description": "Contactโ€™s Emails", + "icon": "IconMail", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryEmail": "''", + "additionalEmails": null + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c502a91b-fde5-426b-aa27-a856ad28fc71", + "type": "RELATION", + "name": "messageParticipants", + "label": "Message Participants", + "description": "Message Participants", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "89f811eb-6211-4b53-bc32-1acf1241a5dd", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "c502a91b-fde5-426b-aa27-a856ad28fc71", + "name": "messageParticipants" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8507c38d-6f90-4b15-afa1-19b749774092", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b9d318fd-1f3c-45eb-afcc-1f0a4133e2a4", + "type": "MULTI_SELECT", + "name": "workPreference", + "label": "Work Preference", + "description": "Person's Work Preference", + "icon": "IconHome", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.497Z", + "updatedAt": "2024-10-10T15:05:42.497Z", + "defaultValue": null, + "options": [ + { + "id": "d93d744c-999d-44ce-b374-cedffe58a570", + "color": "green", + "label": "On-Site", + "value": "ON_SITE", + "position": 0 + }, + { + "id": "0c1c6f5c-e2ad-4330-9855-06982c930095", + "color": "turquoise", + "label": "Hybrid", + "value": "HYBRID", + "position": 1 + }, + { + "id": "56ab2069-89ce-491e-8743-6260ad4e906c", + "color": "sky", + "label": "Remote Work", + "value": "REMOTE_WORK", + "position": 2 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "812d8e31-43da-429c-8189-391e7492e6f5", + "type": "PHONES", + "name": "whatsapp", + "label": "Whatsapp", + "description": "Contact's Whatsapp Number", + "icon": "IconBrandWhatsapp", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.397Z", + "updatedAt": "2024-10-10T15:05:42.397Z", + "defaultValue": [ + { + "additionalPhones": {}, + "primaryPhoneNumber": "", + "primaryPhoneCountryCode": "" + } + ], + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9f20ae61-c8c6-40a9-b1c0-c4e1417affee", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Person record Position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f62802b9-ef34-4186-8db9-ab2d365aa51a", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7b699b17-5d81-41cd-a16c-a9e678a6ab40", + "type": "TS_VECTOR", + "name": "searchVector", + "label": "Search vector", + "description": "Field used for full-text search", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "60664773-e0eb-4376-8d17-543eca22b19d", + "type": "PHONES", + "name": "phones", + "label": "Phones", + "description": "Contactโ€™s phone numbers", + "icon": "IconPhone", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "additionalPhones": null, + "primaryPhoneNumber": "''", + "primaryPhoneCountryCode": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "89da564c-2cb7-4ffa-9bfb-b067222d167c", + "type": "LINKS", + "name": "linkedinLink", + "label": "Linkedin", + "description": "Contactโ€™s Linkedin account", + "icon": "IconBrandLinkedin", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fd058bb2-7fee-4d8c-8cce-031cb50e7bfe", + "type": "RELATION", + "name": "pointOfContactForOpportunities", + "label": "Linked Opportunities", + "description": "List of opportunities for which that person is the point of contact", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "cdcedc94-c874-441c-a3ee-9d787cc67cce", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "fd058bb2-7fee-4d8c-8cce-031cb50e7bfe", + "name": "pointOfContactForOpportunities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "24a6beff-2f9f-4dd6-8d77-728080e72adb", + "name": "pointOfContact" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "90040907-df23-4e64-a35e-b1f109b730b5", + "type": "TEXT", + "name": "jobTitle", + "label": "Job Title", + "description": "Contactโ€™s job title", + "icon": "IconBriefcase", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4c3340cd-5177-41e3-a76a-12bd85f413b8", + "type": "TEXT", + "name": "avatarUrl", + "label": "Avatar", + "description": "Contactโ€™s avatar", + "icon": "IconFileUpload", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "06a69687-dab0-4edd-ba1b-1e23c01ddd11", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7da10df3-f210-421f-9003-533fe5a19dee", + "type": "FULL_NAME", + "name": "name", + "label": "Name", + "description": "Contactโ€™s name", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "lastName": "''", + "firstName": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9733d9e6-cfea-45ca-b8e3-2368c72c6c22", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "Contactโ€™s company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "80092f7e-405b-4a94-b767-cdf877b2ef41", + "type": "RELATION", + "name": "taskTargets", + "label": "Tasks", + "description": "Tasks tied to the contact", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7fd53175-abb0-470e-a394-9c1de590b24e", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "80092f7e-405b-4a94-b767-cdf877b2ef41", + "name": "taskTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "80b77505-9340-45f2-8c9e-931754da1192", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "42052d18-6fab-4d08-bb69-667683392617", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the contact", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "033e99d2-7a1a-405c-950d-06031b93a773", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "42052d18-6fab-4d08-bb69-667683392617", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7626da40-ddb4-4cbc-8554-441f9785ef5e", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7d307698-eece-4e31-8c4c-7d5d596eb542", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "Contactโ€™s company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "659373cb-ab85-4d36-aed2-58789be66d23", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7d307698-eece-4e31-8c4c-7d5d596eb542", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7fbd8d22-49a3-4c55-a80b-1876129eee86", + "name": "people" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "81bb2a30-e02d-48ff-925d-14a6c84d535c", + "type": "RELATION", + "name": "timelineActivities", + "label": "Events", + "description": "Events linked to the person", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "66a4759e-f5f3-4979-9891-f7c8c60143df", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "81bb2a30-e02d-48ff-925d-14a6c84d535c", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "ac01d46e-e6da-42d7-aa8b-16eb964485fa", + "name": "person" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "17fc5491-8def-413f-8f02-e1e70bcacbb2", + "type": "TEXT", + "name": "intro", + "label": "Intro", + "description": "Contact's Intro", + "icon": "IconNote", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.312Z", + "updatedAt": "2024-10-10T15:05:42.312Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1144f285-5116-49ac-91c1-347877888fcf", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "745f585b-7ac3-45c1-9d3a-c984f420c32a", + "type": "LINKS", + "name": "xLink", + "label": "X", + "description": "Contactโ€™s X/Twitter account", + "icon": "IconBrandX", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1929a65e-2e30-4b56-8f85-5c18d9948b57", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities", + "labelSingular": "Timeline Activity", + "labelPlural": "Timeline Activities", + "description": "Aggregated / filtered event to be displayed on the timeline", + "icon": "IconIconTimelineEvent", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "86139958-429c-4d36-80b6-a6faa4ed80ef", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d019f884-cea1-4b6a-a1c7-cb75a63a8bb1", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_a98bc2277b52c6dd52303e52c21", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "766b1864-0ba4-4b3d-b8df-a35c81af7f35", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "a1c53a03-7e99-4d03-bab4-7c7f04f6790e" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "956842c7-621e-41a9-95de-8c68f16e3136", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "d9162917-c820-4002-8241-0de6ad5ad8d7" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "645073b7-e8e5-49de-8b3f-3f9720296159", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_84b1e01cb0480e514a6e7ec0095", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "ada56d55-9814-424e-83da-9b584ea0a94b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "a1c53a03-7e99-4d03-bab4-7c7f04f6790e" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "98810481-bd9a-4ab1-bc2c-2c81e4880f50", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "e0415588-ce4c-4715-8027-73e4f59ce40b" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "6620b20c-e1c7-48c3-a949-4ddd7e121fd0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_7e0d952730f13369e3bd9c2f1a9", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d9ca91ee-6794-4c20-b691-74f0f4091e62", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "a1c53a03-7e99-4d03-bab4-7c7f04f6790e" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "f559be97-9dae-416c-99df-399a3196f84c", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "27af584b-7869-48cd-82bf-b7cea235cfbb" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "b0a44268-a8cc-4d34-b6ac-2c4f41daa993", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_a930d316a6b4f3b81d3f026dd16", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "d7209fa3-c69a-4999-9e19-ae7e7ceee1c0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "ced82236-eddf-45f8-8378-6a84b285c8ba" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "0bb617e3-ede3-40d2-b4f6-617bba617132", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_b292fe34a9e2d55884febd07e93", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "173120ce-b190-45d5-9fe5-c2324268c4ed", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "a1c53a03-7e99-4d03-bab4-7c7f04f6790e" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "9876795c-3bdc-4f96-9500-a078d118117f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "0af727ed-8edf-438f-ad53-b9b8b4af8bc1" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "68babc5a-ed9a-4b27-b2a9-b20224b46f9a", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_daf6592d1dff4cff3401bf23c67", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "7b157673-8b45-479e-a460-6dca57c36204", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "a1c53a03-7e99-4d03-bab4-7c7f04f6790e" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "3e40e0d6-d4c1-4a34-a9c1-df89ee8cd6d4", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "fe1628ec-c28d-4fcb-9e43-b9335be3ef81" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjIz" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a1c53a03-7e99-4d03-bab4-7c7f04f6790e", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "58dbba6f-4a0c-4db7-883a-8a99618be069", + "type": "RELATION", + "name": "opportunity", + "label": "Opportunity", + "description": "Event opportunity", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0f840679-a025-4d20-9bc0-102e5865dda0", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "58dbba6f-4a0c-4db7-883a-8a99618be069", + "name": "opportunity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "d560e379-92da-454c-9f4a-1be6d5543816", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3156baf4-4034-4c43-bba6-345807b15bc3", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "93e24db5-c860-420d-90eb-782c3855a36d", + "type": "RELATION", + "name": "workspaceMember", + "label": "Workspace Member", + "description": "Event workspace member", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "5622d0fd-cbf2-4baa-8be9-bc5ea20d321b", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "93e24db5-c860-420d-90eb-782c3855a36d", + "name": "workspaceMember" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "6d033705-c48b-43e0-9cc1-5191c0f9ecd2", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d9162917-c820-4002-8241-0de6ad5ad8d7", + "type": "UUID", + "name": "noteId", + "label": "Note id (foreign key)", + "description": "Event note id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8376c21d-fd88-4006-b8d7-40d1f27692ac", + "type": "RELATION", + "name": "note", + "label": "Note", + "description": "Event note", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "49ad3d9a-01a3-4c82-b6bc-a8d21398e51a", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8376c21d-fd88-4006-b8d7-40d1f27692ac", + "name": "note" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "325aafc9-525a-457e-9f67-15a8994bd6b8", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2d6776a4-7f43-46a6-9fbf-cf1fa21cd13f", + "type": "DATE_TIME", + "name": "happensAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a8323586-5ff9-491c-a067-ae4a348adaf5", + "type": "TEXT", + "name": "linkedRecordCachedName", + "label": "Linked Record cached name", + "description": "Cached record name", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f4341a81-fcba-4480-a757-929635181162", + "type": "UUID", + "name": "rocketId", + "label": "Rocket ID (foreign key)", + "description": "Timeline Activity Rocket id foreign key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.708Z", + "updatedAt": "2024-10-10T15:05:42.708Z", + "defaultValue": null, + "options": null, + "settings": { + "isForeignKey": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "86139958-429c-4d36-80b6-a6faa4ed80ef", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "41761644-f778-43cb-a5d8-80286d7dee8c", + "type": "TEXT", + "name": "name", + "label": "Event name", + "description": "Event name", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fe1628ec-c28d-4fcb-9e43-b9335be3ef81", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "Event person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7d14aef8-f63c-47cd-8ce7-29806518d6ca", + "type": "RELATION", + "name": "rocket", + "label": "Rocket", + "description": "Timeline Activity Rocket", + "icon": "IconTimeline", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.709Z", + "updatedAt": "2024-10-10T15:05:42.709Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "f8ddd621-26f4-4fa8-b426-3f545094bd5f", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7d14aef8-f63c-47cd-8ce7-29806518d6ca", + "name": "rocket" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "baea02d1-1817-4df3-b9eb-c8020452f3e0", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5096824c-bd72-488b-902e-e5768070e736", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "Event company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0f9bad06-3762-4797-9079-d7d190da55e5", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "5096824c-bd72-488b-902e-e5768070e736", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "50c723e9-af8c-455e-a9c6-0dbd591b0258", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e0415588-ce4c-4715-8027-73e4f59ce40b", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "Event company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4e1bab9b-3855-42c5-b0ea-7fcc119949fe", + "type": "UUID", + "name": "linkedObjectMetadataId", + "label": "Linked Object Metadata Id", + "description": "inked Object Metadata Id", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ced82236-eddf-45f8-8378-6a84b285c8ba", + "type": "UUID", + "name": "workspaceMemberId", + "label": "Workspace Member id (foreign key)", + "description": "Event workspace member id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "49b48088-8d98-4d8d-a9a8-c4f8f462aa72", + "type": "RELATION", + "name": "task", + "label": "Task", + "description": "Event task", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "e940e46a-ab1a-4e0c-82a4-1af978adcc2c", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "49b48088-8d98-4d8d-a9a8-c4f8f462aa72", + "name": "task" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "77726d5f-43e6-4011-952a-d9585dc14597", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ac01d46e-e6da-42d7-aa8b-16eb964485fa", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "Event person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "66a4759e-f5f3-4979-9891-f7c8c60143df", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "ac01d46e-e6da-42d7-aa8b-16eb964485fa", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "81bb2a30-e02d-48ff-925d-14a6c84d535c", + "name": "timelineActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c1522e3e-e03f-43d3-971c-af60e584ca7a", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1a313c5f-8fbe-4986-9d1c-933e9ed4053c", + "type": "RAW_JSON", + "name": "properties", + "label": "Event details", + "description": "Json value for event details", + "icon": "IconListDetails", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0af727ed-8edf-438f-ad53-b9b8b4af8bc1", + "type": "UUID", + "name": "taskId", + "label": "Task id (foreign key)", + "description": "Event task id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0a5469c0-341b-4085-bc79-a75bae50a278", + "type": "UUID", + "name": "linkedRecordId", + "label": "Linked Record id", + "description": "Linked Record id", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "27af584b-7869-48cd-82bf-b7cea235cfbb", + "type": "UUID", + "name": "opportunityId", + "label": "Opportunity id (foreign key)", + "description": "Event opportunity id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants", + "labelSingular": "Calendar event participant", + "labelPlural": "Calendar event participants", + "description": "Calendar event participants", + "icon": "IconCalendar", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "cfcd1013-a7a8-492d-acbe-c5266baed6cd", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "6c2da5c4-b0b0-41d1-967b-7dfc0fac7a06", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_0da422bbe7adbabb8144c696ebd", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8ffd767f-2987-402f-8a27-13245a019583", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "5434e1fa-b089-4dd2-ba71-230ec323236f" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "8c3e1ee1-755e-4984-8ee0-15468d599b5f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_dd22aee9059fd7002165df6d8cc", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8de5e4bc-53ba-44ee-821c-d4ec87ef7997", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "af004cd3-1b68-41be-a50a-6e406c979968" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "914d1b2f-2ff6-40ad-94fe-e64b4eda1dae", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_2bf094726f6d91639302c1c143d", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "6d19cbcf-1260-4188-a3bb-dd6360f89851", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "ab0136fe-6d3a-469a-81ef-a3c5f4426226" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "0de99eff-7da0-4dbd-a5be-823464ed6522", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "af004cd3-1b68-41be-a50a-6e406c979968" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3c4d9816-97f2-4528-ab9b-47013dd6fa12", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "733e467e-f258-4d83-aa99-2db4674a9c10", + "type": "SELECT", + "name": "responseStatus", + "label": "Response Status", + "description": "Response Status", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'NEEDS_ACTION'", + "options": [ + { + "id": "f42abc88-10a8-4e85-8beb-22c099372e83", + "color": "orange", + "label": "Needs Action", + "value": "NEEDS_ACTION", + "position": 0 + }, + { + "id": "4ba52cef-ccdb-4e62-a811-6f7db31fb836", + "color": "red", + "label": "Declined", + "value": "DECLINED", + "position": 1 + }, + { + "id": "790aa598-64bc-4be7-a1f1-c87380fbcc36", + "color": "yellow", + "label": "Tentative", + "value": "TENTATIVE", + "position": 2 + }, + { + "id": "777111bf-a14a-4e36-9626-647c75978155", + "color": "green", + "label": "Accepted", + "value": "ACCEPTED", + "position": 3 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "77115ac7-f8d1-486f-9c8d-020532f996c1", + "type": "TEXT", + "name": "displayName", + "label": "Display Name", + "description": "Display Name", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "409c079f-742a-4bf5-a710-c782544fa21b", + "type": "RELATION", + "name": "workspaceMember", + "label": "Workspace Member", + "description": "Workspace Member", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "15a3fcda-90b1-4599-a31c-bd0807184401", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "409c079f-742a-4bf5-a710-c782544fa21b", + "name": "workspaceMember" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "e167ca8f-66e1-4d49-8a2f-eb8c96c2285d", + "name": "calendarEventParticipants" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f75ef0a1-23d5-4504-8ccf-5729806d14b5", + "type": "RELATION", + "name": "calendarEvent", + "label": "Event ID", + "description": "Event ID", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "cd9d90ae-65c2-49b4-ac70-76335498e9f1", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f75ef0a1-23d5-4504-8ccf-5729806d14b5", + "name": "calendarEvent" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "981fd8a9-37a2-4742-98c1-08509d995bd3", + "nameSingular": "calendarEvent", + "namePlural": "calendarEvents" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "db316e57-6812-40b7-8c9f-e85f0f492ca8", + "name": "calendarEventParticipants" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "cfcd1013-a7a8-492d-acbe-c5266baed6cd", + "type": "TEXT", + "name": "handle", + "label": "Handle", + "description": "Handle", + "icon": "IconMail", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "af004cd3-1b68-41be-a50a-6e406c979968", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f3a2d134-0b23-4bc6-a857-cbaeb85a54d8", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ab0136fe-6d3a-469a-81ef-a3c5f4426226", + "type": "UUID", + "name": "workspaceMemberId", + "label": "Workspace Member id (foreign key)", + "description": "Workspace Member id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3ffb6e9f-600b-40bd-90b8-d042607b3c64", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "Person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "d0aca517-d1b2-4923-a374-65c899bf677d", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "3ffb6e9f-600b-40bd-90b8-d042607b3c64", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "0dec9acf-ed41-4b67-8722-a16296bd79aa", + "name": "calendarEventParticipants" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "57fa6b52-2169-4ecf-ab1a-6c5a5983dbb4", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5434e1fa-b089-4dd2-ba71-230ec323236f", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "Person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "40d7bc5e-2d27-4fb4-afd8-99434799e144", + "type": "BOOLEAN", + "name": "isOrganizer", + "label": "Is Organizer", + "description": "Is Organizer", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": false, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f5a2f8c3-aff4-484e-9dc6-5a649031dc5f", + "type": "UUID", + "name": "calendarEventId", + "label": "Event ID id (foreign key)", + "description": "Event ID id foreign key", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "981fd8a9-37a2-4742-98c1-08509d995bd3", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "calendarEvent", + "namePlural": "calendarEvents", + "labelSingular": "Calendar event", + "labelPlural": "Calendar events", + "description": "Calendar events", + "icon": "IconCalendar", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "49a3400e-e71f-40cb-86b0-bfa8d2ef02ca", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE3" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9d659849-ffbb-4550-9590-e6fa9967b586", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2d337999-7468-4f6f-bce0-87679dcb5e2f", + "type": "RELATION", + "name": "calendarChannelEventAssociations", + "label": "Calendar Channel Event Associations", + "description": "Calendar Channel Event Associations", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "daa269f7-c111-492e-88c8-1bfe82e3d637", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "981fd8a9-37a2-4742-98c1-08509d995bd3", + "nameSingular": "calendarEvent", + "namePlural": "calendarEvents" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "2d337999-7468-4f6f-bce0-87679dcb5e2f", + "name": "calendarChannelEventAssociations" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "149f1a0d-f528-48a3-a3f8-0203926d07f5", + "nameSingular": "calendarChannelEventAssociation", + "namePlural": "calendarChannelEventAssociations" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1dc48a6a-270b-466a-8e11-9efd02729791", + "name": "calendarEvent" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d83e0bfc-1879-4a13-92b1-e639f58b2d20", + "type": "TEXT", + "name": "description", + "label": "Description", + "description": "Description", + "icon": "IconFileDescription", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9dcd16f4-35f6-4cd3-b13a-6d9f18d6ef3c", + "type": "TEXT", + "name": "conferenceSolution", + "label": "Conference Solution", + "description": "Conference Solution", + "icon": "IconScreenShare", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0500721b-4273-4d6b-be4c-3458ac521ef5", + "type": "BOOLEAN", + "name": "isFullDay", + "label": "Is Full Day", + "description": "Is Full Day", + "icon": "Icon24Hours", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": false, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "747dc06a-73f3-43cc-ac03-6195edd77034", + "type": "DATE_TIME", + "name": "startsAt", + "label": "Start Date", + "description": "Start Date", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d2e9755f-4593-452b-b3c0-8debd51153d6", + "type": "TEXT", + "name": "iCalUID", + "label": "iCal UID", + "description": "iCal UID", + "icon": "IconKey", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "db316e57-6812-40b7-8c9f-e85f0f492ca8", + "type": "RELATION", + "name": "calendarEventParticipants", + "label": "Event Participants", + "description": "Event Participants", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "cd9d90ae-65c2-49b4-ac70-76335498e9f1", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "981fd8a9-37a2-4742-98c1-08509d995bd3", + "nameSingular": "calendarEvent", + "namePlural": "calendarEvents" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "db316e57-6812-40b7-8c9f-e85f0f492ca8", + "name": "calendarEventParticipants" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "af56ee43-5666-482f-a980-434fefac00c7", + "nameSingular": "calendarEventParticipant", + "namePlural": "calendarEventParticipants" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "f75ef0a1-23d5-4504-8ccf-5729806d14b5", + "name": "calendarEvent" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c7a5ce10-8ca8-40ec-a989-45cb53475d4a", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "82922b98-0f27-421a-8b70-0b845fc827e7", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6edaa2aa-617d-4f54-906e-885682bd6d1c", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "918e7bcf-0afb-4857-9b35-ec307ccfb1ac", + "type": "DATE_TIME", + "name": "externalCreatedAt", + "label": "Creation DateTime", + "description": "Creation DateTime", + "icon": "IconCalendarPlus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "02e208a0-45cc-429b-ba83-32406e20cb2e", + "type": "DATE_TIME", + "name": "endsAt", + "label": "End Date", + "description": "End Date", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c0383d68-3275-4827-9350-f7a117869e16", + "type": "LINKS", + "name": "conferenceLink", + "label": "Meet Link", + "description": "Meet Link", + "icon": "IconLink", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7bbecfaa-b414-40b7-b72e-e5285ff61818", + "type": "BOOLEAN", + "name": "isCanceled", + "label": "Is canceled", + "description": "Is canceled", + "icon": "IconCalendarCancel", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": false, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7a6164bc-0594-40de-94b6-b79065dadaca", + "type": "TEXT", + "name": "location", + "label": "Location", + "description": "Location", + "icon": "IconMapPin", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "49a3400e-e71f-40cb-86b0-bfa8d2ef02ca", + "type": "TEXT", + "name": "title", + "label": "Title", + "description": "Title", + "icon": "IconH1", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ba5611be-cafc-41bb-bdfc-a4881b6adb25", + "type": "DATE_TIME", + "name": "externalUpdatedAt", + "label": "Update DateTime", + "description": "Update DateTime", + "icon": "IconCalendarCog", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "92b529f1-b82b-4352-a0d5-18f32f8e47ab", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "messageChannel", + "namePlural": "messageChannels", + "labelSingular": "Message Channel", + "labelPlural": "Message Channels", + "description": "Message Channels", + "icon": "IconMessage", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "c6b9967f-3bf3-4de8-a0f7-1a5d8404068f", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f45f65e3-0387-4af1-be3e-162dc3c77a62", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_c3af632ce35236d21f8ae1f4cfd", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "98488b85-c958-4fa4-9f8b-5b0387eee9e8", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "7365f80a-9b92-4aeb-81aa-0a25be7f0e97" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjIw" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4a315b5d-ec77-47cf-9467-cc3d86328f02", + "type": "RELATION", + "name": "connectedAccount", + "label": "Connected Account", + "description": "Connected Account", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "4b072b41-d4e4-4bb0-b64b-51b17887b5a3", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "92b529f1-b82b-4352-a0d5-18f32f8e47ab", + "nameSingular": "messageChannel", + "namePlural": "messageChannels" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "4a315b5d-ec77-47cf-9467-cc3d86328f02", + "name": "connectedAccount" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7036df84-657d-4e56-bb7b-48a8c72df2be", + "name": "messageChannels" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b38ce87f-490e-4787-b665-096ca83ea9d7", + "type": "DATE_TIME", + "name": "syncStageStartedAt", + "label": "Sync stage started at", + "description": "Sync stage started at", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "df4c1a6b-ed46-4ca5-baf7-6f0fb053e1bf", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2db95ca6-4d28-46a1-aef8-7e947244bf17", + "type": "SELECT", + "name": "type", + "label": "Type", + "description": "Channel Type", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'email'", + "options": [ + { + "id": "dda1da8b-785f-4d7f-8f42-8e9f48c3ba7d", + "color": "green", + "label": "Email", + "value": "email", + "position": 0 + }, + { + "id": "72d3ec38-a442-442b-bea8-34252a14769b", + "color": "blue", + "label": "SMS", + "value": "sms", + "position": 1 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "58657610-ed68-4735-933e-390cfa3b448a", + "type": "TEXT", + "name": "syncCursor", + "label": "Last sync cursor", + "description": "Last sync cursor", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "18c6892b-8723-41fd-b414-5fbd14e59fd4", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c6b9967f-3bf3-4de8-a0f7-1a5d8404068f", + "type": "TEXT", + "name": "handle", + "label": "Handle", + "description": "Handle", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6a0dc17c-504f-4f37-b423-7e4a806b89df", + "type": "BOOLEAN", + "name": "excludeNonProfessionalEmails", + "label": "Exclude non professional emails", + "description": "Exclude non professional emails", + "icon": "IconBriefcase", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5e01dcbf-ed18-43e9-a2d0-ee3286d9d0bf", + "type": "UUID", + "name": "connectedAccountId", + "label": "Connected Account id (foreign key)", + "description": "Connected Account id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "79232ac7-685b-4591-978c-7ce86bd10aec", + "type": "BOOLEAN", + "name": "isContactAutoCreationEnabled", + "label": "Is Contact Auto Creation Enabled", + "description": "Is Contact Auto Creation Enabled", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7365f80a-9b92-4aeb-81aa-0a25be7f0e97", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "90648e68-2f7c-4d0c-ab8a-79e6eec769ee", + "type": "RELATION", + "name": "messageChannelMessageAssociations", + "label": "Message Channel Association", + "description": "Messages from the channel.", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "612d689f-eb0d-4e6b-b72e-82acb8ae76e2", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "92b529f1-b82b-4352-a0d5-18f32f8e47ab", + "nameSingular": "messageChannel", + "namePlural": "messageChannels" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "90648e68-2f7c-4d0c-ab8a-79e6eec769ee", + "name": "messageChannelMessageAssociations" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "631882fd-28e8-4a87-8ceb-f8217006a620", + "nameSingular": "messageChannelMessageAssociation", + "namePlural": "messageChannelMessageAssociations" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "aac54567-521e-4fe8-a0bc-116f2aabf592", + "name": "messageChannel" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ee91dcdd-90d4-4258-9575-cf5a49c2c67c", + "type": "BOOLEAN", + "name": "excludeGroupEmails", + "label": "Exclude group emails", + "description": "Exclude group emails", + "icon": "IconUsersGroup", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b5da4f14-5aca-4311-bf0c-06a0678388a1", + "type": "BOOLEAN", + "name": "isSyncEnabled", + "label": "Is Sync Enabled", + "description": "Is Sync Enabled", + "icon": "IconRefresh", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3b99363d-b465-47ee-b7d6-8f9776c5aa84", + "type": "SELECT", + "name": "visibility", + "label": "Visibility", + "description": "Visibility", + "icon": "IconEyeglass", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'SHARE_EVERYTHING'", + "options": [ + { + "id": "ab451f3e-e3c0-4695-bcdb-f4e1d4d80d9a", + "color": "green", + "label": "Metadata", + "value": "METADATA", + "position": 0 + }, + { + "id": "9b543bcd-1a4d-4daf-ac80-1bf6a10451e8", + "color": "blue", + "label": "Subject", + "value": "SUBJECT", + "position": 1 + }, + { + "id": "036d2ea4-5333-4131-9c07-6f59796388f3", + "color": "orange", + "label": "Share Everything", + "value": "SHARE_EVERYTHING", + "position": 2 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2547a346-f71c-4f28-a7fd-6181ac26ea98", + "type": "NUMBER", + "name": "throttleFailureCount", + "label": "Throttle Failure Count", + "description": "Throttle Failure Count", + "icon": "IconX", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": 0, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9b7e0a88-b635-4ce3-a401-0bd9429aec84", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6cc31dce-2ad0-443f-b4ba-54f2fdf11bb3", + "type": "SELECT", + "name": "contactAutoCreationPolicy", + "label": "Contact auto creation policy", + "description": "Automatically create People records when receiving or sending emails", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'SENT'", + "options": [ + { + "id": "631a81c3-ac67-48c7-879f-f09e17696387", + "color": "green", + "label": "Sent and Received", + "value": "SENT_AND_RECEIVED", + "position": 0 + }, + { + "id": "229a6e5b-3299-4b75-ada7-7512be411ba5", + "color": "blue", + "label": "Sent", + "value": "SENT", + "position": 1 + }, + { + "id": "9cc86698-3cc4-439e-b1db-c7f3fcb57039", + "color": "red", + "label": "None", + "value": "NONE", + "position": 2 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "01f7fe00-ee96-4c2e-a7e8-568cf750ee1e", + "type": "DATE_TIME", + "name": "syncedAt", + "label": "Last sync date", + "description": "Last sync date", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7ebfb686-ecb8-422b-b0e0-f657876f54ae", + "type": "SELECT", + "name": "syncStatus", + "label": "Sync status", + "description": "Sync status", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": [ + { + "id": "0401fe59-2ba4-4fb7-987f-f034cc320dc0", + "color": "yellow", + "label": "Ongoing", + "value": "ONGOING", + "position": 1 + }, + { + "id": "39e83b69-9c92-40dc-9d56-108c0cc503df", + "color": "blue", + "label": "Not Synced", + "value": "NOT_SYNCED", + "position": 2 + }, + { + "id": "9add7229-e120-492f-bb55-2331213d816e", + "color": "green", + "label": "Active", + "value": "ACTIVE", + "position": 3 + }, + { + "id": "2f387da6-5621-4a98-a393-2afbb6dca0cc", + "color": "red", + "label": "Failed Insufficient Permissions", + "value": "FAILED_INSUFFICIENT_PERMISSIONS", + "position": 4 + }, + { + "id": "e7ae1d24-0d7b-47bf-b9f5-37c58f339b19", + "color": "red", + "label": "Failed Unknown", + "value": "FAILED_UNKNOWN", + "position": 5 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6b5d2d3d-7f2c-473c-a480-72f0118a94fe", + "type": "SELECT", + "name": "syncStage", + "label": "Sync stage", + "description": "Sync stage", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'FULL_MESSAGE_LIST_FETCH_PENDING'", + "options": [ + { + "id": "a1249249-c073-4722-9959-9080e9f280e8", + "color": "blue", + "label": "Full messages list fetch pending", + "value": "FULL_MESSAGE_LIST_FETCH_PENDING", + "position": 0 + }, + { + "id": "4ce4e92c-a3e2-43ae-a1bf-c99fd6e58155", + "color": "blue", + "label": "Partial messages list fetch pending", + "value": "PARTIAL_MESSAGE_LIST_FETCH_PENDING", + "position": 1 + }, + { + "id": "2953ac47-534a-49e3-9c78-4577db774dd0", + "color": "orange", + "label": "Messages list fetch ongoing", + "value": "MESSAGE_LIST_FETCH_ONGOING", + "position": 2 + }, + { + "id": "7518c99f-9d8d-4a94-b747-e527e4579dbe", + "color": "blue", + "label": "Messages import pending", + "value": "MESSAGES_IMPORT_PENDING", + "position": 3 + }, + { + "id": "98ace89b-8b49-4e7a-8d75-d8a4b9648c51", + "color": "orange", + "label": "Messages import ongoing", + "value": "MESSAGES_IMPORT_ONGOING", + "position": 4 + }, + { + "id": "89909514-399d-41c7-97c0-260f087a3a56", + "color": "red", + "label": "Failed", + "value": "FAILED", + "position": 5 + } + ], + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "8cceadc4-de6b-4ecf-8324-82c6b4eec077", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "calendarChannel", + "namePlural": "calendarChannels", + "labelSingular": "Calendar Channel", + "labelPlural": "Calendar Channels", + "description": "Calendar Channels", + "icon": "IconCalendar", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "72762a31-80cf-45cf-bc6c-49609d4c940e", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "e342f766-c2f8-4ad5-8c91-c2a5bca9590f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_3465c79448bacd2f1268e5f6310", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "352f65a9-6b7e-4b12-a29a-d9b9800297d9", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "1b7e194e-4b39-40ab-a91c-eb06cf6d6ddc" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "3e73f944-1df9-4e7c-9ceb-13a6add8fe23", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "41fef682-fda8-488a-baa6-a58e38d5b71a" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE3" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e9781587-1419-4700-bc53-ed815b69d169", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1acd47f7-2ddf-434a-876a-683f602dcfbd", + "type": "SELECT", + "name": "syncStatus", + "label": "Sync status", + "description": "Sync status", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": [ + { + "id": "2a678cd8-8077-4fc0-bc7e-68dfd9d58668", + "color": "yellow", + "label": "Ongoing", + "value": "ONGOING", + "position": 1 + }, + { + "id": "142f711d-95ba-4707-9e83-209c50d93e85", + "color": "blue", + "label": "Not Synced", + "value": "NOT_SYNCED", + "position": 2 + }, + { + "id": "bac0648b-9b3b-4bce-b59c-bc5afedd2012", + "color": "green", + "label": "Active", + "value": "ACTIVE", + "position": 3 + }, + { + "id": "365a2ee9-e467-4cd9-b74f-045c2b832560", + "color": "red", + "label": "Failed Insufficient Permissions", + "value": "FAILED_INSUFFICIENT_PERMISSIONS", + "position": 4 + }, + { + "id": "a372985e-5404-45ce-9265-510552007505", + "color": "red", + "label": "Failed Unknown", + "value": "FAILED_UNKNOWN", + "position": 5 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6689ffa2-6e50-4a72-8574-3e91469976b6", + "type": "NUMBER", + "name": "throttleFailureCount", + "label": "Throttle Failure Count", + "description": "Throttle Failure Count", + "icon": "IconX", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": 0, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "72ea3e4e-79c9-40ff-b83d-e413d86d7781", + "type": "DATE_TIME", + "name": "syncStageStartedAt", + "label": "Sync stage started at", + "description": "Sync stage started at", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "72b79103-2228-4530-8481-68504790bfcd", + "type": "BOOLEAN", + "name": "isContactAutoCreationEnabled", + "label": "Is Contact Auto Creation Enabled", + "description": "Is Contact Auto Creation Enabled", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1b7e194e-4b39-40ab-a91c-eb06cf6d6ddc", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "674a0bad-ff98-40d2-a711-47a92d7b3a46", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6c212aec-f637-4abc-84e4-9328ec4f454a", + "type": "BOOLEAN", + "name": "isSyncEnabled", + "label": "Is Sync Enabled", + "description": "Is Sync Enabled", + "icon": "IconRefresh", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2d3cece4-46f8-4b2d-bff7-381591429995", + "type": "SELECT", + "name": "visibility", + "label": "Visibility", + "description": "Visibility", + "icon": "IconEyeglass", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'SHARE_EVERYTHING'", + "options": [ + { + "id": "0fd14471-aa1c-4637-91a6-0917614dfe35", + "color": "green", + "label": "Metadata", + "value": "METADATA", + "position": 0 + }, + { + "id": "b95b0238-fdb2-4b63-8b3f-908a156cb6ee", + "color": "orange", + "label": "Share Everything", + "value": "SHARE_EVERYTHING", + "position": 1 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c845dede-582d-47c8-a631-34862f5e9c87", + "type": "DATE_TIME", + "name": "syncedAt", + "label": "Last sync date", + "description": "Last sync date", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "75fba4a8-ddf9-46e3-a6f0-4da74d180dc4", + "type": "TEXT", + "name": "syncCursor", + "label": "Sync Cursor", + "description": "Sync Cursor. Used for syncing events from the calendar provider", + "icon": "IconReload", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fe2812bf-cf28-4ab1-9c52-1e8598224947", + "type": "RELATION", + "name": "connectedAccount", + "label": "Connected Account", + "description": "Connected Account", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "38ead7c5-b8c5-40a1-9db4-088a93b3c798", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "8cceadc4-de6b-4ecf-8324-82c6b4eec077", + "nameSingular": "calendarChannel", + "namePlural": "calendarChannels" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "fe2812bf-cf28-4ab1-9c52-1e8598224947", + "name": "connectedAccount" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d828bda6-68e2-47f0-b0aa-b810b1f33981", + "nameSingular": "connectedAccount", + "namePlural": "connectedAccounts" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "44aa8b95-c247-4507-bf76-17ff3449b4cb", + "name": "calendarChannels" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7ec1ea5f-c98f-4569-91ef-8797294cf183", + "type": "RELATION", + "name": "calendarChannelEventAssociations", + "label": "Calendar Channel Event Associations", + "description": "Calendar Channel Event Associations", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c040d959-808f-4b7d-8844-ac87a45c0b04", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "8cceadc4-de6b-4ecf-8324-82c6b4eec077", + "nameSingular": "calendarChannel", + "namePlural": "calendarChannels" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7ec1ea5f-c98f-4569-91ef-8797294cf183", + "name": "calendarChannelEventAssociations" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "149f1a0d-f528-48a3-a3f8-0203926d07f5", + "nameSingular": "calendarChannelEventAssociation", + "namePlural": "calendarChannelEventAssociations" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "5d68a9e6-9e85-4ae4-8601-43e685dcf90b", + "name": "calendarChannel" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "41fef682-fda8-488a-baa6-a58e38d5b71a", + "type": "UUID", + "name": "connectedAccountId", + "label": "Connected Account id (foreign key)", + "description": "Connected Account id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "eeb8bb8b-85b7-46f8-a434-ab35704f1afa", + "type": "SELECT", + "name": "contactAutoCreationPolicy", + "label": "Contact auto creation policy", + "description": "Automatically create records for people you participated with in an event.", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'AS_PARTICIPANT_AND_ORGANIZER'", + "options": [ + { + "id": "6632b738-012f-4841-ac54-7bc6593cd551", + "color": "green", + "label": "As Participant and Organizer", + "value": "AS_PARTICIPANT_AND_ORGANIZER", + "position": 0 + }, + { + "id": "c0b6918d-925c-4f80-a900-c374833e2c40", + "color": "orange", + "label": "As Participant", + "value": "AS_PARTICIPANT", + "position": 1 + }, + { + "id": "639ded55-04c3-4068-b340-ad8eb6137cee", + "color": "blue", + "label": "As Organizer", + "value": "AS_ORGANIZER", + "position": 2 + }, + { + "id": "2493394c-ca24-4600-8caf-fff75eae583a", + "color": "red", + "label": "None", + "value": "NONE", + "position": 3 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5b646ee9-aa3d-41ff-b786-3f9cfad7ee12", + "type": "SELECT", + "name": "syncStage", + "label": "Sync stage", + "description": "Sync stage", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'FULL_CALENDAR_EVENT_LIST_FETCH_PENDING'", + "options": [ + { + "id": "9ddbe48c-b6b5-4582-8079-b97ab459dc05", + "color": "blue", + "label": "Full calendar event list fetch pending", + "value": "FULL_CALENDAR_EVENT_LIST_FETCH_PENDING", + "position": 0 + }, + { + "id": "b632a8f0-ab5d-4c39-954e-4b0a5b7bce35", + "color": "blue", + "label": "Partial calendar event list fetch pending", + "value": "PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING", + "position": 1 + }, + { + "id": "9591ff35-9a14-4f04-b25c-482d84f57e6a", + "color": "orange", + "label": "Calendar event list fetch ongoing", + "value": "CALENDAR_EVENT_LIST_FETCH_ONGOING", + "position": 2 + }, + { + "id": "f64e01f4-de1e-4b2c-99ee-31f649b65dcd", + "color": "blue", + "label": "Calendar events import pending", + "value": "CALENDAR_EVENTS_IMPORT_PENDING", + "position": 3 + }, + { + "id": "c56a146f-88fe-4676-a69f-5b22e75c8376", + "color": "orange", + "label": "Calendar events import ongoing", + "value": "CALENDAR_EVENTS_IMPORT_ONGOING", + "position": 4 + }, + { + "id": "084344f8-fa0c-45a2-ac45-0526b9afc004", + "color": "red", + "label": "Failed", + "value": "FAILED", + "position": 5 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "72762a31-80cf-45cf-bc6c-49609d4c940e", + "type": "TEXT", + "name": "handle", + "label": "Handle", + "description": "Handle", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6a3003ce-1041-451b-8e66-f69d9b0dd2ab", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "8ae98b12-2ef6-4c20-adc6-240857dd7343", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "blocklist", + "namePlural": "blocklists", + "labelSingular": "Blocklist", + "labelPlural": "Blocklists", + "description": "Blocklist", + "icon": "IconForbid2", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "50f7918f-c677-4e34-baae-3012e3b6d5a8", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "7a223e05-f418-496a-b258-1df57a7c4acf", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_76a190ab8a6f439791358d63d60", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8e1db228-ee1a-455f-a00d-b32d69aeb309", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "fd3f4444-1951-4869-a25c-f6198d9d37a2" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjY=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c1ba16b8-45a1-48b3-a945-eb9390421d4c", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "50f7918f-c677-4e34-baae-3012e3b6d5a8", + "type": "TEXT", + "name": "handle", + "label": "Handle", + "description": "Handle", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d3588b81-9a6d-49c9-8c1f-66006c5bbf40", + "type": "UUID", + "name": "workspaceMemberId", + "label": "WorkspaceMember id (foreign key)", + "description": "WorkspaceMember id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c7856143-ebb9-425b-8e13-1be7b63387a6", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fd3f4444-1951-4869-a25c-f6198d9d37a2", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fe6a4945-0e11-43b8-b052-a8b433f341ea", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "84b616f8-2db3-472d-93dd-a8e89b9db810", + "type": "RELATION", + "name": "workspaceMember", + "label": "WorkspaceMember", + "description": "WorkspaceMember", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1185289f-ef64-4b23-a7ef-c16303bea50f", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "8ae98b12-2ef6-4c20-adc6-240857dd7343", + "nameSingular": "blocklist", + "namePlural": "blocklists" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "84b616f8-2db3-472d-93dd-a8e89b9db810", + "name": "workspaceMember" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "b391761f-fedd-4a1d-acd1-497c21a615ba", + "name": "blocklist" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "88f29168-a15b-4330-89a1-680581a2e86b", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "viewFilter", + "namePlural": "viewFilters", + "labelSingular": "View Filter", + "labelPlural": "View Filters", + "description": "(System) View Filters", + "icon": "IconFilterBolt", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "f036575d-bd0d-43da-bbcc-545384a75b64", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d9778e2a-e57a-4117-83f1-5da2c78036e2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_5653b106ee9a9e3d5c1c790419a", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "efb10056-7029-43c4-a074-4504eca5c775", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "cb05eb84-b336-494a-b3d9-eb9cf64dba10" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjk=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "31efb6ce-3571-4786-99bf-40b9f22e4678", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "70f8731d-006a-4a1e-9391-e5f180c63d24", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8180c369-8f83-4244-9ae5-ebed178d3601", + "type": "TEXT", + "name": "operand", + "label": "Operand", + "description": "View Filter operand", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'Contains'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "961fcf39-4973-49e0-8686-715d1c6f8f7f", + "type": "TEXT", + "name": "value", + "label": "Value", + "description": "View Filter value", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f036575d-bd0d-43da-bbcc-545384a75b64", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "cb05eb84-b336-494a-b3d9-eb9cf64dba10", + "type": "UUID", + "name": "viewId", + "label": "View id (foreign key)", + "description": "View Filter related view id foreign key", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "854ce53f-9073-4cc2-b3f3-2f8467afccc1", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "356029e0-d8e9-4d36-8921-936265c27d90", + "type": "TEXT", + "name": "displayValue", + "label": "Display Value", + "description": "View Filter Display Value", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2004362c-52c5-4a3f-99e7-ddef7eeaf9af", + "type": "UUID", + "name": "fieldMetadataId", + "label": "Field Metadata Id", + "description": "View Filter target field", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d3db3f5c-49f7-4008-b8a4-cec85ac3505e", + "type": "RELATION", + "name": "view", + "label": "View", + "description": "View Filter related view", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "678c1f91-1825-4eb5-9960-bea05bd2ca87", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "88f29168-a15b-4330-89a1-680581a2e86b", + "nameSingular": "viewFilter", + "namePlural": "viewFilters" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "d3db3f5c-49f7-4008-b8a4-cec85ac3505e", + "name": "view" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "312163f0-9b2f-4c59-8473-9eb1906b8f41", + "name": "viewFilters" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "823e8b9d-1947-48f9-9f43-116a2cbceba3", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "webhook", + "namePlural": "webhooks", + "labelSingular": "Webhook", + "labelPlural": "Webhooks", + "description": "A webhook", + "icon": "IconRobot", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "9f7e51c5-d296-4a59-ad04-82c92b5b26fd", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjY=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "df862ab4-9901-4a1e-87ae-231f783f9647", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "af92d573-4e0e-409c-be39-d6756f5ac540", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e1890810-5257-4bf8-9f56-e1c801424462", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fb283e72-3e58-4509-aa8a-f5e364bc6862", + "type": "TEXT", + "name": "operation", + "label": "Operation", + "description": "Webhook operation", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "defedbc6-cf37-4bed-9594-0eba58fb3b0f", + "type": "TEXT", + "name": "description", + "label": "Description", + "description": null, + "icon": "IconInfo", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4b835f28-6706-49df-8399-5ba87cb02d0f", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9f7e51c5-d296-4a59-ad04-82c92b5b26fd", + "type": "TEXT", + "name": "targetUrl", + "label": "Target Url", + "description": "Webhook target url", + "icon": "IconLink", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "workflow", + "namePlural": "workflows", + "labelSingular": "Workflow", + "labelPlural": "Workflows", + "description": "A workflow", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "aba08838-a25b-4e59-b634-c3bbd4e38c47", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ef5457d1-77ad-4c68-87aa-31a108160922", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "05d6227a-bc2d-4e08-8390-cfcec921e4a4", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the contact", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "fa6c3b55-5fe4-4406-89a8-9474ca539769", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "05d6227a-bc2d-4e08-8390-cfcec921e4a4", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "71e4555a-9fcc-41bd-a8f0-248d1605567e", + "name": "workflow" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "561bdfd7-91b4-426c-a7ff-30c5469e7d54", + "type": "MULTI_SELECT", + "name": "statuses", + "label": "Statuses", + "description": "The current statuses of the workflow versions", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": [ + { + "color": "yellow", + "label": "Draft", + "value": "DRAFT", + "position": 0 + }, + { + "color": "green", + "label": "Active", + "value": "ACTIVE", + "position": 1 + }, + { + "color": "grey", + "label": "Deactivated", + "value": "DEACTIVATED", + "position": 2 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1f691bcc-0b90-452c-b922-54a6f39e9e37", + "type": "RELATION", + "name": "runs", + "label": "Runs", + "description": "Workflow runs linked to the workflow.", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "e73cc998-6f8b-4968-a56f-504738c4aa71", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1f691bcc-0b90-452c-b922-54a6f39e9e37", + "name": "runs" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "45b7e1cf-792c-45fa-8d6a-0d5e67e1fa42", + "nameSingular": "workflowRun", + "namePlural": "workflowRuns" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1e1810ae-6ebf-49a1-b7e4-2b217c3a5a13", + "name": "workflow" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "330683fa-283d-4e2a-b351-88ded0749f48", + "type": "TEXT", + "name": "lastPublishedVersionId", + "label": "Last published Version Id", + "description": "The workflow last published version id", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "427e9c03-19df-4f8a-89d7-af0bcdbe8a38", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Workflow record position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0cf97b57-1c48-48a3-bccb-c1e4557e1de0", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bd955f7c-c827-477e-9aaa-f46fc9cc09a1", + "type": "RELATION", + "name": "eventListeners", + "label": "Event Listeners", + "description": "Workflow event listeners linked to the workflow.", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "dc06be4a-4d8f-45ed-b331-4a353a080894", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "bd955f7c-c827-477e-9aaa-f46fc9cc09a1", + "name": "eventListeners" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "65cce76e-0f4c-4de1-a68a-6cadce4d000e", + "nameSingular": "workflowEventListener", + "namePlural": "workflowEventListeners" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "de496463-da61-4d9d-8d14-730a48c66f45", + "name": "workflow" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "aba08838-a25b-4e59-b634-c3bbd4e38c47", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "The workflow name", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "46f260da-54b5-4739-90c3-b6aab057e442", + "type": "RELATION", + "name": "versions", + "label": "Versions", + "description": "Workflow versions linked to the workflow.", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "5e09f62f-3f3a-4b9a-a2f8-434e7ac69969", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "46f260da-54b5-4739-90c3-b6aab057e442", + "name": "versions" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "e5915d30-4425-4c4c-a9c4-1b4bff20c469", + "nameSingular": "workflowVersion", + "namePlural": "workflowVersions" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "69e06184-8fa4-4df5-95dc-7563936ed95f", + "name": "workflow" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "97f377f3-7d31-486d-8a59-fad17484efcf", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "de537cba-0660-4a43-872a-8a5c867f3638", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants", + "labelSingular": "Message Participant", + "labelPlural": "Message Participants", + "description": "Message Participants", + "icon": "IconUserCircle", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "78a29a18-74ca-4d90-81bc-563916d82017", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "b4a9798c-a77f-443b-9a68-4f105affbed2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_6d9700e5ae2ab8c294d614e72f6", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "89bb196f-6678-4b21-a527-4753a6830cff", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_8d0144e4074d86d0cb7094f40c2", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "aca3173e-3128-402e-9c4c-7c0f126b7626", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "6b5429cd-116b-4470-95e6-bb4a15269715" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f588e813-71dc-4cf9-96d9-d7443bb11cbf", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_8c4f617db0813d41aef587e49ea", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "665ff0a5-e5b6-4fc3-8a04-670078815cf6", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "90325846-5bd5-4867-ad09-a2dd66b569b3" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEy" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "76780c2e-a012-4ff7-8c5f-1dee290dd458", + "type": "SELECT", + "name": "role", + "label": "Role", + "description": "Role", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'from'", + "options": [ + { + "id": "37c322cd-7db3-4bcd-9b57-00c6e99a2e95", + "color": "green", + "label": "From", + "value": "from", + "position": 0 + }, + { + "id": "d1bcffe7-9417-4580-98ed-6f3202713057", + "color": "blue", + "label": "To", + "value": "to", + "position": 1 + }, + { + "id": "6e4bb3b8-7ba1-48bb-9a4d-a5027f7e1283", + "color": "orange", + "label": "Cc", + "value": "cc", + "position": 2 + }, + { + "id": "51527ffd-620b-4b53-859c-93714cddc508", + "color": "red", + "label": "Bcc", + "value": "bcc", + "position": 3 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "90325846-5bd5-4867-ad09-a2dd66b569b3", + "type": "UUID", + "name": "workspaceMemberId", + "label": "Workspace Member id (foreign key)", + "description": "Workspace member id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6f328351-fb72-43a2-bf91-ef7c98972589", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "Person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e497dbce-a48a-4ea8-82c9-5f8a38ca26dc", + "type": "TEXT", + "name": "displayName", + "label": "Display Name", + "description": "Display Name", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "edc0cbe6-9c05-48a7-8cf5-6ff782ad055d", + "type": "RELATION", + "name": "workspaceMember", + "label": "Workspace Member", + "description": "Workspace member", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1c4db1ed-209f-4274-bf95-004e0cb74404", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "edc0cbe6-9c05-48a7-8cf5-6ff782ad055d", + "name": "workspaceMember" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "07667783-a2b1-4eb5-8e3b-86ec786993fa", + "name": "messageParticipants" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0b579046-7846-4d34-a22a-21c64991e776", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "78a29a18-74ca-4d90-81bc-563916d82017", + "type": "TEXT", + "name": "handle", + "label": "Handle", + "description": "Handle", + "icon": "IconAt", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6b5429cd-116b-4470-95e6-bb4a15269715", + "type": "UUID", + "name": "messageId", + "label": "Message id (foreign key)", + "description": "Message id foreign key", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8afc59b9-a712-4158-9103-910312d481e5", + "type": "RELATION", + "name": "message", + "label": "Message", + "description": "Message", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "2433b7ad-ce4f-4ea3-a572-910b6e2ce66f", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8afc59b9-a712-4158-9103-910312d481e5", + "name": "message" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "nameSingular": "message", + "namePlural": "messages" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8791bc22-6d54-4267-9b2f-758946e99bbf", + "name": "messageParticipants" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2bb51505-1b04-43c7-8e81-74c33911f4f8", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ef9604ad-a71d-444c-936f-bbd8e4a18997", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8507c38d-6f90-4b15-afa1-19b749774092", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "Person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "89f811eb-6211-4b53-bc32-1acf1241a5dd", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8507c38d-6f90-4b15-afa1-19b749774092", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "c502a91b-fde5-426b-aa27-a856ad28fc71", + "name": "messageParticipants" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3f2d6322-f612-4c7c-8329-42de4a988867", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "note", + "namePlural": "notes", + "labelSingular": "Note", + "labelPlural": "Notes", + "description": "A note", + "icon": "IconNotes", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "c9c0b35e-a84c-449a-b6ca-69efc8f71a52", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "14bcdb46-70ad-4947-92d6-2b981a01d8dc", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Note record position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "20646869-ca0e-49a0-9344-fe9272d64f6d", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Note attachments", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "fc78e196-db67-41d8-8d95-b1da605f98d5", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "20646869-ca0e-49a0-9344-fe9272d64f6d", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "052cd77c-3540-4c77-a571-e0144b944351", + "name": "note" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "45d38878-cca8-4c8c-b7e4-539adf09c5b1", + "type": "RELATION", + "name": "noteTargets", + "label": "Relations", + "description": "Note targets", + "icon": "IconArrowUpRight", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7be32a73-9b29-422b-b5aa-1933cf5ad254", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "45d38878-cca8-4c8c-b7e4-539adf09c5b1", + "name": "noteTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "5ea37b6c-76b5-4cc3-981e-fefebaa91607", + "name": "note" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "20718057-bac8-4dc8-b755-d547d461ddf2", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "94bd02c5-733a-466f-8525-e4b53a221533", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f86335f9-07e3-4108-a2a2-cafa6820e85e", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the note", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "8206c6dd-98d8-4526-9836-bad9f2b065bb", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f86335f9-07e3-4108-a2a2-cafa6820e85e", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "53790b28-fd71-4f0f-994d-72e9cb8b018b", + "name": "note" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2f11215d-436b-4cf6-ab77-7bb582af633c", + "type": "RICH_TEXT", + "name": "body", + "label": "Body", + "description": "Note body", + "icon": "IconFilePencil", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c9c0b35e-a84c-449a-b6ca-69efc8f71a52", + "type": "TEXT", + "name": "title", + "label": "Title", + "description": "Note title", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fb8df7bd-0b5b-42cf-9f62-1e0b05a06286", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "325aafc9-525a-457e-9f67-15a8994bd6b8", + "type": "RELATION", + "name": "timelineActivities", + "label": "Timeline Activities", + "description": "Timeline Activities linked to the note.", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "49ad3d9a-01a3-4c82-b6bc-a8d21398e51a", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "6a09bc08-33ae-4321-868a-30064279097f", + "nameSingular": "note", + "namePlural": "notes" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "325aafc9-525a-457e-9f67-15a8994bd6b8", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8376c21d-fd88-4006-b8d7-40d1f27692ac", + "name": "note" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9c8f3988-16e1-40be-abc1-4ab3e03822f6", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e94bf127-bdef-4d9e-a703-5202d624bc5b", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "task", + "namePlural": "tasks", + "labelSingular": "Task", + "labelPlural": "Tasks", + "description": "A task", + "icon": "IconCheckbox", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "5691876e-1463-4117-a7ef-2d0b089fa1ff", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "0d1fcd80-02e2-4bbe-80c5-bc0c09f9241d", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_ee5298b25512b38b29390e084f7", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "4afbc310-7760-4e36-9796-7590c75c902b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "559b6272-f96c-4f39-883e-a01b4741b2ed" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "39a9a119-fbfe-4a02-9b32-cb805fa52791", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "e99d0dcd-4e33-46bd-9bd7-d80a957003f8" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE1" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "46331eb9-1722-4cfa-8d2e-fdbc87e6d879", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e3e5b381-059f-49f9-acc6-38d92ca70bd7", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "61700dc0-7a07-4421-a85f-50d1b40ef66e", + "type": "DATE_TIME", + "name": "dueAt", + "label": "Due Date", + "description": "Task due date", + "icon": "IconCalendarEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0a0a3b30-46a9-476b-ab0c-a4b9a91a0456", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d6ad1859-b68e-4363-8bba-7db17b61fca5", + "type": "SELECT", + "name": "status", + "label": "Status", + "description": "Task status", + "icon": "IconCheck", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'TODO'", + "options": [ + { + "id": "af1e2cda-b54d-4014-b1ec-dc8237098028", + "color": "sky", + "label": "To do", + "value": "TODO", + "position": 0 + }, + { + "id": "e2c18902-9ba6-4d35-9036-22d8c04985fd", + "color": "purple", + "label": "In progress", + "value": "IN_PROGESS", + "position": 1 + }, + { + "id": "b0f06a1d-7821-46ae-9eb9-1a13201d0a2f", + "color": "green", + "label": "Done", + "value": "DONE", + "position": 1 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e99d0dcd-4e33-46bd-9bd7-d80a957003f8", + "type": "UUID", + "name": "assigneeId", + "label": "Assignee id (foreign key)", + "description": "Task assignee id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1bb59b31-1c7d-4697-98d5-9f984cc9ea9c", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Task attachments", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "4a95e7b4-8bdc-49ca-b6d3-ae3d86a6e040", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1bb59b31-1c7d-4697-98d5-9f984cc9ea9c", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "9907dc47-5e02-4966-91f8-5df22fafaaa4", + "name": "task" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0400e2ae-05e4-46f5-b7e1-3fd8e7c15731", + "type": "RELATION", + "name": "assignee", + "label": "Assignee", + "description": "Task assignee", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "970f0f7f-a30d-4a6f-b023-c3bc5a1b412c", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "0400e2ae-05e4-46f5-b7e1-3fd8e7c15731", + "name": "assignee" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "5233906b-4d12-4e40-9081-436ff5c6cefe", + "name": "assignedTasks" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9fd18cc3-10c9-4017-91a6-ee42e1955e29", + "type": "RICH_TEXT", + "name": "body", + "label": "Body", + "description": "Task body", + "icon": "IconFilePencil", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "77726d5f-43e6-4011-952a-d9585dc14597", + "type": "RELATION", + "name": "timelineActivities", + "label": "Timeline Activities", + "description": "Timeline Activities linked to the task.", + "icon": "IconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "e940e46a-ab1a-4e0c-82a4-1af978adcc2c", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "77726d5f-43e6-4011-952a-d9585dc14597", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "49b48088-8d98-4d8d-a9a8-c4f8f462aa72", + "name": "task" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0111a4d1-f387-4f21-bc99-94b80139ff7f", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the task", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "14f171b6-2a7e-4ae3-adc6-1721e91b791f", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "0111a4d1-f387-4f21-bc99-94b80139ff7f", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "60db3d95-7824-4335-9a49-eec97888c068", + "name": "task" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5691876e-1463-4117-a7ef-2d0b089fa1ff", + "type": "TEXT", + "name": "title", + "label": "Title", + "description": "Task title", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ceef7231-1933-4c8b-a2d1-a0e9749cd69a", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Task record position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "74bf0e2d-9fc5-4609-b180-a50e18f5f9ca", + "type": "RELATION", + "name": "taskTargets", + "label": "Relations", + "description": "Task targets", + "icon": "IconArrowUpRight", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "2cb467d4-4e91-459c-a145-5f276f6186e1", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "681f89d7-0581-42b0-b97d-870e3b2a8359", + "nameSingular": "task", + "namePlural": "tasks" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "74bf0e2d-9fc5-4609-b180-a50e18f5f9ca", + "name": "taskTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "61963eea-7040-4cba-be90-031464ad1e69", + "name": "task" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bec117bf-86b7-4531-b192-d8917777a9c6", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "559b6272-f96c-4f39-883e-a01b4741b2ed", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "message", + "namePlural": "messages", + "labelSingular": "Message", + "labelPlural": "Messages", + "description": "Message", + "icon": "IconMessage", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "8624c6f2-87b6-4bb5-b90a-4fd63230c2fd", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "ccd3d99a-7422-40f1-9eb7-39fd739613bf", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_78fa73d661d632619e17de211e6", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "dbb0a4c8-7607-474c-a360-56ee92260e7c", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "373b653f-399a-4fe2-940e-b10efe9973b1" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "373b653f-399a-4fe2-940e-b10efe9973b1", + "type": "UUID", + "name": "messageThreadId", + "label": "Message Thread Id id (foreign key)", + "description": "Message Thread Id id foreign key", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "34f11a4b-3fab-4402-a1dd-e5ba6eb57a40", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "129b9862-08af-4424-b313-2b38dae6c003", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8791bc22-6d54-4267-9b2f-758946e99bbf", + "type": "RELATION", + "name": "messageParticipants", + "label": "Message Participants", + "description": "Message Participants", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "2433b7ad-ce4f-4ea3-a572-910b6e2ce66f", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "nameSingular": "message", + "namePlural": "messages" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8791bc22-6d54-4267-9b2f-758946e99bbf", + "name": "messageParticipants" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "6edf5dd8-ee31-42ec-80f9-728b01c50ff4", + "nameSingular": "messageParticipant", + "namePlural": "messageParticipants" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8afc59b9-a712-4158-9103-910312d481e5", + "name": "message" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c2396aae-ce94-4dc2-b0b1-45c595bbb067", + "type": "TEXT", + "name": "text", + "label": "Text", + "description": "Text", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "82974b3e-72cf-47d4-9b1d-52008c370b14", + "type": "DATE_TIME", + "name": "receivedAt", + "label": "Received At", + "description": "The date the message was received", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "efdfbd70-a365-4e96-9fb0-095eb91e061a", + "type": "RELATION", + "name": "messageThread", + "label": "Message Thread Id", + "description": "Message Thread Id", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "fcbbe4ce-01e3-4332-8424-16709c40d819", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "nameSingular": "message", + "namePlural": "messages" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "efdfbd70-a365-4e96-9fb0-095eb91e061a", + "name": "messageThread" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f98ea433-1b70-46d3-aefa-43eb369925d2", + "nameSingular": "messageThread", + "namePlural": "messageThreads" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "84b1f586-7867-4cd9-b793-4826d4d99cf5", + "name": "messages" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b5d8a70e-5e7c-4ec1-bcdd-d29ce5ce31bf", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a1050506-9f21-47d5-be1f-2ae25b974c1c", + "type": "TEXT", + "name": "headerMessageId", + "label": "Header message Id", + "description": "Message id from the message header", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9a32cb5a-af34-4c27-9e66-a741674b0701", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8624c6f2-87b6-4bb5-b90a-4fd63230c2fd", + "type": "TEXT", + "name": "subject", + "label": "Subject", + "description": "Subject", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1be8e5e6-167b-49cd-9a37-224969040f28", + "type": "RELATION", + "name": "messageChannelMessageAssociations", + "label": "Message Channel Association", + "description": "Messages from the channel.", + "icon": "IconMessage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "f7d4519f-c655-4cb4-a371-c7bf44451fd4", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "nameSingular": "message", + "namePlural": "messages" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1be8e5e6-167b-49cd-9a37-224969040f28", + "name": "messageChannelMessageAssociations" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "631882fd-28e8-4a87-8ceb-f8217006a620", + "nameSingular": "messageChannelMessageAssociation", + "namePlural": "messageChannelMessageAssociations" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "310ff925-20c8-4525-9343-9c243efeb8df", + "name": "message" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "65cce76e-0f4c-4de1-a68a-6cadce4d000e", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "workflowEventListener", + "namePlural": "workflowEventListeners", + "labelSingular": "WorkflowEventListener", + "labelPlural": "WorkflowEventListeners", + "description": "A workflow event listener", + "icon": null, + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "bc61037f-5ab1-48c4-9565-5b743b45bc1e", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "74da0929-df7b-429d-93dc-5658aeb95a87", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_9d6a1fb98ccde16ede8c5949d40", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "b6983178-9322-4010-aa89-61d2860af688", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "1e7c94c5-c013-4924-aa6c-cb67d45b33a3" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjY=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8cf7ceee-898f-4835-b966-eaf2c1429798", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6f46ad82-ecec-472d-9a1b-dbabbda3c339", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "18c59e83-4a1a-406b-b1cc-8b7706c9a661", + "type": "UUID", + "name": "workflowId", + "label": "Workflow id (foreign key)", + "description": "WorkflowEventListener workflow id foreign key", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1e7c94c5-c013-4924-aa6c-cb67d45b33a3", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "de496463-da61-4d9d-8d14-730a48c66f45", + "type": "RELATION", + "name": "workflow", + "label": "Workflow", + "description": "WorkflowEventListener workflow", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "dc06be4a-4d8f-45ed-b331-4a353a080894", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "65cce76e-0f4c-4de1-a68a-6cadce4d000e", + "nameSingular": "workflowEventListener", + "namePlural": "workflowEventListeners" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "de496463-da61-4d9d-8d14-730a48c66f45", + "name": "workflow" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "bd955f7c-c827-477e-9aaa-f46fc9cc09a1", + "name": "eventListeners" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bc61037f-5ab1-48c4-9565-5b743b45bc1e", + "type": "TEXT", + "name": "eventName", + "label": "Name", + "description": "The workflow event listener name", + "icon": "IconPhoneCheck", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3b08bd66-e033-4c78-befd-5a7e19be4ebe", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "631882fd-28e8-4a87-8ceb-f8217006a620", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "messageChannelMessageAssociation", + "namePlural": "messageChannelMessageAssociations", + "labelSingular": "Message Channel Message Association", + "labelPlural": "Message Channel Message Associations", + "description": "Message Synced with a Message Channel", + "icon": "IconMessage", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "87d926ac-0aab-4ea6-b41f-7ab64cbdd65b", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f6c85d6e-6efb-43bb-bb4e-47a3b98856f9", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_671dd9e01a80d1e4c89fc166c3b", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "ce685d06-92ee-4612-9cb1-dbd6bb9fa713", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "c44b95ee-3e23-48c0-864f-eb693a46719d" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "48bde8b2-0629-4028-bb59-cc1634902000", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "c6659e96-78c1-4a71-b927-5d5c0d852248" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d1d32a0f-692a-4dce-8abf-cc11c26fbc07", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_63953e5f88351922043480b8801", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "4490d296-7ef7-46b5-b76a-20797916881b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "c44b95ee-3e23-48c0-864f-eb693a46719d" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "b029d0fa-593e-4000-a708-b7263f338e77", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "bdb6b50a-a084-4717-83d4-cdf7d36a056e" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEw" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5ae16290-e931-4be5-b155-343044540f60", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c6659e96-78c1-4a71-b927-5d5c0d852248", + "type": "UUID", + "name": "messageId", + "label": "Message Id id (foreign key)", + "description": "Message Id id foreign key", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bdb6b50a-a084-4717-83d4-cdf7d36a056e", + "type": "UUID", + "name": "messageChannelId", + "label": "Message Channel Id id (foreign key)", + "description": "Message Channel Id id foreign key", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "310ff925-20c8-4525-9343-9c243efeb8df", + "type": "RELATION", + "name": "message", + "label": "Message Id", + "description": "Message Id", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "f7d4519f-c655-4cb4-a371-c7bf44451fd4", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "631882fd-28e8-4a87-8ceb-f8217006a620", + "nameSingular": "messageChannelMessageAssociation", + "namePlural": "messageChannelMessageAssociations" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "310ff925-20c8-4525-9343-9c243efeb8df", + "name": "message" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "673b8cb8-44c1-4c20-9834-7c35d44fd180", + "nameSingular": "message", + "namePlural": "messages" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1be8e5e6-167b-49cd-9a37-224969040f28", + "name": "messageChannelMessageAssociations" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "490611ce-ddb6-4dee-a2c1-159bdf0ab5a9", + "type": "TEXT", + "name": "messageThreadExternalId", + "label": "Thread External Id", + "description": "Thread id from the messaging provider", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "aac54567-521e-4fe8-a0bc-116f2aabf592", + "type": "RELATION", + "name": "messageChannel", + "label": "Message Channel Id", + "description": "Message Channel Id", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "612d689f-eb0d-4e6b-b72e-82acb8ae76e2", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "631882fd-28e8-4a87-8ceb-f8217006a620", + "nameSingular": "messageChannelMessageAssociation", + "namePlural": "messageChannelMessageAssociations" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "aac54567-521e-4fe8-a0bc-116f2aabf592", + "name": "messageChannel" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "92b529f1-b82b-4352-a0d5-18f32f8e47ab", + "nameSingular": "messageChannel", + "namePlural": "messageChannels" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "90648e68-2f7c-4d0c-ab8a-79e6eec769ee", + "name": "messageChannelMessageAssociations" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c44b95ee-3e23-48c0-864f-eb693a46719d", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "316e028e-acbf-4d5e-a7ad-28ba9ae41938", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "87d926ac-0aab-4ea6-b41f-7ab64cbdd65b", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "139c4436-2b14-41f0-a29f-64da3c7f46cf", + "type": "TEXT", + "name": "messageExternalId", + "label": "Message External Id", + "description": "Message id from the messaging provider", + "icon": "IconHash", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "16d3b512-51f7-451a-8531-666b7ad80bcb", + "type": "SELECT", + "name": "direction", + "label": "Direction", + "description": "Message Direction", + "icon": "IconDirection", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'INCOMING'", + "options": [ + { + "id": "de903213-75ec-464b-b4ea-29b636638475", + "color": "green", + "label": "Incoming", + "value": "INCOMING", + "position": 0 + }, + { + "id": "2d0e7921-4cb4-41ac-87a6-263957a0f037", + "color": "blue", + "label": "Outgoing", + "value": "OUTGOING", + "position": 1 + } + ], + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "activity", + "namePlural": "activities", + "labelSingular": "Activity", + "labelPlural": "Activities", + "description": "An activity", + "icon": "IconCheckbox", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "e865ea21-e17e-47e3-8d14-6b3e4aa8b6a9", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f5874304-d8ee-4cba-93dc-4f8f3d4826c7", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_29e3cc1255fe5ae28e61841001c", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "c858a5d9-2120-460a-9006-827014ce7de5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "f64fd8c3-ce7d-462f-b12d-1437434cd614" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "26f6e296-e919-4239-985e-d9c26ada07f1", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_4fd6a4b57c6237b197275440102", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "2914f54a-0fd1-4467-b8b7-b3564ea2eeb5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "a5636fee-d403-4824-b2fd-bfdc1144927c" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "5e838e0c-73c2-4e9c-abed-b4e117ec5ef0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "9d00fec5-f501-4c12-831d-78936dca9c4f" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE2" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ccd81283-2fcb-445c-af6f-c2ac27a42824", + "type": "RELATION", + "name": "comments", + "label": "Comments", + "description": "Activity comments", + "icon": "IconComment", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "ed3d480f-cc49-4fee-ae36-94e6edd64940", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "ccd81283-2fcb-445c-af6f-c2ac27a42824", + "name": "comments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "eda936a5-97b9-4b9f-986a-d8e19e8ea882", + "nameSingular": "comment", + "namePlural": "comments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "04e07930-a3f1-4f57-9265-7364f18f2651", + "name": "activity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "eac47cad-333f-40d9-bb57-0f9239520875", + "type": "DATE_TIME", + "name": "reminderAt", + "label": "Reminder Date", + "description": "Activity reminder date", + "icon": "IconCalendarEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9c2c3c63-b1ef-4065-b314-93d812db936e", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e865ea21-e17e-47e3-8d14-6b3e4aa8b6a9", + "type": "TEXT", + "name": "title", + "label": "Title", + "description": "Activity title", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d2f57ffe-0cbd-40a0-b83a-1939aaeac560", + "type": "RELATION", + "name": "assignee", + "label": "Assignee", + "description": "Activity assignee", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1756bbce-2212-4ec2-b1e3-5053810abdb1", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "d2f57ffe-0cbd-40a0-b83a-1939aaeac560", + "name": "assignee" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "be802643-0a4b-42d1-a87c-606fce69f9f7", + "name": "assignedActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "054b0615-518b-4b65-84ec-8663b07bff43", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9d00fec5-f501-4c12-831d-78936dca9c4f", + "type": "UUID", + "name": "assigneeId", + "label": "Assignee id (foreign key)", + "description": "Activity assignee id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1b094a8b-20f7-4402-8fdc-40af2405186f", + "type": "RELATION", + "name": "author", + "label": "Author", + "description": "Activity author", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c07499e3-5511-4e95-82de-5d2490c89470", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1b094a8b-20f7-4402-8fdc-40af2405186f", + "name": "author" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8e4da134-4b27-4f18-9fae-dddee6c6f3e9", + "name": "authoredActivities" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a5636fee-d403-4824-b2fd-bfdc1144927c", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "df718d85-fe6c-4740-8397-e7b3d3809ce8", + "type": "TEXT", + "name": "body", + "label": "Body", + "description": "Activity body", + "icon": "IconList", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7d84599b-e518-4dcf-8719-1b7a78843303", + "type": "DATE_TIME", + "name": "dueAt", + "label": "Due Date", + "description": "Activity due date", + "icon": "IconCalendarEvent", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "247a39b1-477f-4727-abe3-11f17a816611", + "type": "RELATION", + "name": "activityTargets", + "label": "Targets", + "description": "Activity targets", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "2ed29dba-4fa9-47d4-b5cb-7cdf6912267f", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "247a39b1-477f-4727-abe3-11f17a816611", + "name": "activityTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "89b493ae-6316-4de6-894b-e65566e3ca3a", + "name": "activity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fe4cb329-682f-426b-b367-08f7ae5f3e10", + "type": "TEXT", + "name": "type", + "label": "Type", + "description": "Activity type", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'Note'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f64fd8c3-ce7d-462f-b12d-1437434cd614", + "type": "UUID", + "name": "authorId", + "label": "Author id (foreign key)", + "description": "Activity author id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ab39f722-059b-4804-8683-a75f58db81c3", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Activity attachments", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "58616cac-8d7a-4148-9051-a6878ca7e361", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "ab39f722-059b-4804-8683-a75f58db81c3", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7592ab0d-a3bd-459f-a2cb-8220148b7cc6", + "name": "activity" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "aacc33bd-7ba4-4885-a43a-761c48826279", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "657693c8-662d-4679-b89e-d930d0c04ad9", + "type": "DATE_TIME", + "name": "completedAt", + "label": "Completion Date", + "description": "Activity completion date", + "icon": "IconCheck", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "50f61b05-868d-425b-ab3f-c085e1652d82", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "viewField", + "namePlural": "viewFields", + "labelSingular": "View Field", + "labelPlural": "View Fields", + "description": "(System) View Fields", + "icon": "IconTag", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "9561ae08-0209-4d26-ade1-04fcf0b5ee55", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "a6ec8c2f-707f-4bf8-a3de-51d7424f7f53", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_260f80ae1d2ccc67388995d6d05", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "b0ea4747-200b-4948-a5a0-10f6b7150d72", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_6d269465206d2f3e283ce479b2e", + "indexWhereClause": "\"deletedAt\" IS NULL", + "indexType": "BTREE", + "isUnique": true, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "6584884e-629c-4a0c-9531-1e5f9606efc5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "2e706f77-92fe-40e2-847e-f9d03c4e8762" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "5088ff87-fa9f-494b-8052-f30b42a9d54b", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "779c3768-2387-4954-ac78-eac2266b879c" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjk=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9561ae08-0209-4d26-ade1-04fcf0b5ee55", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2e706f77-92fe-40e2-847e-f9d03c4e8762", + "type": "UUID", + "name": "viewId", + "label": "View id (foreign key)", + "description": "View Field related view id foreign key", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "779c3768-2387-4954-ac78-eac2266b879c", + "type": "UUID", + "name": "fieldMetadataId", + "label": "Field Metadata Id", + "description": "View Field target field", + "icon": "IconTag", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "03e37726-2233-4ff6-ac40-423aad045e61", + "type": "NUMBER", + "name": "position", + "label": "Position", + "description": "View Field position", + "icon": "IconList", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": 0, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a292d9ab-60ac-4483-89a7-4473f1f7faa4", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d6a609c8-f596-46a2-acfc-086adea1744f", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "639e87d6-fbfd-4cd0-9b61-c374f2b0f816", + "type": "BOOLEAN", + "name": "isVisible", + "label": "Visible", + "description": "View Field visibility", + "icon": "IconEye", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": true, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a6aec827-c50d-4079-aff3-a7b4632d74ea", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "adffcf84-60be-48b3-bb3e-a13d5451cc84", + "type": "NUMBER", + "name": "size", + "label": "Size", + "description": "View Field size", + "icon": "IconEye", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": 0, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "911faf74-aef1-4b31-a5a3-f1ed80496f18", + "type": "RELATION", + "name": "view", + "label": "View", + "description": "View Field related view", + "icon": "IconLayoutCollage", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7184d260-316e-4e2c-af72-431957b1af98", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "50f61b05-868d-425b-ab3f-c085e1652d82", + "nameSingular": "viewField", + "namePlural": "viewFields" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "911faf74-aef1-4b31-a5a3-f1ed80496f18", + "name": "view" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "c46916fc-0528-4331-9766-6ac2247a70fb", + "nameSingular": "view", + "namePlural": "views" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8c46a404-3ed7-4a94-8a02-e9b03197de5e", + "name": "viewFields" + } + } + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "45b7e1cf-792c-45fa-8d6a-0d5e67e1fa42", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "workflowRun", + "namePlural": "workflowRuns", + "labelSingular": "Workflow Run", + "labelPlural": "Workflow Runs", + "description": "A workflow run", + "icon": "IconHistory", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "a93f8b7c-6f7b-434f-9c7c-c316640d959a", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "babe87a1-7a4d-41a3-b53b-4b0e388a0724", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_eee970874f46ff99eefc0015001", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "e5983670-2cb1-43d9-96a4-423e039570d3", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "986ce5ad-1381-4fd4-84ba-a91e2582d0ca" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "1b77049d-6e10-4dce-a985-f76540e522a4", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_faa5772594c4ce15b9305919f2f", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "075185d5-c0ec-4b02-8fe0-266cca150361", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "6b7c15a7-bc2e-4ebd-9083-9e561573f4b2" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjE0" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6b7c15a7-bc2e-4ebd-9083-9e561573f4b2", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b5683731-4a83-493a-b0f6-5a521b1fdc65", + "type": "RAW_JSON", + "name": "output", + "label": "Output", + "description": "Json object to provide output of the workflow run", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e0e5b711-ccda-4616-98e2-736d43e3b0b1", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0b00ed15-16de-44da-801d-29633021ceaa", + "type": "DATE_TIME", + "name": "startedAt", + "label": "Workflow run started at", + "description": "Workflow run started at", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a93f8b7c-6f7b-434f-9c7c-c316640d959a", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "Name of the workflow run", + "icon": "IconText", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "986ce5ad-1381-4fd4-84ba-a91e2582d0ca", + "type": "UUID", + "name": "workflowId", + "label": "Workflow id (foreign key)", + "description": "Workflow linked to the run. id foreign key", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "351e2e90-ca93-4e66-9733-811998b116d1", + "type": "SELECT", + "name": "status", + "label": "Workflow run status", + "description": "Workflow run status", + "icon": "IconStatusChange", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "'NOT_STARTED'", + "options": [ + { + "id": "79559b6a-641c-423a-8d76-bfb6f571da2e", + "color": "grey", + "label": "Not started", + "value": "NOT_STARTED", + "position": 0 + }, + { + "id": "d2443780-e320-435f-b7ba-9b17a77d401c", + "color": "yellow", + "label": "Running", + "value": "RUNNING", + "position": 1 + }, + { + "id": "e6eb98ea-9413-49d0-a7ef-1f04980890c6", + "color": "green", + "label": "Completed", + "value": "COMPLETED", + "position": 2 + }, + { + "id": "85887608-fd25-4798-a91e-90f44d2c91e1", + "color": "red", + "label": "Failed", + "value": "FAILED", + "position": 3 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1e1810ae-6ebf-49a1-b7e4-2b217c3a5a13", + "type": "RELATION", + "name": "workflow", + "label": "Workflow", + "description": "Workflow linked to the run.", + "icon": "IconSettingsAutomation", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "e73cc998-6f8b-4968-a56f-504738c4aa71", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "45b7e1cf-792c-45fa-8d6a-0d5e67e1fa42", + "nameSingular": "workflowRun", + "namePlural": "workflowRuns" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1e1810ae-6ebf-49a1-b7e4-2b217c3a5a13", + "name": "workflow" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "7cab9c82-929f-4ea3-98e1-5c221a12263d", + "nameSingular": "workflow", + "namePlural": "workflows" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "1f691bcc-0b90-452c-b922-54a6f39e9e37", + "name": "runs" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "3c44c451-c804-49f0-8505-1b6cd9b9ff8d", + "type": "UUID", + "name": "workflowVersionId", + "label": "Workflow version id (foreign key)", + "description": "Workflow version linked to the run. id foreign key", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2df11dc4-8ac5-4832-840d-391487e2743b", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ca88a1c5-f009-46e7-8350-640d4154c203", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Workflow run position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "aed8a050-3ad5-4db6-b8e3-ec207e5e381e", + "type": "DATE_TIME", + "name": "endedAt", + "label": "Workflow run ended at", + "description": "Workflow run ended at", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ff72aee0-1e66-4493-a057-4f4455f6b738", + "type": "RELATION", + "name": "workflowVersion", + "label": "Workflow version", + "description": "Workflow version linked to the run.", + "icon": "IconVersions", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "5a8466a5-1aa6-48e2-a415-878bcb28eb0e", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "45b7e1cf-792c-45fa-8d6a-0d5e67e1fa42", + "nameSingular": "workflowRun", + "namePlural": "workflowRuns" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "ff72aee0-1e66-4493-a057-4f4455f6b738", + "name": "workflowVersion" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "e5915d30-4425-4c4c-a9c4-1b4bff20c469", + "nameSingular": "workflowVersion", + "namePlural": "workflowVersions" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "8afee0ef-f4da-41bf-824a-e7cb357d8fc7", + "name": "runs" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bcd5dc97-b168-4c12-9624-9a6de51d5497", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8f462d3d-8f64-4ebb-a4a3-b0b89ff804ce", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "activityTarget", + "namePlural": "activityTargets", + "labelSingular": "Activity Target", + "labelPlural": "Activity Targets", + "description": "An activity target", + "icon": "IconCheckbox", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "d065441d-308f-4fc9-845d-30c634328802", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "f130efec-e85d-4aae-9178-4d3a7043f155", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_899f0157b7ab84de320daec7041", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "88d0bdd6-b736-4e77-ac0d-ae165ae66db5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "d4cc7cf0-d1b1-4be9-af85-de8d8e184ed1" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "be1d11f7-41e8-470d-bac8-1da9e1c06cd4", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_45db58e96a1bb9769a13a02c828", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "915aa8e3-2bd6-440f-a8b5-ba746caf88cb", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "dc199543-c1a2-44d6-861b-70631e9ba86a" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "a188d050-017d-4863-8ac8-0bcdac2fe36c", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "adf5b30e-3dad-46d9-b69b-caf13743a69c" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "422e32e1-5431-4946-be58-eac598431d29", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_6eb4b6d76fb7806d24d08bb1766", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "497ab28c-706d-4b61-befe-9823b5a140f3", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "bb01f3ff-e3d2-4f56-98b6-7cb8e41b17a5" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "aa082553-f078-48b1-976f-47d477a8ee11", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "dc199543-c1a2-44d6-861b-70631e9ba86a" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d8d0f396-224e-4d9e-8d44-f96044ff6378", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_224c121e7e3114e53f42b5774cb", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "5b205e1b-9c11-49a8-917b-a0ae7695e0e2", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "949994a2-dedb-4b12-967a-bf8646549ec2" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d065441d-308f-4fc9-845d-30c634328802", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "54a9d278-cf83-4152-bdfb-f26245d39d65", + "type": "RELATION", + "name": "person", + "label": "Person", + "description": "ActivityTarget person", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "85634196-b66b-4921-b30f-c4e078b58926", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "54a9d278-cf83-4152-bdfb-f26245d39d65", + "name": "person" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "418c54ba-a0da-4feb-8198-9287bf38e31c", + "name": "activityTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8972d18f-9c46-4d11-bffb-f77278a17b99", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b3d96a62-4d15-433d-bd4f-caf3c62d1876", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d4cc7cf0-d1b1-4be9-af85-de8d8e184ed1", + "type": "UUID", + "name": "companyId", + "label": "Company id (foreign key)", + "description": "ActivityTarget company id foreign key", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9b42b1c9-98e0-4893-9ae9-3e9195dc904a", + "type": "UUID", + "name": "rocketId", + "label": "Rocket ID (foreign key)", + "description": "ActivityTarget Rocket id foreign key", + "icon": null, + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.713Z", + "updatedAt": "2024-10-10T15:05:42.713Z", + "defaultValue": null, + "options": null, + "settings": { + "isForeignKey": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "89b493ae-6316-4de6-894b-e65566e3ca3a", + "type": "RELATION", + "name": "activity", + "label": "Activity", + "description": "ActivityTarget activity", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "2ed29dba-4fa9-47d4-b5cb-7cdf6912267f", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "89b493ae-6316-4de6-894b-e65566e3ca3a", + "name": "activity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "5a1aa92b-1ee9-4a7e-ab08-ca8c1e462d16", + "nameSingular": "activity", + "namePlural": "activities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "247a39b1-477f-4727-abe3-11f17a816611", + "name": "activityTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "36de1418-2c3d-4444-b88f-606993e3d796", + "type": "RELATION", + "name": "opportunity", + "label": "Opportunity", + "description": "ActivityTarget opportunity", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "a0f78a63-3342-48c2-966d-2de9111ef8ca", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "36de1418-2c3d-4444-b88f-606993e3d796", + "name": "opportunity" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "ba574a70-ed4f-450b-be6b-71907d7264cd", + "name": "activityTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a0752532-9ed5-4ba1-8ee4-eb7393d36a62", + "type": "RELATION", + "name": "company", + "label": "Company", + "description": "ActivityTarget company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "75f42dd7-0c3a-4658-98d2-7af163fbcf6a", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "a0752532-9ed5-4ba1-8ee4-eb7393d36a62", + "name": "company" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "b9c2d94a-41dd-4a59-8266-76973d472a09", + "name": "activityTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9deff6cb-fd97-4d2a-9c5b-be2fd90d5d7e", + "type": "RELATION", + "name": "rocket", + "label": "Rocket", + "description": "ActivityTarget Rocket", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.714Z", + "updatedAt": "2024-10-10T15:05:42.714Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0b9d9ac1-f853-4df1-be0e-1b2f6fce2d4f", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "9deff6cb-fd97-4d2a-9c5b-be2fd90d5d7e", + "name": "rocket" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "2951011a-6c7b-4a4c-bded-f212f70ca93a", + "name": "activityTargets" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bb01f3ff-e3d2-4f56-98b6-7cb8e41b17a5", + "type": "UUID", + "name": "opportunityId", + "label": "Opportunity id (foreign key)", + "description": "ActivityTarget opportunity id foreign key", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "949994a2-dedb-4b12-967a-bf8646549ec2", + "type": "UUID", + "name": "activityId", + "label": "Activity id (foreign key)", + "description": "ActivityTarget activity id foreign key", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "adf5b30e-3dad-46d9-b69b-caf13743a69c", + "type": "UUID", + "name": "personId", + "label": "Person id (foreign key)", + "description": "ActivityTarget person id foreign key", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "dc199543-c1a2-44d6-861b-70631e9ba86a", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "company", + "namePlural": "companies", + "labelSingular": "Company", + "labelPlural": "Companies", + "description": "A company", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "bed55285-cec4-4a78-ba3a-a70febd66dc6", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "371d89ea-c6b5-41b6-a190-1ab22566b8d0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_UNIQUE_2a32339058d0b6910b0834ddf81", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": true, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "229ae450-d9c8-40fa-ad58-fefe1925b806", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "c41db169-bd37-442d-a8ce-61a60307587c" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "ba420185-6ce2-4162-8705-7e7fb4ceb3b5", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_fb1f4905546cfc6d70a971c76f7", + "indexWhereClause": null, + "indexType": "GIN", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "296575a1-21ac-4821-96b5-3cfc4fda0486", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "6e131b8b-27b6-4b00-8a41-8e35ccca14ae" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d2c39785-3dfb-41e3-8d72-93438715b82f", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_123501237187c835ede626367b7", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "cf214e76-2c21-408d-a397-fd86f7e77d06", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "68df0343-db1c-46ee-a142-8694d291827a" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjI4" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4da6fb76-9f16-4fd9-baf9-3fe93dd93002", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "31df49f2-d793-462e-aca6-be5ac250f069", + "type": "MULTI_SELECT", + "name": "workPolicy", + "label": "Work Policy", + "description": "Company's Work Policy", + "icon": "IconHome", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.123Z", + "updatedAt": "2024-10-10T15:05:42.123Z", + "defaultValue": null, + "options": [ + { + "id": "4a5d069c-706e-497a-9a22-23a1ff870cc6", + "color": "green", + "label": "On-Site", + "value": "ON_SITE", + "position": 0 + }, + { + "id": "c8a21cdc-220f-45aa-ad42-506f0e3a0ae8", + "color": "turquoise", + "label": "Hybrid", + "value": "HYBRID", + "position": 1 + }, + { + "id": "3eb0c16b-e0a6-4991-a65d-5b972b12c4da", + "color": "sky", + "label": "Remote Work", + "value": "REMOTE_WORK", + "position": 2 + } + ], + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "77cdca6a-51fd-42d2-bb1c-5e423f721fff", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a4758b6c-4a48-4ec7-8898-d1f91eec75d9", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Company record position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "50c723e9-af8c-455e-a9c6-0dbd591b0258", + "type": "RELATION", + "name": "timelineActivities", + "label": "Timeline Activities", + "description": "Timeline Activities linked to the company", + "icon": "IconIconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0f9bad06-3762-4797-9079-d7d190da55e5", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "50c723e9-af8c-455e-a9c6-0dbd591b0258", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "5096824c-bd72-488b-902e-e5768070e736", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a19a3726-642e-4b1f-a8e0-0d05ce62dcd4", + "type": "BOOLEAN", + "name": "idealCustomerProfile", + "label": "ICP", + "description": "Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you", + "icon": "IconTarget", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": false, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7fbd8d22-49a3-4c55-a80b-1876129eee86", + "type": "RELATION", + "name": "people", + "label": "People", + "description": "People linked to the company.", + "icon": "IconUsers", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "659373cb-ab85-4d36-aed2-58789be66d23", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "7fbd8d22-49a3-4c55-a80b-1876129eee86", + "name": "people" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b74e80b0-7132-469f-bbd9-6e6fc12f04f8", + "nameSingular": "person", + "namePlural": "people" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7d307698-eece-4e31-8c4c-7d5d596eb542", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "77add2d7-09c4-4648-b202-f7909fd11442", + "type": "NUMBER", + "name": "employees", + "label": "Employees", + "description": "Number of employees in the company", + "icon": "IconUsers", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8caf11d2-f8c7-41df-948b-10c6e702459c", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ef38e2d0-9369-4744-840d-85e5841f36dc", + "type": "LINKS", + "name": "xLink", + "label": "X", + "description": "The company Twitter/X account", + "icon": "IconBrandX", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "585ee60d-cc7d-4500-8db9-e38ab8f90a60", + "type": "ADDRESS", + "name": "address", + "label": "Address", + "description": "Address of the company", + "icon": "IconMap", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "addressLat": null, + "addressLng": null, + "addressCity": "''", + "addressState": "''", + "addressCountry": "''", + "addressStreet1": "''", + "addressStreet2": "''", + "addressPostcode": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "bed55285-cec4-4a78-ba3a-a70febd66dc6", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "The company name", + "icon": "IconBuildingSkyscraper", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b9c2d94a-41dd-4a59-8266-76973d472a09", + "type": "RELATION", + "name": "activityTargets", + "label": "Activities", + "description": "Activities tied to the company", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "75f42dd7-0c3a-4658-98d2-7af163fbcf6a", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "b9c2d94a-41dd-4a59-8266-76973d472a09", + "name": "activityTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "a0752532-9ed5-4ba1-8ee4-eb7393d36a62", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "395bfecf-e9ce-46d2-9612-3d8840ee3f59", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "8323137c-6b37-4ec2-9977-accefa773841", + "type": "RELATION", + "name": "opportunities", + "label": "Opportunities", + "description": "Opportunities linked to the company.", + "icon": "IconTargetArrow", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c2fcfb71-1f23-47b7-a818-27371a165214", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "8323137c-6b37-4ec2-9977-accefa773841", + "name": "opportunities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "be13cda6-aff5-4003-8fe9-e936011b3325", + "nameSingular": "opportunity", + "namePlural": "opportunities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "a679e0f1-1766-4814-ac8e-64b82329cdb9", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c41db169-bd37-442d-a8ce-61a60307587c", + "type": "LINKS", + "name": "domainName", + "label": "Domain Name", + "description": "The company website URL. We use this url to fetch the company icon", + "icon": "IconLink", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "9aa7c531-5fcc-4a24-a149-a28daf8d11a3", + "type": "LINKS", + "name": "linkedinLink", + "label": "Linkedin", + "description": "The company Linkedin account", + "icon": "IconBrandLinkedin", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "09e066b4-d809-49c8-908b-51b8b8724a4c", + "type": "RELATION", + "name": "accountOwner", + "label": "Account Owner", + "description": "Your team member responsible for managing the company account", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "9b71c7ff-5d2e-43c4-a524-611e309e6f45", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "09e066b4-d809-49c8-908b-51b8b8724a4c", + "name": "accountOwner" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "b6b66a3c-6c39-4e93-b940-836aced4de12", + "name": "accountOwnerForCompanies" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "408a7fce-1980-48b2-9c0e-9e23b58b5e07", + "type": "RELATION", + "name": "noteTargets", + "label": "Notes", + "description": "Notes tied to the company", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "99a8cc42-5f5d-41f2-9d5a-44e18f8412e4", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "408a7fce-1980-48b2-9c0e-9e23b58b5e07", + "name": "noteTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "67bfa361-4269-4494-b2eb-3a9f26f992fd", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "328a0398-9023-4f24-b4df-ba56f5efcc0e", + "type": "TEXT", + "name": "tagline", + "label": "Tagline", + "description": "Company's Tagline", + "icon": "IconAdCircle", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:41.897Z", + "updatedAt": "2024-10-10T15:05:41.897Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "134b368a-cf99-438e-8105-9150a2827fd4", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites linked to the company", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "3e2f75b9-76aa-436f-8d24-129798ca4090", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "134b368a-cf99-438e-8105-9150a2827fd4", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "3f7749a5-7d4e-4b3c-b5ae-12b8ea631676", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1b548bb0-c4fc-4232-a18f-b7882b6a1ddf", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Attachments linked to the company", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c9ce54d1-cb4a-4492-879f-be86056fcce5", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1b548bb0-c4fc-4232-a18f-b7882b6a1ddf", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "2f907f04-3122-4b7b-bd83-c192cacc4a83", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "7d98e29b-96a1-4f6b-973e-dac0b3477f03", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "e79f5c59-356b-473d-97f6-69fc4e075134", + "type": "LINKS", + "name": "introVideo", + "label": "Intro Video", + "description": "Company's Intro Video", + "icon": "IconVideo", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.009Z", + "updatedAt": "2024-10-10T15:05:42.009Z", + "defaultValue": { + "primaryLinkUrl": "''", + "secondaryLinks": null, + "primaryLinkLabel": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "eafdfdda-7cda-4fe4-bcaa-be49232fbfd4", + "type": "RELATION", + "name": "taskTargets", + "label": "Tasks", + "description": "Tasks tied to the company", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "e38b7482-42bb-43b3-a36f-9c7eec06d59e", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "39d5f2b7-03ce-41e7-afe9-7710aeb766a2", + "nameSingular": "company", + "namePlural": "companies" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "eafdfdda-7cda-4fe4-bcaa-be49232fbfd4", + "name": "taskTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "24a17b5b-e5cd-43c9-bcd8-422a00b0ebf6", + "name": "company" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6e131b8b-27b6-4b00-8a41-8e35ccca14ae", + "type": "TS_VECTOR", + "name": "searchVector", + "label": "Search vector", + "description": "Field used for full-text search", + "icon": "IconUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "dbcb6817-a87a-4d76-8b1c-eca83d525c5d", + "type": "CURRENCY", + "name": "annualRecurringRevenue", + "label": "ARR", + "description": "Annual Recurring Revenue: The actual or estimated annual revenue of the company", + "icon": "IconMoneybag", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": { + "amountMicros": null, + "currencyCode": "''" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "68df0343-db1c-46ee-a142-8694d291827a", + "type": "UUID", + "name": "accountOwnerId", + "label": "Account Owner id (foreign key)", + "description": "Your team member responsible for managing the company account id foreign key", + "icon": "IconUserCircle", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5e0a25f0-fe8b-42f3-af2b-423766333ab2", + "type": "BOOLEAN", + "name": "visaSponsorship", + "label": "Visa Sponsorship", + "description": "Company's Visa Sponsorship Policy", + "icon": "IconBrandVisa", + "isCustom": true, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.226Z", + "updatedAt": "2024-10-10T15:05:42.226Z", + "defaultValue": false, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "2590029a-05d7-4908-8b7a-a253967068a1", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "auditLog", + "namePlural": "auditLogs", + "labelSingular": "Audit Log", + "labelPlural": "Audit Logs", + "description": "An audit log of actions performed in the system", + "icon": "IconIconTimelineEvent", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "4fdcef64-197f-438c-8b92-a6712a80e747", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "d0f42493-dfe8-43a9-9c2f-bb0f63759210", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_ca389a7ad7595bb15d733535998", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "8115bdeb-f099-464f-b438-5ac80d23637a", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "c89083d6-f45d-4fd0-8149-9c3a9e71eb1f" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "743142f7-9e4d-4f92-beae-7d7040233009", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a8ebb52f-95f7-4c65-bd68-b9d2372907c8", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "be533c19-a031-4c6d-86cf-3e2fb2defb2f", + "type": "UUID", + "name": "workspaceMemberId", + "label": "Workspace Member id (foreign key)", + "description": "Event workspace member id foreign key", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c89083d6-f45d-4fd0-8149-9c3a9e71eb1f", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1475304d-4734-4b66-96a8-ed7d84727fe6", + "type": "RELATION", + "name": "workspaceMember", + "label": "Workspace Member", + "description": "Event workspace member", + "icon": "IconCircleUser", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "1aef1a4d-f090-4f5a-8e39-f7d21d465199", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "2590029a-05d7-4908-8b7a-a253967068a1", + "nameSingular": "auditLog", + "namePlural": "auditLogs" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1475304d-4734-4b66-96a8-ed7d84727fe6", + "name": "workspaceMember" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "f2414140-86ea-4fa3-bc63-ca5dab9f9044", + "nameSingular": "workspaceMember", + "namePlural": "workspaceMembers" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "4af78cad-69c5-4190-a23f-6db322f80f27", + "name": "auditLogs" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "86e941ea-a43a-412c-8197-479adbb09a15", + "type": "RAW_JSON", + "name": "context", + "label": "Event context", + "description": "Json object to provide context (user, device, workspace, etc.)", + "icon": "IconListDetails", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "ab8ea51d-bc10-426c-9e5c-65c09c09f5bd", + "type": "TEXT", + "name": "objectMetadataId", + "label": "Object metadata id", + "description": "Object metadata id", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4fdcef64-197f-438c-8b92-a6712a80e747", + "type": "TEXT", + "name": "name", + "label": "Event name", + "description": "Event name/type", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d6812840-4b41-46f8-a5c0-6ad697d00fe3", + "type": "UUID", + "name": "recordId", + "label": "Record id", + "description": "Record id", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0b88c2e2-68a1-488a-ae3c-32f1919ba8b8", + "type": "TEXT", + "name": "objectName", + "label": "Object name", + "description": "Object name", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "be183254-da94-4664-a8d4-b72a26ba115d", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5e414096-d916-4d3f-9710-54a4932078a8", + "type": "RAW_JSON", + "name": "properties", + "label": "Event details", + "description": "Json value for event details", + "icon": "IconListDetails", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "rocket", + "namePlural": "rockets", + "labelSingular": "Rocket", + "labelPlural": "Rockets", + "description": "A rocket", + "icon": "IconRocket", + "isCustom": true, + "isRemote": false, + "isActive": true, + "isSystem": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "labelIdentifierFieldMetadataId": null, + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "ee1a3903-9f40-4789-a61c-90716bce789d", + "createdAt": "2024-10-10T15:05:42.750Z", + "updatedAt": "2024-10-10T15:05:42.750Z", + "name": "IDX_530792e4278e7696c4e3e3e55f8", + "indexWhereClause": null, + "indexType": "GIN", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "b6b09e0f-38a7-4c1a-b80b-46e392accdd8", + "createdAt": "2024-10-10T15:05:42.750Z", + "updatedAt": "2024-10-10T15:05:42.750Z", + "order": 0, + "fieldMetadataId": "c2df610f-40a0-4c0f-9c24-dd3356f5f871" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "4d64de07-68da-42c9-b169-d5adf91ae282", + "type": "RELATION", + "name": "favorites", + "label": "Favorites", + "description": "Favorites tied to the Rocket", + "icon": "IconHeart", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.718Z", + "updatedAt": "2024-10-10T15:05:42.718Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "9a12826d-783a-4411-89ac-a2a2369e5eb2", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "4d64de07-68da-42c9-b169-d5adf91ae282", + "name": "favorites" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "cb8c8d67-16c0-4a38-a919-b375845abf42", + "nameSingular": "favorite", + "namePlural": "favorites" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "89fe6ca9-b01e-4f73-8fe2-f51ba3a67024", + "name": "rocket" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "38556e40-979a-4c6a-9328-a99b1392d98f", + "type": "POSITION", + "name": "position", + "label": "Position", + "description": "Position", + "icon": "IconHierarchy2", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c2df610f-40a0-4c0f-9c24-dd3356f5f871", + "type": "TS_VECTOR", + "name": "searchVector", + "label": "Search vector", + "description": "Field used for full-text search", + "icon": null, + "isCustom": false, + "isActive": false, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.747Z", + "updatedAt": "2024-10-10T15:05:42.747Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "492bf0fb-85ee-4805-a41d-6ad94b1bd904", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "d4700cc4-14a1-4264-96c9-0d87ebeeb482", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Deletion date", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "c2721d06-ae7f-40e4-b061-d96179f0be97", + "type": "RELATION", + "name": "attachments", + "label": "Attachments", + "description": "Attachments tied to the Rocket", + "icon": "IconFileImport", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.722Z", + "updatedAt": "2024-10-10T15:05:42.722Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "07793950-4b19-4d72-afda-f1815ec8e5e4", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "c2721d06-ae7f-40e4-b061-d96179f0be97", + "name": "attachments" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "d00ff1e9-774a-4b08-87fb-03d37c24f174", + "nameSingular": "attachment", + "namePlural": "attachments" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "01b00bde-0e1f-4e47-ad06-9df00dd1c7a3", + "name": "rocket" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b3186db8-8ea1-49b6-8922-82e4bdc06eb9", + "type": "RELATION", + "name": "noteTargets", + "label": "Notes", + "description": "Notes tied to the Rocket", + "icon": "IconNotes", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.725Z", + "updatedAt": "2024-10-10T15:05:42.725Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "bd9974b4-9210-4ef3-892d-4adc2d40feb6", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "b3186db8-8ea1-49b6-8922-82e4bdc06eb9", + "name": "noteTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "fd99213f-1b50-4d72-8708-75ba80097736", + "nameSingular": "noteTarget", + "namePlural": "noteTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "a87c2280-8913-4e89-b6a3-4403b70087d4", + "name": "rocket" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "2951011a-6c7b-4a4c-bded-f212f70ca93a", + "type": "RELATION", + "name": "activityTargets", + "label": "Activities", + "description": "Activities tied to the Rocket", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.714Z", + "updatedAt": "2024-10-10T15:05:42.714Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "0b9d9ac1-f853-4df1-be0e-1b2f6fce2d4f", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "2951011a-6c7b-4a4c-bded-f212f70ca93a", + "name": "activityTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "43fe0e45-b323-4b6e-ab98-1d9fe30c9af9", + "nameSingular": "activityTarget", + "namePlural": "activityTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "9deff6cb-fd97-4d2a-9c5b-be2fd90d5d7e", + "name": "rocket" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "baea02d1-1817-4df3-b9eb-c8020452f3e0", + "type": "RELATION", + "name": "timelineActivities", + "label": "Timeline Activities", + "description": "Timeline Activities tied to the Rocket", + "icon": "IconIconTimelineEvent", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.709Z", + "updatedAt": "2024-10-10T15:05:42.709Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "f8ddd621-26f4-4fa8-b426-3f545094bd5f", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "baea02d1-1817-4df3-b9eb-c8020452f3e0", + "name": "timelineActivities" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "b6e22795-68e7-4d18-a242-545afea5a8a9", + "nameSingular": "timelineActivity", + "namePlural": "timelineActivities" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7d14aef8-f63c-47cd-8ce7-29806518d6ca", + "name": "rocket" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "fd7a600a-93e7-4139-b944-4cd2022f07c6", + "type": "ACTOR", + "name": "createdBy", + "label": "Created by", + "description": "The creator of the record", + "icon": "IconCreativeCommonsSa", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": { + "name": "''", + "source": "'MANUAL'" + }, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1fb5fc79-28b2-419f-87cf-ecb498d7d3dd", + "type": "TEXT", + "name": "name", + "label": "Name", + "description": "Name", + "icon": "IconAbc", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": "'Untitled'", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "f3e444fc-afa9-45d7-b885-5000c2fa2b7d", + "type": "RELATION", + "name": "taskTargets", + "label": "Tasks", + "description": "Tasks tied to the Rocket", + "icon": "IconCheckbox", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.731Z", + "updatedAt": "2024-10-10T15:05:42.731Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "7df72299-c1f4-4575-98bf-24156cb3e5b8", + "direction": "ONE_TO_MANY", + "sourceObjectMetadata": { + "__typename": "object", + "id": "1e5ee6b2-67e5-4549-bebc-8d35bc6bc649", + "nameSingular": "rocket", + "namePlural": "rockets" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "f3e444fc-afa9-45d7-b885-5000c2fa2b7d", + "name": "taskTargets" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "edfd2da3-26e4-4e84-b490-c0790848dc23", + "nameSingular": "taskTarget", + "namePlural": "taskTargets" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "4c42e3b9-26a9-4ce1-a37a-9606da0bc12a", + "name": "rocket" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "22ea99a0-3835-4e6a-b3ef-af40ec618326", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": "now", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "6ba199c2-44a2-4cee-b9f2-ee6d6686b6f8", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:42.702Z", + "updatedAt": "2024-10-10T15:05:42.702Z", + "defaultValue": "now", + "options": null, + "settings": null, + "relationDefinition": null + } + } + ] + } + } + }, + { + "__typename": "objectEdge", + "node": { + "__typename": "object", + "id": "149f1a0d-f528-48a3-a3f8-0203926d07f5", + "dataSourceId": "d8a38ce6-6ac9-4c10-b55f-408386f86290", + "nameSingular": "calendarChannelEventAssociation", + "namePlural": "calendarChannelEventAssociations", + "labelSingular": "Calendar Channel Event Association", + "labelPlural": "Calendar Channel Event Associations", + "description": "Calendar Channel Event Associations", + "icon": "IconCalendar", + "isCustom": false, + "isRemote": false, + "isActive": true, + "isSystem": true, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "labelIdentifierFieldMetadataId": "0687870d-58fb-45ee-ad5f-9c35bea78a70", + "imageIdentifierFieldMetadataId": null, + "indexMetadatas": { + "__typename": "ObjectIndexMetadatasConnection", + "edges": [ + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "7e192f75-5a94-4914-a60a-58d324a76ae4", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_a88c3ab301c25202d4b52fb4b1b", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "71fa278a-a8a3-4129-bdaf-8ee244ce5257", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "72a89c96-8301-46b5-b2ba-b05c418a9863" + } + } + ] + } + } + }, + { + "__typename": "indexEdge", + "node": { + "__typename": "index", + "id": "90fc8a97-03c9-4bf3-8de0-7a9bc6417579", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "name": "IDX_92a888b681107c4f78926820db7", + "indexWhereClause": null, + "indexType": "BTREE", + "isUnique": false, + "indexFieldMetadatas": { + "__typename": "IndexIndexFieldMetadatasConnection", + "edges": [ + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "632e255a-abbf-466c-96e1-f9b9c160cfc0", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 1, + "fieldMetadataId": "045d4eed-8f9f-463d-9ee0-4b579c090121" + } + }, + { + "__typename": "indexFieldEdge", + "node": { + "__typename": "indexField", + "id": "e7a0d7d5-0006-4795-b9e4-3ab822aa16e1", + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "order": 0, + "fieldMetadataId": "90e949eb-5d48-4a77-8496-71aa56184df7" + } + } + ] + } + } + } + ] + }, + "fields": { + "__typename": "ObjectFieldsConnection", + "pageInfo": { + "__typename": "PageInfo", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", + "endCursor": "YXJyYXljb25uZWN0aW9uOjk=" + }, + "edges": [ + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "045d4eed-8f9f-463d-9ee0-4b579c090121", + "type": "DATE_TIME", + "name": "deletedAt", + "label": "Deleted at", + "description": "Date when the record was deleted", + "icon": "IconCalendarMinus", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "1dc48a6a-270b-466a-8e11-9efd02729791", + "type": "RELATION", + "name": "calendarEvent", + "label": "Event ID", + "description": "Event ID", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "daa269f7-c111-492e-88c8-1bfe82e3d637", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "149f1a0d-f528-48a3-a3f8-0203926d07f5", + "nameSingular": "calendarChannelEventAssociation", + "namePlural": "calendarChannelEventAssociations" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "1dc48a6a-270b-466a-8e11-9efd02729791", + "name": "calendarEvent" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "981fd8a9-37a2-4742-98c1-08509d995bd3", + "nameSingular": "calendarEvent", + "namePlural": "calendarEvents" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "2d337999-7468-4f6f-bce0-87679dcb5e2f", + "name": "calendarChannelEventAssociations" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a407c6e4-c9ea-4dd4-838a-230893118ab8", + "type": "TEXT", + "name": "recurringEventExternalId", + "label": "Recurring Event ID", + "description": "Recurring Event ID", + "icon": "IconHistory", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0687870d-58fb-45ee-ad5f-9c35bea78a70", + "type": "UUID", + "name": "id", + "label": "Id", + "description": "Id", + "icon": "Icon123", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "uuid", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "0b649b56-2018-4645-b6d1-3dbb44125b50", + "type": "TEXT", + "name": "eventExternalId", + "label": "Event external ID", + "description": "Event external ID", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "''", + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "5d68a9e6-9e85-4ae4-8601-43e685dcf90b", + "type": "RELATION", + "name": "calendarChannel", + "label": "Channel ID", + "description": "Channel ID", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": true, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": { + "__typename": "RelationDefinition", + "relationId": "c040d959-808f-4b7d-8844-ac87a45c0b04", + "direction": "MANY_TO_ONE", + "sourceObjectMetadata": { + "__typename": "object", + "id": "149f1a0d-f528-48a3-a3f8-0203926d07f5", + "nameSingular": "calendarChannelEventAssociation", + "namePlural": "calendarChannelEventAssociations" + }, + "sourceFieldMetadata": { + "__typename": "field", + "id": "5d68a9e6-9e85-4ae4-8601-43e685dcf90b", + "name": "calendarChannel" + }, + "targetObjectMetadata": { + "__typename": "object", + "id": "8cceadc4-de6b-4ecf-8324-82c6b4eec077", + "nameSingular": "calendarChannel", + "namePlural": "calendarChannels" + }, + "targetFieldMetadata": { + "__typename": "field", + "id": "7ec1ea5f-c98f-4569-91ef-8797294cf183", + "name": "calendarChannelEventAssociations" + } + } + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "90e949eb-5d48-4a77-8496-71aa56184df7", + "type": "UUID", + "name": "calendarEventId", + "label": "Event ID id (foreign key)", + "description": "Event ID id foreign key", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "a4b46b44-3ff6-46cd-b354-2ae81e3298e8", + "type": "DATE_TIME", + "name": "updatedAt", + "label": "Last update", + "description": "Last time the record was changed", + "icon": "IconCalendarClock", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "72a89c96-8301-46b5-b2ba-b05c418a9863", + "type": "UUID", + "name": "calendarChannelId", + "label": "Channel ID id (foreign key)", + "description": "Channel ID id foreign key", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": true, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": null, + "options": null, + "settings": null, + "relationDefinition": null + } + }, + { + "__typename": "fieldEdge", + "node": { + "__typename": "field", + "id": "b7acd3f1-6921-4a7a-92a6-fbf48572bbc2", + "type": "DATE_TIME", + "name": "createdAt", + "label": "Creation date", + "description": "Creation date", + "icon": "IconCalendar", + "isCustom": false, + "isActive": true, + "isSystem": false, + "isNullable": false, + "isUnique": false, + "createdAt": "2024-10-10T15:05:37.064Z", + "updatedAt": "2024-10-10T15:05:37.064Z", + "defaultValue": "now", + "options": null, + "settings": { + "displayAsRelativeDate": true + }, + "relationDefinition": null + } + } + ] + } + } + } + ] + } + } as ObjectMetadataItemsQuery; \ No newline at end of file diff --git a/packages/twenty-front/src/testing/mock-data/generated/standard-metadata-query-result.ts b/packages/twenty-front/src/testing/mock-data/generated/standard-metadata-query-result.ts deleted file mode 100644 index 664750f85e25..000000000000 --- a/packages/twenty-front/src/testing/mock-data/generated/standard-metadata-query-result.ts +++ /dev/null @@ -1,13564 +0,0 @@ -import { - ObjectMetadataItemsQuery -} from '~/generated-metadata/graphql'; - -// This file is not designed to be manually edited. -// It's an extract from the dev seeded environment metadata call -// TODO: automate the generation of this file -export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = { - __typename: 'Query', - objects: { - "__typename": "ObjectConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjMx" - }, - "edges": [ - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities", - "labelSingular": "Timeline Activity", - "labelPlural": "Timeline Activities", - "description": "Aggregated / filtered event to be displayed on the timeline", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "b2b89347-48a2-4f0c-a176-ad10ae33b7b1", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjIw" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0e545220-a625-4571-a8e7-f97b7aee90c1", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "Event note id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "987fd4f6-4c5f-48a4-82f3-fd769de80dc4", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "Event opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d8ae1b79-b532-412c-92cf-767a32e3cda2", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "987fd4f6-4c5f-48a4-82f3-fd769de80dc4", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4e24ba90-8fcd-4df5-9fe8-48679c75d374", - "name": "timelineActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ce28e696-4969-4e06-9056-6a176d9d7c90", - "type": "UUID", - "name": "linkedObjectMetadataId", - "label": "Linked Object Metadata Id", - "description": "inked Object Metadata Id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e48eeafe-43d8-4abc-95c8-6e7a6a56a7c9", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "Event task", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "87c0082f-5411-4202-97cd-fc1d9112fa7a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e48eeafe-43d8-4abc-95c8-6e7a6a56a7c9", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "975e6a19-d90c-45dc-9bb0-ffc57f4e1950", - "name": "timelineActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b2b89347-48a2-4f0c-a176-ad10ae33b7b1", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0224c08b-2c2e-474f-8360-dafad378cf62", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Event company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1e93ed03-91a5-4ad4-bca7-c6a637551289", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0224c08b-2c2e-474f-8360-dafad378cf62", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f39f1db9-3d7f-46d3-aa0c-4cae44352407", - "name": "timelineActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0669197c-bc4e-4a44-9cd9-db449bfa380e", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Event person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "680351ba-8759-405d-8fda-90799bf75741", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0669197c-bc4e-4a44-9cd9-db449bfa380e", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3700e772-3bf6-4150-b5ce-f7f00ded863a", - "name": "timelineActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a3a51b55-8387-412f-8661-cbaf58bde346", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Event person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f064e57f-26ea-4aa5-9f95-eda25af30f0f", - "type": "DATE_TIME", - "name": "happensAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bc0e2a25-4e13-4751-a79a-2d264582ef9a", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "Event note", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "7ec36219-a377-4aea-98be-7954590f8a32", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "bc0e2a25-4e13-4751-a79a-2d264582ef9a", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b66379fc-ac94-4823-b759-aa940fde9c73", - "name": "timelineActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ae929592-4f74-419e-8b26-6d216859078f", - "type": "RAW_JSON", - "name": "properties", - "label": "Event details", - "description": "Json value for event details", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "64a86ebc-93d3-47f2-a013-0e6c8ef2af18", - "type": "TEXT", - "name": "name", - "label": "Event name", - "description": "Event name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7df779f8-89a1-4c41-9868-0d98f53b29aa", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Event company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cf56c3f4-dd93-465c-8ab3-edbd6cc5f246", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "27cf8bcc-101c-42e1-999f-32365e0abc80", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "Event opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9ddef7df-ec3c-42b6-b279-ffb60dbf5a8a", - "type": "UUID", - "name": "linkedRecordId", - "label": "Linked Record id", - "description": "Linked Record id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f465561a-ea75-46e7-8110-dd4ed4f25f72", - "type": "TEXT", - "name": "linkedRecordCachedName", - "label": "Linked Record cached name", - "description": "Cached record name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b7d464b1-d234-4295-9845-bed07dbc41e4", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "Event task id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "63ec5746-623a-41d5-8ed8-aca9577102eb", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9fc19fe9-2563-41ac-8c92-2062ff3a0c0c", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Event workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5b2015dd-3fac-4118-adf5-3cceede873eb", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "9fc19fe9-2563-41ac-8c92-2062ff3a0c0c", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ace8a324-075e-49a3-92fa-34e07590ec72", - "name": "timelineActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d59ec465-34c7-471d-9c20-7be1f081d4cd", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Event workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "message", - "namePlural": "messages", - "labelSingular": "Message", - "labelPlural": "Messages", - "description": "Message", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "34c76e0d-23e5-476a-bd56-2f9b02eae3ea", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "74b4d296-dfde-40b4-b6aa-012a17ef6451", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "138ee2a7-7f7e-4901-b5d2-7ccc13a0bc38", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "906714b3-0268-49bb-85b4-705587e6f4c1", - "type": "SELECT", - "name": "direction", - "label": "Direction", - "description": "Message Direction", - "icon": "IconDirection", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'INCOMING'", - "options": [ - { - "id": "09fd3f5f-5903-4a3a-8f8b-335825349389", - "color": "green", - "label": "Incoming", - "value": "INCOMING", - "position": 0 - }, - { - "id": "0df4272e-dfef-450e-84b7-d1477e66ee7f", - "color": "blue", - "label": "Outgoing", - "value": "OUTGOING", - "position": 1 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "114f853e-2684-4e62-92c9-0213ace3c498", - "type": "RELATION", - "name": "messageThread", - "label": "Message Thread Id", - "description": "Message Thread Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c958fe88-7d66-4c1b-87c7-55ab724f42c5", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "nameSingular": "message", - "namePlural": "messages" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "114f853e-2684-4e62-92c9-0213ace3c498", - "name": "messageThread" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1f73c3c3-a356-4a70-8a91-948e70120fdf", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "9016096d-93c4-495f-93d5-b966e5bedc74", - "name": "messages" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0837697b-286c-43e2-9364-532d5e06cd76", - "type": "DATE_TIME", - "name": "receivedAt", - "label": "Received At", - "description": "The date the message was received", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f6a6d3f8-fef7-4d91-b187-7ea8d6291372", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "15658254-6562-4fad-9ef3-393f913e95c2", - "type": "RELATION", - "name": "messageParticipants", - "label": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2180d888-98dc-428e-a157-c30ce7bf8ce4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "nameSingular": "message", - "namePlural": "messages" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "15658254-6562-4fad-9ef3-393f913e95c2", - "name": "messageParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "660b4257-010e-4039-897a-e274f2559ed5", - "name": "message" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c4f2dcde-1110-419e-8590-dd1a26a0dfec", - "type": "UUID", - "name": "messageThreadId", - "label": "Message Thread Id id (foreign key)", - "description": "Message Thread Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "eb2e56c5-db83-4844-9179-3890e31edf15", - "type": "TEXT", - "name": "headerMessageId", - "label": "Header message Id", - "description": "Message id from the message header", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2ac789bf-ce05-4f0e-9f04-f848f93c2f21", - "type": "RELATION", - "name": "messageChannelMessageAssociations", - "label": "Message Channel Association", - "description": "Messages from the channel.", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "642b4d8c-f2f8-4590-abce-4b112d8689ba", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "nameSingular": "message", - "namePlural": "messages" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2ac789bf-ce05-4f0e-9f04-f848f93c2f21", - "name": "messageChannelMessageAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "785c0609-42b8-4b0e-b7c2-4d54b6ed651f", - "name": "message" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0b97d62c-af50-48e2-af87-eaedc63c17ee", - "type": "TEXT", - "name": "text", - "label": "Text", - "description": "Text", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "34c76e0d-23e5-476a-bd56-2f9b02eae3ea", - "type": "TEXT", - "name": "subject", - "label": "Subject", - "description": "Subject", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "noteTarget", - "namePlural": "noteTargets", - "labelSingular": "Note Target", - "labelPlural": "Note Targets", - "description": "A note target", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "e97affc0-248a-4f3b-b1c5-f46f3d1edd21", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEw" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5b2ec790-e8b8-4bd0-bf1b-db4ebc2b473a", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "NoteTarget opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c0946e53-4cdd-46b4-b30a-9fce040b9a7a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5b2ec790-e8b8-4bd0-bf1b-db4ebc2b473a", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e6fe20c1-091e-418f-9ff0-8ea7cfb864f8", - "name": "noteTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "be09126d-4c9e-42d1-b686-cd43c4aa32df", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "87334d50-0c5d-4327-a8c5-3db6bc28c1ea", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "NoteTarget note", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "663e9842-8b92-451a-bf73-12a886ff8b05", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "87334d50-0c5d-4327-a8c5-3db6bc28c1ea", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "04794a4e-35c3-46a9-8bf3-8ba1c0324f0b", - "name": "noteTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9025db40-92fb-4fd5-9df3-c5bbf2878e61", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "12ccd632-bfd4-47e7-80dc-4c3f913372f6", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "NoteTarget note id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f90c9e4d-c3d4-43d2-9697-9307e373669d", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "NoteTarget person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8ac4df39-f1a0-4221-a605-bd4c229fbc12", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "NoteTarget person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "700c5e52-3e7b-4471-9826-82270c03c37e", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "8ac4df39-f1a0-4221-a605-bd4c229fbc12", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "82d1a637-3df9-4d59-a412-1cbc1d92baf2", - "name": "noteTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e97affc0-248a-4f3b-b1c5-f46f3d1edd21", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b92459f6-3ab6-4b8f-855e-a759b45118df", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "NoteTarget opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "662f602c-a292-489e-b784-476168f1efcb", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "NoteTarget company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5a0243d0-051b-4f30-b0d2-da66b3b8eefe", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "NoteTarget company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1670824b-e097-4afc-8401-feab7f9af0d4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5a0243d0-051b-4f30-b0d2-da66b3b8eefe", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "74bf3aba-450e-48f9-987a-60662929e768", - "name": "noteTargets" - } - }, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "d2834e90-eecc-4528-bab3-ad005effd6f2", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents", - "labelSingular": "Calendar event", - "labelPlural": "Calendar events", - "description": "Calendar events", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "a13f7bf6-02ab-4f9b-bd3a-c84a56e7ed49", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjE3" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "83cb4332-1363-4228-ab85-7a3d2c4922d1", - "type": "DATE_TIME", - "name": "externalUpdatedAt", - "label": "Update DateTime", - "description": "Update DateTime", - "icon": "IconCalendarCog", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "dde2d1d3-5a7d-4cf3-a982-4f7aff2dfb37", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c8013b48-cc15-4f19-8141-325a12e771e3", - "type": "BOOLEAN", - "name": "isFullDay", - "label": "Is Full Day", - "description": "Is Full Day", - "icon": "Icon24Hours", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": false, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "771aa870-f4e7-4fa6-b2e4-52c41cf3d5fc", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fe7dcb62-099f-4ad1-af7e-a74713f6159d", - "type": "RELATION", - "name": "calendarChannelEventAssociations", - "label": "Calendar Channel Event Associations", - "description": "Calendar Channel Event Associations", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0f9d244b-e9c6-44af-88f4-9ce798d50bf8", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d2834e90-eecc-4528-bab3-ad005effd6f2", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fe7dcb62-099f-4ad1-af7e-a74713f6159d", - "name": "calendarChannelEventAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4fed9657-e68b-4856-8e6d-a1c860d16242", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "18cea1c1-f521-4c41-b694-729756931795", - "name": "calendarEvent" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "74820be8-b911-440a-b7bd-622b3c0fb2f0", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d8ca199f-72ce-4a48-9e29-133a05520b0f", - "type": "TEXT", - "name": "location", - "label": "Location", - "description": "Location", - "icon": "IconMapPin", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a4688389-8263-4209-b72c-a6004d7f0804", - "type": "LINKS", - "name": "conferenceLink", - "label": "Meet Link", - "description": "Meet Link", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6d195419-c615-4b2f-be73-74481a270851", - "type": "DATE_TIME", - "name": "endsAt", - "label": "End Date", - "description": "End Date", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a13f7bf6-02ab-4f9b-bd3a-c84a56e7ed49", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Title", - "icon": "IconH1", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "eb3a27fb-9cb8-4017-b896-e52eaf801dc2", - "type": "RELATION", - "name": "calendarEventParticipants", - "label": "Event Participants", - "description": "Event Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e02ea7b1-1d5a-481b-ab71-3c94ab3f9bf0", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "d2834e90-eecc-4528-bab3-ad005effd6f2", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "eb3a27fb-9cb8-4017-b896-e52eaf801dc2", - "name": "calendarEventParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b04775e2-53a3-4f62-a2ab-858f2a456fa7", - "name": "calendarEvent" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "39d10ace-420f-4053-9d68-1111d3b8f39c", - "type": "TEXT", - "name": "description", - "label": "Description", - "description": "Description", - "icon": "IconFileDescription", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "39534ddd-1942-48ff-a2dc-c0365920bf73", - "type": "DATE_TIME", - "name": "startsAt", - "label": "Start Date", - "description": "Start Date", - "icon": "IconCalendarClock", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f3ed1238-8ccf-45e9-8feb-996c3052e57f", - "type": "BOOLEAN", - "name": "isCanceled", - "label": "Is canceled", - "description": "Is canceled", - "icon": "IconCalendarCancel", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": false, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f6723e97-b9a7-48d7-960c-291e105190fd", - "type": "DATE_TIME", - "name": "externalCreatedAt", - "label": "Creation DateTime", - "description": "Creation DateTime", - "icon": "IconCalendarPlus", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2f6c8e0f-dd7b-4f0b-8e5d-1205720b280b", - "type": "TEXT", - "name": "iCalUID", - "label": "iCal UID", - "description": "iCal UID", - "icon": "IconKey", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f7fddaf8-600c-4ff2-8538-19cc56582764", - "type": "TEXT", - "name": "conferenceSolution", - "label": "Conference Solution", - "description": "Conference Solution", - "icon": "IconScreenShare", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b6595570-eb50-47db-a823-832bf384ae25", - "type": "TEXT", - "name": "recurringEventExternalId", - "label": "Recurring Event ID", - "description": "Recurring Event ID", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "ccb2a7ce-f998-4363-b951-cdf7409b64dc", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "auditLog", - "namePlural": "auditLogs", - "labelSingular": "Audit Log", - "labelPlural": "Audit Logs", - "description": "An audit log of actions performed in the system", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "850587c2-97aa-42b0-a1f0-e87a1ad98f8c", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEw" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2ea84bb0-d37a-4b30-a562-5b1124f9090d", - "type": "UUID", - "name": "recordId", - "label": "Record id", - "description": "Record id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8cc90681-a560-4af8-8a68-695c21b981b1", - "type": "TEXT", - "name": "objectName", - "label": "Object name", - "description": "Object name", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7eda7a7a-c335-429e-b9ac-8009b94c43c1", - "type": "RAW_JSON", - "name": "properties", - "label": "Event details", - "description": "Json value for event details", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1f77c676-5b7e-4105-ad66-f1c3ae12ebbc", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0fedd2c5-1c9c-4e0a-8687-a8ce4dd88378", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Event workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d74023b4-87a8-44d0-84d8-9b2a85018e4b", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "ccb2a7ce-f998-4363-b951-cdf7409b64dc", - "nameSingular": "auditLog", - "namePlural": "auditLogs" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0fedd2c5-1c9c-4e0a-8687-a8ce4dd88378", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "82057d3c-fd1d-4479-a8e3-f18dd9207f3e", - "name": "auditLogs" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bda06197-eea9-4fdd-a9b5-101fb473ba00", - "type": "RAW_JSON", - "name": "context", - "label": "Event context", - "description": "Json object to provide context (user, device, workspace, etc.)", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "850587c2-97aa-42b0-a1f0-e87a1ad98f8c", - "type": "TEXT", - "name": "name", - "label": "Event name", - "description": "Event name/type", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7ff2c10b-db27-44e5-bcaa-8ca3a68e7dae", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cb33c71d-a957-4c29-b9ea-b6eed8ae8eeb", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Event workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "737e4617-4310-4b6d-af50-bb4fb79a10b7", - "type": "TEXT", - "name": "objectMetadataId", - "label": "Object metadata id", - "description": "Object metadata id", - "icon": "IconAbc", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f9aadda1-e30c-4eea-908a-3cea6e5f41cc", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "c81903be-3be2-49af-82b3-d170cd35ac0f", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "viewField", - "namePlural": "viewFields", - "labelSingular": "View Field", - "labelPlural": "View Fields", - "description": "(System) View Fields", - "icon": "IconTag", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "98225cd7-c93e-4f1f-ab9a-36a8b56fbdc9", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjg=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ccf2d3bc-3859-4075-ab3f-4022294f2e30", - "type": "BOOLEAN", - "name": "isVisible", - "label": "Visible", - "description": "View Field visibility", - "icon": "IconEye", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "98225cd7-c93e-4f1f-ab9a-36a8b56fbdc9", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3c5e5a35-731d-4c06-934a-d52bb02bc715", - "type": "NUMBER", - "name": "size", - "label": "Size", - "description": "View Field size", - "icon": "IconEye", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "337d9389-06a9-4cb1-9f2a-76dbb37a7576", - "type": "RELATION", - "name": "view", - "label": "View", - "description": "View Field related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ae731975-39ee-4387-a80c-de94dff0b760", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "c81903be-3be2-49af-82b3-d170cd35ac0f", - "nameSingular": "viewField", - "namePlural": "viewFields" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "337d9389-06a9-4cb1-9f2a-76dbb37a7576", - "name": "view" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c086b30a-0267-4857-9fe0-29a2bbaa8dc8", - "name": "viewFields" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "aafd0a60-9abc-464c-a601-0f4b93643df0", - "type": "NUMBER", - "name": "position", - "label": "Position", - "description": "View Field position", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "48b3cbca-7061-4431-b671-f517178a6fa6", - "type": "UUID", - "name": "viewId", - "label": "View id (foreign key)", - "description": "View Field related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fd19a167-b702-4fe4-b59d-b0bf5b288ccf", - "type": "UUID", - "name": "fieldMetadataId", - "label": "Field Metadata Id", - "description": "View Field target field", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "31ca8ba9-4d75-4b61-ba76-a879e0f230b6", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ba35303a-1451-47fe-8900-80262b90d4c6", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "c6d8d5a8-08ab-4828-8b19-82a9a835685a", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "comment", - "namePlural": "comments", - "labelSingular": "Comment", - "labelPlural": "Comments", - "description": "A comment", - "icon": "IconMessageCircle", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "468e8108-653e-4c9e-ba50-bff7937d89ad", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjc=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7c664574-0bcb-4833-aee1-5a95bad64fbb", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5e0574cf-4695-48f4-aa29-5b755b33102a", - "type": "TEXT", - "name": "body", - "label": "Body", - "description": "Comment body", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a7bc8581-749a-49cf-bf04-3b7bdc0b3f4e", - "type": "UUID", - "name": "authorId", - "label": "Author id (foreign key)", - "description": "Comment author id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2dd9dd34-4b7a-4082-b983-3e5faf37e60c", - "type": "UUID", - "name": "activityId", - "label": "Activity id (foreign key)", - "description": "Comment activity id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ace0311d-6b58-4c34-9e78-3c18ff147408", - "type": "RELATION", - "name": "activity", - "label": "Activity", - "description": "Comment activity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "eaf90876-fac7-448a-906c-7c2b6afcd346", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "c6d8d5a8-08ab-4828-8b19-82a9a835685a", - "nameSingular": "comment", - "namePlural": "comments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ace0311d-6b58-4c34-9e78-3c18ff147408", - "name": "activity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c2a21675-a29d-442a-9f02-84cd93df15ce", - "name": "comments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "468e8108-653e-4c9e-ba50-bff7937d89ad", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fd9fcac5-c853-4fe7-ab1e-18081e4d4517", - "type": "RELATION", - "name": "author", - "label": "Author", - "description": "Comment author", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "017b3808-bc03-4817-8a67-b20770a6a126", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "c6d8d5a8-08ab-4828-8b19-82a9a835685a", - "nameSingular": "comment", - "namePlural": "comments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fd9fcac5-c853-4fe7-ab1e-18081e4d4517", - "name": "author" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c86049ea-6cac-4b7f-a58e-b68b917e4a2b", - "name": "authoredComments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "52a840a8-0d1c-4fdb-bea8-b008c9d3986d", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers", - "labelSingular": "Workspace Member", - "labelPlural": "Workspace Members", - "description": "A workspace member", - "icon": "IconUserCircle", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "b329059f-4b80-4d92-9a2a-4f6373cd8003", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjI0" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6062715e-08e8-4ff5-962d-eed4f992fc61", - "type": "RELATION", - "name": "calendarEventParticipants", - "label": "Calendar Event Participants", - "description": "Calendar Event Participants", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d5ffcbba-0ab9-4f4d-a5e6-15f1e668b04c", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6062715e-08e8-4ff5-962d-eed4f992fc61", - "name": "calendarEventParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fbc9d8eb-c04f-4c86-81ff-d4ca9957d0d4", - "name": "workspaceMember" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8d0cc1c9-ab4a-406c-b35b-1a1b40fd025c", - "type": "TEXT", - "name": "locale", - "label": "Language", - "description": "Preferred language", - "icon": "IconLanguage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'en'", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ace8a324-075e-49a3-92fa-34e07590ec72", - "type": "RELATION", - "name": "timelineActivities", - "label": "Events", - "description": "Events linked to the workspace member", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5b2015dd-3fac-4118-adf5-3cceede873eb", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ace8a324-075e-49a3-92fa-34e07590ec72", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "9fc19fe9-2563-41ac-8c92-2062ff3a0c0c", - "name": "workspaceMember" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "57d6eb4f-c86b-4a50-98ce-fa04c849b1a2", - "type": "RELATION", - "name": "accountOwnerForCompanies", - "label": "Account Owner For Companies", - "description": "Account owner for companies", - "icon": "IconBriefcase", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "97b97e1e-aed0-4d59-997c-13ad9007e037", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "57d6eb4f-c86b-4a50-98ce-fa04c849b1a2", - "name": "accountOwnerForCompanies" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a56f365a-22c8-475d-816b-709f3a19c5fd", - "name": "accountOwner" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4e4050e5-e6fb-4466-b02b-6d12714373b7", - "type": "TEXT", - "name": "avatarUrl", - "label": "Avatar Url", - "description": "Workspace member avatar", - "icon": "IconFileUpload", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2e1b13a4-9ced-4b9d-b9f6-ca274410a933", - "type": "UUID", - "name": "userId", - "label": "User Id", - "description": "Associated User Id", - "icon": "IconCircleUsers", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b329059f-4b80-4d92-9a2a-4f6373cd8003", - "type": "FULL_NAME", - "name": "name", - "label": "Name", - "description": "Workspace member name", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "lastName": "''", - "firstName": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "867aef01-e708-4f56-96b2-11237093a8e6", - "type": "TEXT", - "name": "userEmail", - "label": "User Email", - "description": "Related user email address", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f9d4a27e-1728-44d8-b990-e648d838a35a", - "type": "RELATION", - "name": "authoredActivities", - "label": "Authored activities", - "description": "Activities created by the workspace member", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "fccbbaf8-c653-4e09-8d3e-5652b37d8209", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f9d4a27e-1728-44d8-b990-e648d838a35a", - "name": "authoredActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b31f4c53-a5ee-4939-9804-6964144540ca", - "name": "author" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c6b1b4a1-bad8-4872-b408-aa0ceb668215", - "type": "RELATION", - "name": "blocklist", - "label": "Blocklist", - "description": "Blocklisted handles", - "icon": "IconForbid2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "19b7520c-bc6e-490c-bfab-a3b020315cc4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c6b1b4a1-bad8-4872-b408-aa0ceb668215", - "name": "blocklist" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "60637cd3-24f6-4d9a-9432-a590accbefb9", - "nameSingular": "blocklist", - "namePlural": "blocklists" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "96acdd2a-b7d1-452b-9e58-5c4265691444", - "name": "workspaceMember" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "328d9621-d8aa-4e2d-91c1-e03c621d79a1", - "type": "SELECT", - "name": "timeFormat", - "label": "Time format", - "description": "User's preferred time format", - "icon": "IconClock2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'SYSTEM'", - "options": [ - { - "id": "2ea77a47-9b23-4159-bda8-97bf23a52f61", - "color": "sky", - "label": "System", - "value": "SYSTEM", - "position": 0 - }, - { - "id": "3dc8e58f-c987-4078-b980-d5e2127968e8", - "color": "red", - "label": "24HRS", - "value": "HOUR_24", - "position": 1 - }, - { - "id": "7d24f928-a955-4419-9081-51117eb181e6", - "color": "purple", - "label": "12HRS", - "value": "HOUR_12", - "position": 2 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c00ccd93-ebc7-4744-8cb3-797a752b4627", - "type": "RELATION", - "name": "messageParticipants", - "label": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "7d3faf56-e4bb-45ec-9b75-612ca6e9ae5a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c00ccd93-ebc7-4744-8cb3-797a752b4627", - "name": "messageParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "bc788a8f-8eb2-47bf-a02c-42f7de197ca8", - "name": "workspaceMember" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2fa32c75-6169-449b-bfc2-e1576d5ce5fe", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e2234264-8612-4d1d-bfa8-929cc63bf6fd", - "type": "SELECT", - "name": "dateFormat", - "label": "Date format", - "description": "User's preferred date format", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'SYSTEM'", - "options": [ - { - "id": "c0053f9a-eaa7-44d2-9c0e-73d95715d007", - "color": "turquoise", - "label": "System", - "value": "SYSTEM", - "position": 0 - }, - { - "id": "aacbf232-6f01-48dd-9328-6da7ebea0986", - "color": "red", - "label": "Month First", - "value": "MONTH_FIRST", - "position": 1 - }, - { - "id": "a2b08c2c-7bf4-4730-a4f9-30d122dc5a4b", - "color": "purple", - "label": "Day First", - "value": "DAY_FIRST", - "position": 2 - }, - { - "id": "29de9f62-1eb7-4808-a63e-53d3dc764c9e", - "color": "sky", - "label": "Year First", - "value": "YEAR_FIRST", - "position": 3 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e4c25d9f-10cf-4c33-8c39-7aaac0a98f11", - "type": "RELATION", - "name": "assignedTasks", - "label": "Assigned tasks", - "description": "Tasks assigned to the workspace member", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "8cb075f2-e51c-4684-80f6-cf6af471e82a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e4c25d9f-10cf-4c33-8c39-7aaac0a98f11", - "name": "assignedTasks" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5bcc7e50-73ce-4146-b000-5a336f0e9c40", - "name": "assignee" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2d52a31f-3ad8-4d57-90eb-61142bf58382", - "type": "RELATION", - "name": "assignedActivities", - "label": "Assigned activities", - "description": "Activities assigned to the workspace member", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d5315a70-980f-4c45-9a4f-74779a00fdd3", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2d52a31f-3ad8-4d57-90eb-61142bf58382", - "name": "assignedActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4ec37c9c-be4c-4f52-a441-03a1dfd951db", - "name": "assignee" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e2cdfd71-1c3e-4793-a95d-91df4a3bbe8d", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "104209de-5259-4d74-b14a-f37badf49be9", - "type": "RELATION", - "name": "connectedAccounts", - "label": "Connected accounts", - "description": "Connected accounts", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1ecacc04-e834-421d-bf1b-c765e55a4318", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "104209de-5259-4d74-b14a-f37badf49be9", - "name": "connectedAccounts" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "95bd59b8-8083-4c76-b770-ec40a744138c", - "name": "accountOwner" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "29055851-94dc-4fa9-84d8-295a3d161724", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the workspace member", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a72edc8d-e5e3-4eae-9fd6-4cb0792b18aa", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "29055851-94dc-4fa9-84d8-295a3d161724", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0aaf9f83-9b43-4f15-a187-9c11761b367a", - "name": "workspaceMember" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c86049ea-6cac-4b7f-a58e-b68b917e4a2b", - "type": "RELATION", - "name": "authoredComments", - "label": "Authored comments", - "description": "Authored comments", - "icon": "IconComment", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "017b3808-bc03-4817-8a67-b20770a6a126", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c86049ea-6cac-4b7f-a58e-b68b917e4a2b", - "name": "authoredComments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "c6d8d5a8-08ab-4828-8b19-82a9a835685a", - "nameSingular": "comment", - "namePlural": "comments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fd9fcac5-c853-4fe7-ab1e-18081e4d4517", - "name": "author" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fa2a9e43-03f2-4919-b67d-4b62f5c16758", - "type": "TEXT", - "name": "colorScheme", - "label": "Color Scheme", - "description": "Preferred color scheme", - "icon": "IconColorSwatch", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'Light'", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "82057d3c-fd1d-4479-a8e3-f18dd9207f3e", - "type": "RELATION", - "name": "auditLogs", - "label": "Audit Logs", - "description": "Audit Logs linked to the workspace member", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d74023b4-87a8-44d0-84d8-9b2a85018e4b", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "82057d3c-fd1d-4479-a8e3-f18dd9207f3e", - "name": "auditLogs" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "ccb2a7ce-f998-4363-b951-cdf7409b64dc", - "nameSingular": "auditLog", - "namePlural": "auditLogs" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0fedd2c5-1c9c-4e0a-8687-a8ce4dd88378", - "name": "workspaceMember" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c784bb11-cd83-4392-a14b-4b9028ac4280", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6e622089-670a-4831-964c-f27af03f39c0", - "type": "RELATION", - "name": "authoredAttachments", - "label": "Authored attachments", - "description": "Attachments created by the workspace member", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5b9f08b0-8960-40c4-b6bb-9d3552a24f8d", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6e622089-670a-4831-964c-f27af03f39c0", - "name": "authoredAttachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "85046974-8ab2-456d-a732-64da14715643", - "name": "author" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "43ae7fcb-4e9c-4f67-a9cd-8fe5b2c3991b", - "type": "TEXT", - "name": "timeZone", - "label": "Time zone", - "description": "User time zone", - "icon": "IconTimezone", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'system'", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "person", - "namePlural": "people", - "labelSingular": "Person", - "labelPlural": "People", - "description": "A person", - "icon": "IconUser", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "e1eb21dc-7a5e-41e5-99be-8889a3d5ca15", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjIz" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "130dedfb-f30c-4a70-b2ca-a70e2aa8c291", - "type": "TEXT", - "name": "jobTitle", - "label": "Job Title", - "description": "Contactโ€™s job title", - "icon": "IconBriefcase", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "75210cae-eb24-4932-8f00-ccbce38a3d66", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "608e9b7c-8d7a-409c-88c8-455b72b1bbca", - "type": "TEXT", - "name": "avatarUrl", - "label": "Avatar", - "description": "Contactโ€™s avatar", - "icon": "IconFileUpload", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2748b607-0fbb-42c8-b79f-695921bcb8ed", - "type": "LINKS", - "name": "xLink", - "label": "X", - "description": "Contactโ€™s X/Twitter account", - "icon": "IconBrandX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ee5347ee-390c-42de-bde5-ebaffd2ad0e5", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5fbb29e4-4ee6-4122-b1c3-1a632e2501ef", - "type": "LINKS", - "name": "linkedinLink", - "label": "Linkedin", - "description": "Contactโ€™s Linkedin account", - "icon": "IconBrandLinkedin", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a420a3c3-f245-4ab4-9094-bb4cfb7edc9b", - "type": "TEXT", - "name": "city", - "label": "City", - "description": "Contactโ€™s city", - "icon": "IconMap", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "19f77ace-4b00-4fac-ba7d-8c7a3dde409b", - "type": "RELATION", - "name": "messageParticipants", - "label": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5bb99199-6a3c-4947-b16b-6a90c6097eac", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "19f77ace-4b00-4fac-ba7d-8c7a3dde409b", - "name": "messageParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "362195e4-4dfb-49e1-b25b-fe3ffe7b7f14", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e1eb21dc-7a5e-41e5-99be-8889a3d5ca15", - "type": "FULL_NAME", - "name": "name", - "label": "Name", - "description": "Contactโ€™s name", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "lastName": "''", - "firstName": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3700e772-3bf6-4150-b5ce-f7f00ded863a", - "type": "RELATION", - "name": "timelineActivities", - "label": "Events", - "description": "Events linked to the person", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "680351ba-8759-405d-8fda-90799bf75741", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3700e772-3bf6-4150-b5ce-f7f00ded863a", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0669197c-bc4e-4a44-9cd9-db449bfa380e", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d9594064-5e84-4981-a0f8-0d047322f1e9", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "05863c2c-bcf7-4d88-b0cd-f00335b6854d", - "type": "RELATION", - "name": "pointOfContactForOpportunities", - "label": "POC for Opportunities", - "description": "Point of Contact for Opportunities", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "58a081ed-e5e7-44f8-bae6-99be66b6ac2f", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "05863c2c-bcf7-4d88-b0cd-f00335b6854d", - "name": "pointOfContactForOpportunities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "18ea34ae-f9bc-4240-b65f-46f0d688135f", - "name": "pointOfContact" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cb89d529-55f2-4757-8248-6939aa038363", - "type": "EMAIL", - "name": "email", - "label": "Email", - "description": "Contactโ€™s Email", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ee426b52-f4d3-4b96-a7fc-04d968b66331", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the contact", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "74f62324-bc36-4210-bb88-e0e6e0136c9f", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ee426b52-f4d3-4b96-a7fc-04d968b66331", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4a9e3e27-70b0-4ed7-9edf-9126c1675b22", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c87e6f64-f722-4487-9930-1c6fb67572c1", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the contact", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "450a3266-7706-4593-a458-5897c5f60fc5", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c87e6f64-f722-4487-9930-1c6fb67572c1", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f0748d0d-e6b4-44ea-b957-0c0d81af4627", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fe7c56ff-5531-4abc-a10a-048b01034596", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "538e49cd-f04a-4889-9994-35cacc0754b7", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Contactโ€™s company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2f030298-14c7-48a4-b351-2ec185bb1814", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "538e49cd-f04a-4889-9994-35cacc0754b7", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0943b1b4-3aae-4ebe-8e8e-b1a8640d78d9", - "name": "people" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "aa9f1d0c-ef2c-4bec-849f-aaf3b83a6c3c", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Contactโ€™s company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fd48c551-1309-473d-bb7e-921c577b731b", - "type": "RELATION", - "name": "calendarEventParticipants", - "label": "Calendar Event Participants", - "description": "Calendar Event Participants", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "178c5cfe-cc05-49ec-bedb-eff402da4e8f", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fd48c551-1309-473d-bb7e-921c577b731b", - "name": "calendarEventParticipants" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a2ecf99f-9725-4b20-90df-28ad410f173b", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d7df3544-94ed-439f-94de-86ad7828669a", - "type": "TEXT", - "name": "phone", - "label": "Phone", - "description": "Contactโ€™s phone number", - "icon": "IconPhone", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "82d1a637-3df9-4d59-a412-1cbc1d92baf2", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the contact", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "700c5e52-3e7b-4471-9826-82270c03c37e", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "82d1a637-3df9-4d59-a412-1cbc1d92baf2", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "8ac4df39-f1a0-4221-a605-bd4c229fbc12", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5030bb60-7366-4e7d-8ba4-35c6a6255547", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments linked to the contact.", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0c8f43c1-d325-4a58-99a7-926b1db4e8fc", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5030bb60-7366-4e7d-8ba4-35c6a6255547", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b192eb71-bcfb-46ab-ae88-83a73700ee34", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a53d5e8d-85d9-45de-9f45-d8b4f5b11c3a", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the contact", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e95da71a-7162-4282-8ff7-ea65fea36fe8", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a53d5e8d-85d9-45de-9f45-d8b4f5b11c3a", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "db61f1e6-17d5-4f1d-8c18-8cb5f1108831", - "name": "person" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "148421f0-84b0-4af0-bc54-17a116242b14", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Person record Position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "aeb6b83d-3545-4d48-8281-0d46258a3447", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "apiKey", - "namePlural": "apiKeys", - "labelSingular": "Api Key", - "labelPlural": "Api Keys", - "description": "An api key", - "icon": "IconRobot", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "d9c16cc2-6a42-4ea8-a6be-08f6d9571c14", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjU=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d9c16cc2-6a42-4ea8-a6be-08f6d9571c14", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "ApiKey name", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ac04da17-5ffc-4027-9124-4af02c307d23", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3b213998-ade1-4836-a42f-1dc9b476f8f3", - "type": "DATE_TIME", - "name": "expiresAt", - "label": "Expiration date", - "description": "ApiKey expiration date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a3a6cbc2-f8dd-4ed0-8648-065253f9183e", - "type": "DATE_TIME", - "name": "revokedAt", - "label": "Revocation date", - "description": "ApiKey revocation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "15091a7f-64c4-4011-91b0-a63ed1f298ec", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3003e27d-9fc7-417b-a388-062144884d76", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "activity", - "namePlural": "activities", - "labelSingular": "Activity", - "labelPlural": "Activities", - "description": "An activity", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "c0608084-ab8e-4527-8a27-81f12f43550a", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjE1" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c0608084-ab8e-4527-8a27-81f12f43550a", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Activity title", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b478ec3c-46d5-46b1-9a55-0938c0a7213d", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5d7fa454-c89c-4fdd-ac48-6b119977c8bd", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Activity attachments", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "69539f96-cede-4f76-bd64-84b1182c3427", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5d7fa454-c89c-4fdd-ac48-6b119977c8bd", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a6299818-986d-4358-9a7b-04e6f5e0fd8b", - "name": "activity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "15c8c04b-1d3c-4d40-b405-bfce8d1c46ad", - "type": "UUID", - "name": "authorId", - "label": "Author id (foreign key)", - "description": "Activity author id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fdf929a7-5529-4be3-a7df-aa08d2a23b2c", - "type": "RELATION", - "name": "activityTargets", - "label": "Targets", - "description": "Activity targets", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "938c0b4f-e398-4db6-8893-ad6b609556a9", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fdf929a7-5529-4be3-a7df-aa08d2a23b2c", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "8bcf99e6-2368-4caa-9c5e-e70c46bc6ab7", - "name": "activity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0cbf9212-7130-4a59-9752-c29b00b0e6fd", - "type": "TEXT", - "name": "type", - "label": "Type", - "description": "Activity type", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'Note'", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c2a21675-a29d-442a-9f02-84cd93df15ce", - "type": "RELATION", - "name": "comments", - "label": "Comments", - "description": "Activity comments", - "icon": "IconComment", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "eaf90876-fac7-448a-906c-7c2b6afcd346", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c2a21675-a29d-442a-9f02-84cd93df15ce", - "name": "comments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "c6d8d5a8-08ab-4828-8b19-82a9a835685a", - "nameSingular": "comment", - "namePlural": "comments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ace0311d-6b58-4c34-9e78-3c18ff147408", - "name": "activity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b31f4c53-a5ee-4939-9804-6964144540ca", - "type": "RELATION", - "name": "author", - "label": "Author", - "description": "Activity author", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "fccbbaf8-c653-4e09-8d3e-5652b37d8209", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b31f4c53-a5ee-4939-9804-6964144540ca", - "name": "author" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f9d4a27e-1728-44d8-b990-e648d838a35a", - "name": "authoredActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4ec37c9c-be4c-4f52-a441-03a1dfd951db", - "type": "RELATION", - "name": "assignee", - "label": "Assignee", - "description": "Activity assignee", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d5315a70-980f-4c45-9a4f-74779a00fdd3", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4ec37c9c-be4c-4f52-a441-03a1dfd951db", - "name": "assignee" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2d52a31f-3ad8-4d57-90eb-61142bf58382", - "name": "assignedActivities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e3010b50-0dce-4ca7-8fc6-580601f03a6a", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bd9cb34e-4225-469b-8703-2948ff2a7503", - "type": "TEXT", - "name": "body", - "label": "Body", - "description": "Activity body", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ba97c4a5-7141-4a70-8963-f36e24c8ab09", - "type": "DATE_TIME", - "name": "completedAt", - "label": "Completion Date", - "description": "Activity completion date", - "icon": "IconCheck", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "eddc4e8e-0664-4e28-a46d-87d39efe8b82", - "type": "DATE_TIME", - "name": "reminderAt", - "label": "Reminder Date", - "description": "Activity reminder date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "845efc95-6c7c-4292-a4df-b9d1d159f6b9", - "type": "UUID", - "name": "assigneeId", - "label": "Assignee id (foreign key)", - "description": "Activity assignee id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e50e247e-3db5-468a-802d-04d02b7b0331", - "type": "DATE_TIME", - "name": "dueAt", - "label": "Due Date", - "description": "Activity due date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2a0b35ca-c76e-494f-a03a-c9e9844d2696", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "attachment", - "namePlural": "attachments", - "labelSingular": "Attachment", - "labelPlural": "Attachments", - "description": "An attachment", - "icon": "IconFileImport", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "9d4f5164-457e-4e78-bacd-8633e234153b", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjE5" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f4c14cf5-f007-4e2b-a7b5-f5a77c7c5492", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0913c9cc-c3d4-4fd4-9fc7-b758daa08ba4", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "Attachment task", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "cc228da1-14c7-4c49-a84d-231ba6166f38", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0913c9cc-c3d4-4fd4-9fc7-b758daa08ba4", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "7c3b7305-e7be-4dbf-9e94-ee354e011f63", - "name": "attachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "36f7236a-bfd2-404c-adb6-66b294ca5435", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Attachment company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2be99c6b-02c8-4ca0-b155-dcf7539097b5", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "36f7236a-bfd2-404c-adb6-66b294ca5435", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "16706d44-4042-4998-b5c7-15437e052196", - "name": "attachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b192eb71-bcfb-46ab-ae88-83a73700ee34", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Attachment person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0c8f43c1-d325-4a58-99a7-926b1db4e8fc", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b192eb71-bcfb-46ab-ae88-83a73700ee34", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5030bb60-7366-4e7d-8ba4-35c6a6255547", - "name": "attachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a6299818-986d-4358-9a7b-04e6f5e0fd8b", - "type": "RELATION", - "name": "activity", - "label": "Activity", - "description": "Attachment activity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "69539f96-cede-4f76-bd64-84b1182c3427", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a6299818-986d-4358-9a7b-04e6f5e0fd8b", - "name": "activity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5d7fa454-c89c-4fdd-ac48-6b119977c8bd", - "name": "attachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1d2daa92-3e60-4d45-b3f5-2cdaea3171c8", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9d9f7585-6229-49ec-8657-a0f16608aa18", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Attachment person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bbde9d64-838e-4eea-b947-81fa6f44c76a", - "type": "UUID", - "name": "authorId", - "label": "Author id (foreign key)", - "description": "Attachment author id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f8a0a4ad-a6f5-4eb3-985d-a3134e5449ad", - "type": "RELATION", - "name": "note", - "label": "Note", - "description": "Attachment note", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bb4120f5-5135-4881-97a8-d50e6df2f97e", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f8a0a4ad-a6f5-4eb3-985d-a3134e5449ad", - "name": "note" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "nameSingular": "note", - "namePlural": "notes" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2a2fa9e4-242f-449e-a191-d1937ee4cedc", - "name": "attachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9d4f5164-457e-4e78-bacd-8633e234153b", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "Attachment name", - "icon": "IconFileUpload", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9a3bd188-009f-4790-8f11-7978d420ebad", - "type": "UUID", - "name": "noteId", - "label": "Note id (foreign key)", - "description": "Attachment note id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "dd866825-21ca-4f0b-86e5-9bff38a9daa0", - "type": "TEXT", - "name": "type", - "label": "Type", - "description": "Attachment type", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "855ffc2d-08c3-42e1-bd5a-c8a11fced7dc", - "type": "TEXT", - "name": "fullPath", - "label": "Full path", - "description": "Attachment full path", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b16667c6-6f37-4010-871d-219f840a5b50", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Attachment company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f9a294b9-9def-4e4d-8f2b-8db82b65606c", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "Attachment opportunity id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b240e780-8bf4-4193-b4c4-1eaf8cac31f6", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6d99366d-b55c-4574-a533-709c8a902eef", - "type": "UUID", - "name": "activityId", - "label": "Activity id (foreign key)", - "description": "Attachment activity id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "85046974-8ab2-456d-a732-64da14715643", - "type": "RELATION", - "name": "author", - "label": "Author", - "description": "Attachment author", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5b9f08b0-8960-40c4-b6bb-9d3552a24f8d", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "85046974-8ab2-456d-a732-64da14715643", - "name": "author" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6e622089-670a-4831-964c-f27af03f39c0", - "name": "authoredAttachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b4868b15-ff98-4f36-9f59-1dbf63052bb7", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "Attachment opportunity", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c20ecc99-e48a-4311-b850-8fbf1a7b68ea", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b4868b15-ff98-4f36-9f59-1dbf63052bb7", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "095b38a6-1881-40b8-9849-cb80d19aa295", - "name": "attachments" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "04d74996-9228-47ee-9297-333439d4937e", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "Attachment task id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "activityTarget", - "namePlural": "activityTargets", - "labelSingular": "Activity Target", - "labelPlural": "Activity Targets", - "description": "An activity target", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "58b83d14-5f42-49c8-9048-485fe0259e7b", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEw" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "06fa978e-2d47-42ad-8652-00ad8e4f8c03", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "ActivityTarget company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8bcf99e6-2368-4caa-9c5e-e70c46bc6ab7", - "type": "RELATION", - "name": "activity", - "label": "Activity", - "description": "ActivityTarget activity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "938c0b4f-e398-4db6-8893-ad6b609556a9", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "8bcf99e6-2368-4caa-9c5e-e70c46bc6ab7", - "name": "activity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "96bf92fd-6b8f-40b4-afd6-f90fedc40a1a", - "nameSingular": "activity", - "namePlural": "activities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fdf929a7-5529-4be3-a7df-aa08d2a23b2c", - "name": "activityTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ddc84553-0678-4697-a8c2-06ddbc136cab", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "ActivityTarget opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2105fe76-e9fa-4610-992a-261d0f24722d", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ddc84553-0678-4697-a8c2-06ddbc136cab", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "1bab9225-7390-43d7-a2c5-1d14f918efc0", - "name": "activityTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "242672b2-7f38-4864-8101-b8e2fb4605b0", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "ActivityTarget person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9a9a2055-3de4-46ef-a721-edf60be80cee", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "ActivityTarget opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f0748d0d-e6b4-44ea-b957-0c0d81af4627", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "ActivityTarget person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "450a3266-7706-4593-a458-5897c5f60fc5", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f0748d0d-e6b4-44ea-b957-0c0d81af4627", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c87e6f64-f722-4487-9930-1c6fb67572c1", - "name": "activityTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6cf6997c-82cd-41ae-9178-4e02467afe80", - "type": "UUID", - "name": "activityId", - "label": "Activity id (foreign key)", - "description": "ActivityTarget activity id foreign key", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "caeac93e-8092-4661-9b61-ba0c8b20d4e7", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "331d70b2-5cec-438f-b535-1ac0e59900bc", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6213d5af-e8cd-4e5d-9a60-bab631884ae5", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "ActivityTarget company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a0f5427c-2c97-4457-b87f-a4d145e06952", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "6213d5af-e8cd-4e5d-9a60-bab631884ae5", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "33454d9c-57f7-4639-9120-b024f365d52f", - "name": "activityTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "58b83d14-5f42-49c8-9048-485fe0259e7b", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "816a7154-5111-47fa-9d8d-87ca2dafc521", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "viewFilter", - "namePlural": "viewFilters", - "labelSingular": "View Filter", - "labelPlural": "View Filters", - "description": "(System) View Filters", - "icon": "IconFilterBolt", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "dc8df23b-c648-4781-a329-cc921f104396", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjg=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0fbd44d4-1342-45b9-a688-de8f8b3cfe97", - "type": "TEXT", - "name": "value", - "label": "Value", - "description": "View Filter value", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2ad7121a-e95e-4b14-9d36-387feebaf516", - "type": "UUID", - "name": "viewId", - "label": "View id (foreign key)", - "description": "View Filter related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "07eed82c-b376-4cd6-a04a-042fde8d3058", - "type": "TEXT", - "name": "displayValue", - "label": "Display Value", - "description": "View Filter Display Value", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "89eaa2a3-6302-46d8-b06d-6640c336c489", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0f9c4eb8-501d-4861-827a-5ef45a01eba9", - "type": "RELATION", - "name": "view", - "label": "View", - "description": "View Filter related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "7c42db51-2fcc-44b6-9a80-787b1967e69e", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "816a7154-5111-47fa-9d8d-87ca2dafc521", - "nameSingular": "viewFilter", - "namePlural": "viewFilters" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0f9c4eb8-501d-4861-827a-5ef45a01eba9", - "name": "view" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4f92f2f0-9204-4f23-afdc-894829664668", - "name": "viewFilters" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "dc8df23b-c648-4781-a329-cc921f104396", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4f9a929d-5701-4140-b509-8376c8853c2b", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "dfaccfbb-668f-42aa-9e9f-0dfefa4a7e94", - "type": "TEXT", - "name": "operand", - "label": "Operand", - "description": "View Filter operand", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'Contains'", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "169eeb4d-f3e8-46f4-b0a3-7404bc1994cb", - "type": "UUID", - "name": "fieldMetadataId", - "label": "Field Metadata Id", - "description": "View Filter target field", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "718779fd-d87d-4b99-8f6c-3042a6bb03a3", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "viewSort", - "namePlural": "viewSorts", - "labelSingular": "View Sort", - "labelPlural": "View Sorts", - "description": "(System) View Sorts", - "icon": "IconArrowsSort", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "ef4449f6-3cb3-4538-9b93-4bed0060873e", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjY=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "da9e216d-b9ac-44de-a22f-ae579d76cd44", - "type": "TEXT", - "name": "direction", - "label": "Direction", - "description": "View Sort direction", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'asc'", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7a4f3e54-7c7d-4fc6-8f12-a4c7f0d0d731", - "type": "UUID", - "name": "viewId", - "label": "View id (foreign key)", - "description": "View Sort related view id foreign key", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ef4449f6-3cb3-4538-9b93-4bed0060873e", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a021c847-07b3-4d3c-af30-b3330ce58e1b", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "11985659-99a1-4f03-9a95-c5d610acc8e9", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2c09d04d-007c-4652-9c90-c2cfa4696145", - "type": "RELATION", - "name": "view", - "label": "View", - "description": "View Sort related view", - "icon": "IconLayoutCollage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "fcf27acc-a651-4ac2-9f99-aba306756209", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "718779fd-d87d-4b99-8f6c-3042a6bb03a3", - "nameSingular": "viewSort", - "namePlural": "viewSorts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2c09d04d-007c-4652-9c90-c2cfa4696145", - "name": "view" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "nameSingular": "view", - "namePlural": "views" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5969cfdb-bf30-4a34-9b52-11b38945bbd0", - "name": "viewSorts" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "46007b88-46a1-4746-a25f-82151f01525e", - "type": "UUID", - "name": "fieldMetadataId", - "label": "Field Metadata Id", - "description": "View Sort target field", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "company", - "namePlural": "companies", - "labelSingular": "Company", - "labelPlural": "Companies", - "description": "A company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "9e123592-cd2b-471c-8143-3cc0b46089ef", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjIy" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a56f365a-22c8-475d-816b-709f3a19c5fd", - "type": "RELATION", - "name": "accountOwner", - "label": "Account Owner", - "description": "Your team member responsible for managing the company account", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "97b97e1e-aed0-4d59-997c-13ad9007e037", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a56f365a-22c8-475d-816b-709f3a19c5fd", - "name": "accountOwner" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "57d6eb4f-c86b-4a50-98ce-fa04c849b1a2", - "name": "accountOwnerForCompanies" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6c4a0327-d84f-416a-8491-269a74254437", - "type": "BOOLEAN", - "name": "idealCustomerProfile", - "label": "ICP", - "description": "Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you", - "icon": "IconTarget", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": false, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5dcef112-ce1b-46c1-a33a-4d1394628c34", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "277d8939-1ead-4cdb-a560-854644219779", - "type": "MULTI_SELECT", - "name": "testMultiSelect", - "label": "Test Multi Select", - "description": "Test Multi Select", - "icon": "IconSelect", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f207dd14-f05e-4f29-b222-8993d4680f31", - "type": "RAW_JSON", - "name": "testRawJson", - "label": "Test Raw Json", - "description": "Json value for event details", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "81db846a-a2f9-4b31-8931-81fac5cdd1b6", - "type": "RATING", - "name": "testRating", - "label": "Rating", - "description": "Rating value", - "icon": "IconListDetails", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b295267e-e066-4eb1-98ab-50a9d3004394", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the company", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ac6788b1-952c-4376-bafd-66ea5a031398", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b295267e-e066-4eb1-98ab-50a9d3004394", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d89d8a7f-6a14-4fc8-96ca-2966632a1ca4", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "33454d9c-57f7-4639-9120-b024f365d52f", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the company", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a0f5427c-2c97-4457-b87f-a4d145e06952", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "33454d9c-57f7-4639-9120-b024f365d52f", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6213d5af-e8cd-4e5d-9a60-bab631884ae5", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "531f05f1-2c54-4f41-a569-eaac4d58a4ae", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Company record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b2304609-4b9c-40f9-b1d6-21c0974e636e", - "type": "LINKS", - "name": "xLink", - "label": "X", - "description": "The company Twitter/X account", - "icon": "IconBrandX", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "02a1dce3-e661-48d8-868b-83c6e6e79a35", - "type": "LINKS", - "name": "domainName", - "label": "Domain Name", - "description": "The company website URL. We use this url to fetch the company icon", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "81214e8d-e79d-420b-bb2d-8e1f0965b4a6", - "type": "UUID", - "name": "accountOwnerId", - "label": "Account Owner id (foreign key)", - "description": "Your team member responsible for managing the company account id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "92fa0702-e680-4042-aa65-ddf9721030b4", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a0df43b7-d926-44a2-ba12-252866607207", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the company", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "632aaba8-f213-4353-95a5-c090168c3ad7", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a0df43b7-d926-44a2-ba12-252866607207", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "4fa60a42-bd0d-462c-b05d-d85f96b00458", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f39f1db9-3d7f-46d3-aa0c-4cae44352407", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the company", - "icon": "IconIconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1e93ed03-91a5-4ad4-bca7-c6a637551289", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "f39f1db9-3d7f-46d3-aa0c-4cae44352407", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0224c08b-2c2e-474f-8360-dafad378cf62", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1a843af7-219d-460b-9869-b2d89479e42a", - "type": "CURRENCY", - "name": "annualRecurringRevenue", - "label": "ARR", - "description": "Annual Recurring Revenue: The actual or estimated annual revenue of the company", - "icon": "IconMoneybag", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "amountMicros": null, - "currencyCode": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "16706d44-4042-4998-b5c7-15437e052196", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments linked to the company", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2be99c6b-02c8-4ca0-b155-dcf7539097b5", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "16706d44-4042-4998-b5c7-15437e052196", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "36f7236a-bfd2-404c-adb6-66b294ca5435", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9e123592-cd2b-471c-8143-3cc0b46089ef", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "The company name", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "76878ce1-1d00-4e86-8694-58b66bbb5df0", - "type": "LINKS", - "name": "linkedinLink", - "label": "Linkedin", - "description": "The company Linkedin account", - "icon": "IconBrandLinkedin", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "primaryLinkUrl": "''", - "secondaryLinks": null, - "primaryLinkLabel": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bcaf1d51-a492-48db-ab86-35b8ea496364", - "type": "ADDRESS", - "name": "address", - "label": "Address", - "description": "Address of the company", - "icon": "IconMap", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "addressLat": null, - "addressLng": null, - "addressCity": "''", - "addressState": "''", - "addressCountry": "''", - "addressStreet1": "''", - "addressStreet2": "''", - "addressPostcode": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "74bf3aba-450e-48f9-987a-60662929e768", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the company", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1670824b-e097-4afc-8401-feab7f9af0d4", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "74bf3aba-450e-48f9-987a-60662929e768", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5a0243d0-051b-4f30-b0d2-da66b3b8eefe", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "12a6505b-8d0b-4f02-ba94-742d7cc0ac8d", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7ecd1240-7300-4e9e-a9d2-02b489e160b9", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0943b1b4-3aae-4ebe-8e8e-b1a8640d78d9", - "type": "RELATION", - "name": "people", - "label": "People", - "description": "People linked to the company.", - "icon": "IconUsers", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2f030298-14c7-48a4-b351-2ec185bb1814", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0943b1b4-3aae-4ebe-8e8e-b1a8640d78d9", - "name": "people" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "538e49cd-f04a-4889-9994-35cacc0754b7", - "name": "company" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0f4348ce-6621-4af6-b557-f03308a03101", - "type": "NUMBER", - "name": "employees", - "label": "Employees", - "description": "Number of employees in the company", - "icon": "IconUsers", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "34aec238-a534-46e7-be64-d0680a12c8ec", - "type": "RELATION", - "name": "opportunities", - "label": "Opportunities", - "description": "Opportunities linked to the company.", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1ebadb76-46e6-4c57-b24f-441acecbd2d9", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "34aec238-a534-46e7-be64-d0680a12c8ec", - "name": "opportunities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "727ec83b-93b7-4e6b-be22-6f00637ec3f5", - "name": "company" - } - }, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts", - "labelSingular": "Connected Account", - "labelPlural": "Connected Accounts", - "description": "A connected account", - "icon": "IconAt", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "d46591de-57c3-46ae-aca1-3d5cf2e39984", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d7c1698d-a73b-45f4-9bb1-b6b0a4f38fdb", - "type": "DATE_TIME", - "name": "authFailedAt", - "label": "Auth failed at", - "description": "Auth failed at", - "icon": "IconX", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3bf6ad9c-0441-4b8f-8dd0-12d93f83b67a", - "type": "RELATION", - "name": "messageChannels", - "label": "Message Channels", - "description": "Message Channels", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "21bbef75-8acf-48bf-80aa-1d26d50aea22", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "3bf6ad9c-0441-4b8f-8dd0-12d93f83b67a", - "name": "messageChannels" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "311ea123-5b30-4637-ae39-3e639e780c83", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d288fd3a-8fb0-493d-bec3-31a2c4a7d366", - "name": "connectedAccount" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "98853a40-3d9b-42d4-942d-bc08c4d3a520", - "type": "TEXT", - "name": "provider", - "label": "provider", - "description": "The account provider", - "icon": "IconSettings", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d46591de-57c3-46ae-aca1-3d5cf2e39984", - "type": "TEXT", - "name": "handle", - "label": "handle", - "description": "The account handle (email, username, phone number, etc.)", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ac09a2a9-d4ec-4293-b595-e5d684fd776c", - "type": "TEXT", - "name": "lastSyncHistoryId", - "label": "Last sync history ID", - "description": "Last sync history ID", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bda628b5-29a7-41fc-8f50-4bc75902adc4", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c27f1d98-24b4-4776-a49e-ca104eea9aaf", - "type": "TEXT", - "name": "accessToken", - "label": "Access Token", - "description": "Messaging provider access token", - "icon": "IconKey", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e2ba34b6-e188-4b2b-bbf9-0625f33154e1", - "type": "TEXT", - "name": "refreshToken", - "label": "Refresh Token", - "description": "Messaging provider refresh token", - "icon": "IconKey", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bb10e69d-f049-4d97-84f4-09bce29cd401", - "type": "RELATION", - "name": "calendarChannels", - "label": "Calendar Channels", - "description": "Calendar Channels", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "b6b75323-8790-4b3e-8798-e0af646bb9aa", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "bb10e69d-f049-4d97-84f4-09bce29cd401", - "name": "calendarChannels" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0e285964-d858-48bc-98ab-b8c6b1bd5d0b", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ceaf8f8e-297a-418b-a652-01f3eeb5c562", - "name": "connectedAccount" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "48006d4c-0d9c-4099-9ab8-812d0e522faf", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ebc20c3a-157e-46a0-84c7-b0a4a93ead20", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "15512b35-a65c-4f87-8c98-0b0a1b6f92fe", - "type": "UUID", - "name": "accountOwnerId", - "label": "Account Owner id (foreign key)", - "description": "Account Owner id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "95bd59b8-8083-4c76-b770-ec40a744138c", - "type": "RELATION", - "name": "accountOwner", - "label": "Account Owner", - "description": "Account Owner", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1ecacc04-e834-421d-bf1b-c765e55a4318", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "95bd59b8-8083-4c76-b770-ec40a744138c", - "name": "accountOwner" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "104209de-5259-4d74-b14a-f37badf49be9", - "name": "connectedAccounts" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "27048fa7-94eb-4ca0-8479-096a6b990e0f", - "type": "TEXT", - "name": "handleAliases", - "label": "Handle Aliases", - "description": "Handle Aliases", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "60637cd3-24f6-4d9a-9432-a590accbefb9", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "blocklist", - "namePlural": "blocklists", - "labelSingular": "Blocklist", - "labelPlural": "Blocklists", - "description": "Blocklist", - "icon": "IconForbid2", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "67c4f9b6-f1b6-48d0-a502-71aef617fed2", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjU=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6d5237ae-e410-4701-ae0c-8d73bf83c49f", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "67c4f9b6-f1b6-48d0-a502-71aef617fed2", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "96acdd2a-b7d1-452b-9e58-5c4265691444", - "type": "RELATION", - "name": "workspaceMember", - "label": "WorkspaceMember", - "description": "WorkspaceMember", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "19b7520c-bc6e-490c-bfab-a3b020315cc4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "60637cd3-24f6-4d9a-9432-a590accbefb9", - "nameSingular": "blocklist", - "namePlural": "blocklists" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "96acdd2a-b7d1-452b-9e58-5c4265691444", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c6b1b4a1-bad8-4872-b408-aa0ceb668215", - "name": "blocklist" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9b5ffcde-f21d-43c9-9d8c-ff718fe8c4cb", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "616690bd-fdca-483b-9b1d-e85642f770b4", - "type": "UUID", - "name": "workspaceMemberId", - "label": "WorkspaceMember id (foreign key)", - "description": "WorkspaceMember id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "697ba30c-5088-4af7-b7c4-39249c3df401", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "taskTarget", - "namePlural": "taskTargets", - "labelSingular": "Task Target", - "labelPlural": "Task Targets", - "description": "An task target", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "f9109154-65ca-42d5-b21c-1251790f60f8", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEw" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "807cfd9f-4081-4027-b646-cf66d81aa8c6", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "TaskTarget opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "457627a4-8e4f-4720-80b4-b8c47a49a1d7", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "807cfd9f-4081-4027-b646-cf66d81aa8c6", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "100f9c10-11c4-4fee-963a-f98a0e42d05d", - "name": "taskTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "db61f1e6-17d5-4f1d-8c18-8cb5f1108831", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "TaskTarget person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e95da71a-7162-4282-8ff7-ea65fea36fe8", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "db61f1e6-17d5-4f1d-8c18-8cb5f1108831", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a53d5e8d-85d9-45de-9f45-d8b4f5b11c3a", - "name": "taskTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f9109154-65ca-42d5-b21c-1251790f60f8", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e5958bbf-0743-46a4-9222-cefbaa0bd45f", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c84f3d16-2914-4861-8097-d4c58aa7e60e", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "03f631d8-6e86-49c0-9197-dab8df407176", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "TaskTarget person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0f37463a-4a08-44b6-87b6-8175ffa6bff0", - "type": "RELATION", - "name": "task", - "label": "Task", - "description": "TaskTarget task", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1cc7a6b5-66d2-40dc-aa08-4b1a252e3ae3", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0f37463a-4a08-44b6-87b6-8175ffa6bff0", - "name": "task" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "157bef1e-50c2-4c2a-bc48-a5bc790c0f08", - "name": "taskTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d89d8a7f-6a14-4fc8-96ca-2966632a1ca4", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "TaskTarget company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ac6788b1-952c-4376-bafd-66ea5a031398", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d89d8a7f-6a14-4fc8-96ca-2966632a1ca4", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b295267e-e066-4eb1-98ab-50a9d3004394", - "name": "taskTargets" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e5c3fbc8-1d4a-4474-a06b-001868038a82", - "type": "UUID", - "name": "taskId", - "label": "Task id (foreign key)", - "description": "TaskTarget task id foreign key", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e387828b-ddb0-4991-86bb-9f37149cf20c", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "TaskTarget opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1f6a22b6-d296-43ed-bdeb-1303d334aa8c", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "TaskTarget company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants", - "labelSingular": "Calendar event participant", - "labelPlural": "Calendar event participants", - "description": "Calendar event participants", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "a05d5847-6d10-4c04-9c9c-e1b6c239ed0f", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEy" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f50861f2-6921-4c3a-8218-930fa98dcb36", - "type": "UUID", - "name": "calendarEventId", - "label": "Event ID id (foreign key)", - "description": "Event ID id foreign key", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "13af93a8-ad97-477a-992d-be998d44f2f3", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Workspace Member id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3f1d464a-8f3a-42a3-98e3-f314f906d437", - "type": "SELECT", - "name": "responseStatus", - "label": "Response Status", - "description": "Response Status", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'NEEDS_ACTION'", - "options": [ - { - "id": "83748f1c-0fe1-4b92-929f-1adef17c1041", - "color": "orange", - "label": "Needs Action", - "value": "NEEDS_ACTION", - "position": 0 - }, - { - "id": "3b770f03-26a4-4b08-9eee-d2e7cf6fe3c9", - "color": "red", - "label": "Declined", - "value": "DECLINED", - "position": 1 - }, - { - "id": "86b4d0ed-a858-42df-bd9b-31ef0448ef68", - "color": "yellow", - "label": "Tentative", - "value": "TENTATIVE", - "position": 2 - }, - { - "id": "842f49df-916c-4119-86fe-f6332bf6aef2", - "color": "green", - "label": "Accepted", - "value": "ACCEPTED", - "position": 3 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ffaf23b6-1c4c-4073-9c66-2e213764dc86", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "78c18d2c-6316-45f4-983a-17505a4991f2", - "type": "BOOLEAN", - "name": "isOrganizer", - "label": "Is Organizer", - "description": "Is Organizer", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": false, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a2ecf99f-9725-4b20-90df-28ad410f173b", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "178c5cfe-cc05-49ec-bedb-eff402da4e8f", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "a2ecf99f-9725-4b20-90df-28ad410f173b", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fd48c551-1309-473d-bb7e-921c577b731b", - "name": "calendarEventParticipants" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4071dc84-8e86-4174-92c8-d201cbe00587", - "type": "TEXT", - "name": "displayName", - "label": "Display Name", - "description": "Display Name", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "07dcc4ef-0f27-4fd3-bfaf-0f931dfaba97", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b04775e2-53a3-4f62-a2ab-858f2a456fa7", - "type": "RELATION", - "name": "calendarEvent", - "label": "Event ID", - "description": "Event ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "e02ea7b1-1d5a-481b-ab71-3c94ab3f9bf0", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b04775e2-53a3-4f62-a2ab-858f2a456fa7", - "name": "calendarEvent" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d2834e90-eecc-4528-bab3-ad005effd6f2", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "eb3a27fb-9cb8-4017-b896-e52eaf801dc2", - "name": "calendarEventParticipants" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fbc9d8eb-c04f-4c86-81ff-d4ca9957d0d4", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Workspace Member", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d5ffcbba-0ab9-4f4d-a5e6-15f1e668b04c", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "53743ffb-932c-43ec-b624-f5119ec46808", - "nameSingular": "calendarEventParticipant", - "namePlural": "calendarEventParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fbc9d8eb-c04f-4c86-81ff-d4ca9957d0d4", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "6062715e-08e8-4ff5-962d-eed4f992fc61", - "name": "calendarEventParticipants" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cdb9b9ba-ac5c-4806-a017-8ca8b250fc50", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0178d7b0-59e1-48d2-bc62-4138e6d28b60", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a05d5847-6d10-4c04-9c9c-e1b6c239ed0f", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconMail", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "4fed9657-e68b-4856-8e6d-a1c860d16242", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations", - "labelSingular": "Calendar Channel Event Association", - "labelPlural": "Calendar Channel Event Associations", - "description": "Calendar Channel Event Associations", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "277a098a-129a-4c86-b467-6738edc923e6", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjc=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3ff32421-dddd-49d6-bce2-51517a50e621", - "type": "TEXT", - "name": "eventExternalId", - "label": "Event external ID", - "description": "Event external ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cae3d473-1e5b-4963-a88d-8fd2ef8090dc", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "18cea1c1-f521-4c41-b694-729756931795", - "type": "RELATION", - "name": "calendarEvent", - "label": "Event ID", - "description": "Event ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "0f9d244b-e9c6-44af-88f4-9ce798d50bf8", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4fed9657-e68b-4856-8e6d-a1c860d16242", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "18cea1c1-f521-4c41-b694-729756931795", - "name": "calendarEvent" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "d2834e90-eecc-4528-bab3-ad005effd6f2", - "nameSingular": "calendarEvent", - "namePlural": "calendarEvents" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fe7dcb62-099f-4ad1-af7e-a74713f6159d", - "name": "calendarChannelEventAssociations" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4ddb8993-4601-4743-b042-8dab9784a405", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "277a098a-129a-4c86-b467-6738edc923e6", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "567e7a03-352b-4ba6-9fd8-c173a72d8465", - "type": "UUID", - "name": "calendarChannelId", - "label": "Channel ID id (foreign key)", - "description": "Channel ID id foreign key", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d3039865-07b4-4114-bd78-18aa0be2a93b", - "type": "RELATION", - "name": "calendarChannel", - "label": "Channel ID", - "description": "Channel ID", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bf0f695a-08cd-4767-9a83-4fd09f617793", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4fed9657-e68b-4856-8e6d-a1c860d16242", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d3039865-07b4-4114-bd78-18aa0be2a93b", - "name": "calendarChannel" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0e285964-d858-48bc-98ab-b8c6b1bd5d0b", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "9867ad34-df58-4ad0-a459-cc283990b5e5", - "name": "calendarChannelEventAssociations" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "461f9bbb-92b0-4070-a7d2-7f029bae5cff", - "type": "UUID", - "name": "calendarEventId", - "label": "Event ID id (foreign key)", - "description": "Event ID id foreign key", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "note", - "namePlural": "notes", - "labelSingular": "Note", - "labelPlural": "Notes", - "description": "A note", - "icon": "IconNotes", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "e031b434-6370-484a-a88e-c9c526abde5d", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjk=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2b3caa88-203b-460c-be49-88db3b45e18d", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Note record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b66379fc-ac94-4823-b759-aa940fde9c73", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the note.", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "7ec36219-a377-4aea-98be-7954590f8a32", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "b66379fc-ac94-4823-b759-aa940fde9c73", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "bc0e2a25-4e13-4751-a79a-2d264582ef9a", - "name": "note" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "04794a4e-35c3-46a9-8bf3-8ba1c0324f0b", - "type": "RELATION", - "name": "noteTargets", - "label": "Targets", - "description": "Note targets", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "663e9842-8b92-451a-bf73-12a886ff8b05", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "04794a4e-35c3-46a9-8bf3-8ba1c0324f0b", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "87334d50-0c5d-4327-a8c5-3db6bc28c1ea", - "name": "note" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2a2fa9e4-242f-449e-a191-d1937ee4cedc", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Note attachments", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bb4120f5-5135-4881-97a8-d50e6df2f97e", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4cd6194a-093e-4c5d-9ff2-218970b01e3c", - "nameSingular": "note", - "namePlural": "notes" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2a2fa9e4-242f-449e-a191-d1937ee4cedc", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "f8a0a4ad-a6f5-4eb3-985d-a3134e5449ad", - "name": "note" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e031b434-6370-484a-a88e-c9c526abde5d", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Note title", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0c70e522-0571-4fca-8e4e-11c856819aeb", - "type": "RICH_TEXT", - "name": "body", - "label": "Body", - "description": "Note body", - "icon": "IconFilePencil", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "71958224-6de5-4998-82fc-8e85a3b247d6", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0121cd64-663f-4856-b9fc-97973a7e5ee5", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6de21035-5574-471c-80d9-4fb72375ff30", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0d8481f5-5c58-4604-9283-7a5780eab671", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "task", - "namePlural": "tasks", - "labelSingular": "Task", - "labelPlural": "Tasks", - "description": "A task", - "icon": "IconCheckbox", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "d55d2259-62d5-4738-ab6c-07a7293ccb1d", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "54f90efb-a0f4-4adb-9752-58593520ba14", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "671afe20-9fe1-41f7-9ea1-37a41b3f14ea", - "type": "SELECT", - "name": "status", - "label": "Status", - "description": "Task status", - "icon": "IconCheck", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'TODO'", - "options": [ - { - "id": "dedd046f-fc42-4f12-b791-0eb51ca8fa87", - "color": "sky", - "label": "To do", - "value": "TODO", - "position": 0 - }, - { - "id": "849a02b8-c8d4-4249-9b18-4f44a226b8a1", - "color": "purple", - "label": "In progress", - "value": "IN_PROGESS", - "position": 1 - }, - { - "id": "ff6527b5-06af-4e84-808f-bd8148525341", - "color": "green", - "label": "Done", - "value": "DONE", - "position": 1 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "975e6a19-d90c-45dc-9bb0-ffc57f4e1950", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the task.", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "87c0082f-5411-4202-97cd-fc1d9112fa7a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "975e6a19-d90c-45dc-9bb0-ffc57f4e1950", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e48eeafe-43d8-4abc-95c8-6e7a6a56a7c9", - "name": "task" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "91359011-baa5-43a5-9fc2-d46c55c01a50", - "type": "UUID", - "name": "assigneeId", - "label": "Assignee id (foreign key)", - "description": "Task assignee id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "200825f9-f61c-486d-bafc-c89df63bf661", - "type": "DATE_TIME", - "name": "dueAt", - "label": "Due Date", - "description": "Task due date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "811501e4-0b89-41bb-8571-95bcc3875491", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "61f19623-4e85-4710-867c-a5cc500fb3bc", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d34f8907-4850-452f-ae0f-2f4e678b74cc", - "type": "RICH_TEXT", - "name": "body", - "label": "Body", - "description": "Task body", - "icon": "IconFilePencil", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7c3b7305-e7be-4dbf-9e94-ee354e011f63", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Task attachments", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "cc228da1-14c7-4c49-a84d-231ba6166f38", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "7c3b7305-e7be-4dbf-9e94-ee354e011f63", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0913c9cc-c3d4-4fd4-9fc7-b758daa08ba4", - "name": "task" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ca0bf40d-2ddf-494f-bf1b-754f7c3f9c33", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4628c7ec-ba15-4567-b386-10d32e338eb7", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Task record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "157bef1e-50c2-4c2a-bc48-a5bc790c0f08", - "type": "RELATION", - "name": "taskTargets", - "label": "Targets", - "description": "Task targets", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1cc7a6b5-66d2-40dc-aa08-4b1a252e3ae3", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "157bef1e-50c2-4c2a-bc48-a5bc790c0f08", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0f37463a-4a08-44b6-87b6-8175ffa6bff0", - "name": "task" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5bcc7e50-73ce-4146-b000-5a336f0e9c40", - "type": "RELATION", - "name": "assignee", - "label": "Assignee", - "description": "Task assignee", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "8cb075f2-e51c-4684-80f6-cf6af471e82a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4601f72c-580d-4e64-8004-4864f5e60da7", - "nameSingular": "task", - "namePlural": "tasks" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5bcc7e50-73ce-4146-b000-5a336f0e9c40", - "name": "assignee" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "e4c25d9f-10cf-4c33-8c39-7aaac0a98f11", - "name": "assignedTasks" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d55d2259-62d5-4738-ab6c-07a7293ccb1d", - "type": "TEXT", - "name": "title", - "label": "Title", - "description": "Task title", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "favorite", - "namePlural": "favorites", - "labelSingular": "Favorite", - "labelPlural": "Favorites", - "description": "A favorite", - "icon": "IconHeart", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "283bbd7b-1828-47c0-ac12-59b840904057", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8028b960-41fe-45a5-bd88-ed41e3ec9f55", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Favorite workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4a9e3e27-70b0-4ed7-9edf-9126c1675b22", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Favorite person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "74f62324-bc36-4210-bb88-e0e6e0136c9f", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4a9e3e27-70b0-4ed7-9edf-9126c1675b22", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ee426b52-f4d3-4b96-a7fc-04d968b66331", - "name": "favorites" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2a725662-fe1a-44e8-af06-2ae21c9ae0c2", - "type": "RELATION", - "name": "opportunity", - "label": "Opportunity", - "description": "Favorite opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2f07395e-a114-465c-a3a2-9c6b990d3dca", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2a725662-fe1a-44e8-af06-2ae21c9ae0c2", - "name": "opportunity" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2e93a9a9-774b-43fd-8338-d54c29b8704c", - "name": "favorites" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bdc61888-3791-42db-8276-1e91d7f81054", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "283bbd7b-1828-47c0-ac12-59b840904057", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0aaf9f83-9b43-4f15-a187-9c11761b367a", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Favorite workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "a72edc8d-e5e3-4eae-9fd6-4cb0792b18aa", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "0aaf9f83-9b43-4f15-a187-9c11761b367a", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "29055851-94dc-4fa9-84d8-295a3d161724", - "name": "favorites" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4fa60a42-bd0d-462c-b05d-d85f96b00458", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Favorite company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "632aaba8-f213-4353-95a5-c090168c3ad7", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4fa60a42-bd0d-462c-b05d-d85f96b00458", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "a0df43b7-d926-44a2-ba12-252866607207", - "name": "favorites" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "539da33b-6863-41b5-9640-555cd190abf6", - "type": "NUMBER", - "name": "position", - "label": "Position", - "description": "Favorite position", - "icon": "IconList", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0d36e64d-b3aa-43a2-a8d5-26639e695a3c", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Favorite person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f3f3b22c-cd38-4f87-bbf1-333b2661382f", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9cdaf83f-8f2b-4745-9f5e-ed7e295261ab", - "type": "UUID", - "name": "opportunityId", - "label": "Opportunity id (foreign key)", - "description": "Favorite opportunity id foreign key", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2a2e1910-5dd3-4f52-b164-b05117b51e43", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Favorite company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "3babbb57-d5c5-40e3-8c90-4623c39861f4", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "webhook", - "namePlural": "webhooks", - "labelSingular": "Webhook", - "labelPlural": "Webhooks", - "description": "A webhook", - "icon": "IconRobot", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "46a319ab-9265-4f31-914a-b361849a7c93", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjU=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4f333e42-291b-49c0-8e34-a5011899ad64", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "46a319ab-9265-4f31-914a-b361849a7c93", - "type": "TEXT", - "name": "targetUrl", - "label": "Target Url", - "description": "Webhook target url", - "icon": "IconLink", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d5763abd-3ab0-460a-8ce5-f7c69286bdce", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bf449824-9fba-40d3-85b5-b05101952339", - "type": "TEXT", - "name": "description", - "label": "Description", - "description": null, - "icon": "IconInfo", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "03d0aab2-d561-4177-9894-9368c5e7fcaf", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "dfa8f17b-8277-47b8-9be0-ccf18d40fa7e", - "type": "TEXT", - "name": "operation", - "label": "Operation", - "description": "Webhook operation", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "311ea123-5b30-4637-ae39-3e639e780c83", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "messageChannel", - "namePlural": "messageChannels", - "labelSingular": "Message Channel", - "labelPlural": "Message Channels", - "description": "Message Channels", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "fd617e16-7acc-445c-8e47-bae3df663831", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjE5" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "562e521e-c5d5-4ff8-a9c9-1970032b7d31", - "type": "DATE_TIME", - "name": "syncStageStartedAt", - "label": "Sync stage started at", - "description": "Sync stage started at", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fd617e16-7acc-445c-8e47-bae3df663831", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "25a76025-cde8-4ced-91c9-83c053d14384", - "type": "SELECT", - "name": "visibility", - "label": "Visibility", - "description": "Visibility", - "icon": "IconEyeglass", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'SHARE_EVERYTHING'", - "options": [ - { - "id": "29cb9455-90fd-4eff-9dcf-f335b86ad0e2", - "color": "green", - "label": "Metadata", - "value": "METADATA", - "position": 0 - }, - { - "id": "696eef0c-758e-4db3-a897-e8724f3bbb91", - "color": "blue", - "label": "Subject", - "value": "SUBJECT", - "position": 1 - }, - { - "id": "8a2a24ed-dfb4-4dca-b92f-398b9239ec81", - "color": "orange", - "label": "Share Everything", - "value": "SHARE_EVERYTHING", - "position": 2 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3650c95e-adc9-4e10-af9b-f271e3011175", - "type": "TEXT", - "name": "syncCursor", - "label": "Last sync cursor", - "description": "Last sync cursor", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4028ab71-03ae-4743-81fa-53e0fc802839", - "type": "SELECT", - "name": "contactAutoCreationPolicy", - "label": "Contact auto creation policy", - "description": "Automatically create People records when receiving or sending emails", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'SENT'", - "options": [ - { - "id": "db68b799-8701-4d82-8496-0dc007f0f352", - "color": "green", - "label": "Sent and Received", - "value": "SENT_AND_RECEIVED", - "position": 0 - }, - { - "id": "4e01422b-7a3e-4dc5-a3f7-8ffe8a0ce561", - "color": "blue", - "label": "Sent", - "value": "SENT", - "position": 1 - }, - { - "id": "41656b1a-4c84-44b1-8b23-44740840b68e", - "color": "red", - "label": "None", - "value": "NONE", - "position": 2 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0f338b41-0db1-45ae-bd58-7db0f1623d73", - "type": "SELECT", - "name": "syncStage", - "label": "Sync stage", - "description": "Sync stage", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'FULL_MESSAGE_LIST_FETCH_PENDING'", - "options": [ - { - "id": "5d0c8e59-584c-47d4-818b-6b28d6aa6c63", - "color": "blue", - "label": "Full messages list fetch pending", - "value": "FULL_MESSAGE_LIST_FETCH_PENDING", - "position": 0 - }, - { - "id": "460f32ef-f8fd-4744-bb0d-e78991e3ad43", - "color": "blue", - "label": "Partial messages list fetch pending", - "value": "PARTIAL_MESSAGE_LIST_FETCH_PENDING", - "position": 1 - }, - { - "id": "daecc0da-ec0b-45ab-ad2d-2240945a3401", - "color": "orange", - "label": "Messages list fetch ongoing", - "value": "MESSAGE_LIST_FETCH_ONGOING", - "position": 2 - }, - { - "id": "4d2db113-0d55-4b08-aa62-d046cba0355e", - "color": "blue", - "label": "Messages import pending", - "value": "MESSAGES_IMPORT_PENDING", - "position": 3 - }, - { - "id": "36352f84-d97b-4762-b1a1-f10c2174831e", - "color": "orange", - "label": "Messages import ongoing", - "value": "MESSAGES_IMPORT_ONGOING", - "position": 4 - }, - { - "id": "8f3c45c2-8539-48bb-83a0-4af18f9f7852", - "color": "red", - "label": "Failed", - "value": "FAILED", - "position": 5 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3b94c079-895b-4334-ae18-08d6892a5b7c", - "type": "BOOLEAN", - "name": "isContactAutoCreationEnabled", - "label": "Is Contact Auto Creation Enabled", - "description": "Is Contact Auto Creation Enabled", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cc44284e-0f2a-4d29-89b0-12416ed5db94", - "type": "NUMBER", - "name": "throttleFailureCount", - "label": "Throttle Failure Count", - "description": "Throttle Failure Count", - "icon": "IconX", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "415b8ccc-aa22-4fdc-933c-bbaf477ff84b", - "type": "SELECT", - "name": "syncStatus", - "label": "Sync status", - "description": "Sync status", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": [ - { - "id": "38d30f8d-c137-46a6-b4a8-6e3cf681fe1f", - "color": "yellow", - "label": "Ongoing", - "value": "ONGOING", - "position": 1 - }, - { - "id": "e10615dc-208c-4e59-85bb-a109111c3888", - "color": "blue", - "label": "Not Synced", - "value": "NOT_SYNCED", - "position": 2 - }, - { - "id": "f600af02-8be4-4dcd-9880-ed8e00576f20", - "color": "green", - "label": "Active", - "value": "ACTIVE", - "position": 3 - }, - { - "id": "18ff3cfe-7680-4065-8400-6b2a31553de4", - "color": "red", - "label": "Failed Insufficient Permissions", - "value": "FAILED_INSUFFICIENT_PERMISSIONS", - "position": 4 - }, - { - "id": "f0bdbb33-e57d-4f14-8696-077c0edcc3d6", - "color": "red", - "label": "Failed Unknown", - "value": "FAILED_UNKNOWN", - "position": 5 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "90feafa8-c1c8-4c02-97cc-4b2779f64991", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bdd7ff46-118c-44e7-9b2e-cd522a248a8a", - "type": "RELATION", - "name": "messageChannelMessageAssociations", - "label": "Message Channel Association", - "description": "Messages from the channel.", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "40e51c6c-0268-47ca-bab9-4a899391e74b", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "311ea123-5b30-4637-ae39-3e639e780c83", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "bdd7ff46-118c-44e7-9b2e-cd522a248a8a", - "name": "messageChannelMessageAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "34479a8f-e7a4-4069-9f05-08d09113c8dc", - "name": "messageChannel" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1beb00c7-4641-439c-bd91-2d497de10f63", - "type": "BOOLEAN", - "name": "excludeGroupEmails", - "label": "Exclude group emails", - "description": "Exclude group emails", - "icon": "IconUsersGroup", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f4e6c74c-01ba-4afc-83d2-0e5149a35233", - "type": "DATE_TIME", - "name": "syncedAt", - "label": "Last sync date", - "description": "Last sync date", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "36d2fee2-936b-4b7d-82fc-eecb49f8d142", - "type": "SELECT", - "name": "type", - "label": "Type", - "description": "Channel Type", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'email'", - "options": [ - { - "id": "734f44a0-597e-403a-aaab-00ba91d6e832", - "color": "green", - "label": "Email", - "value": "email", - "position": 0 - }, - { - "id": "2827a65a-b2c7-4515-80d8-7d9c12ad5f8a", - "color": "blue", - "label": "SMS", - "value": "sms", - "position": 1 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "cefb6cd7-554d-4e70-8f45-fbb8dbd0fe92", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "d288fd3a-8fb0-493d-bec3-31a2c4a7d366", - "type": "RELATION", - "name": "connectedAccount", - "label": "Connected Account", - "description": "Connected Account", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "21bbef75-8acf-48bf-80aa-1d26d50aea22", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "311ea123-5b30-4637-ae39-3e639e780c83", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "d288fd3a-8fb0-493d-bec3-31a2c4a7d366", - "name": "connectedAccount" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "3bf6ad9c-0441-4b8f-8dd0-12d93f83b67a", - "name": "messageChannels" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e2f86b34-c7ea-41aa-b0cb-dfa171a9380a", - "type": "UUID", - "name": "connectedAccountId", - "label": "Connected Account id (foreign key)", - "description": "Connected Account id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a36aa9b7-abbb-4143-949d-42c9f711679f", - "type": "BOOLEAN", - "name": "isSyncEnabled", - "label": "Is Sync Enabled", - "description": "Is Sync Enabled", - "icon": "IconRefresh", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5fc28ad8-3623-4fb8-a872-efe52af486c1", - "type": "BOOLEAN", - "name": "excludeNonProfessionalEmails", - "label": "Exclude non professional emails", - "description": "Exclude non professional emails", - "icon": "IconBriefcase", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "0397d2f1-3e81-4521-9abf-1b39c584d8eb", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "opportunity", - "namePlural": "opportunities", - "labelSingular": "Opportunity", - "labelPlural": "Opportunities", - "description": "An opportunity", - "icon": "IconTargetArrow", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "3d6fa6cd-b06a-4465-8033-dc0ed8abda9d", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjE4" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "18ea34ae-f9bc-4240-b65f-46f0d688135f", - "type": "RELATION", - "name": "pointOfContact", - "label": "Point of Contact", - "description": "Opportunity point of contact", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "58a081ed-e5e7-44f8-bae6-99be66b6ac2f", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "18ea34ae-f9bc-4240-b65f-46f0d688135f", - "name": "pointOfContact" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "05863c2c-bcf7-4d88-b0cd-f00335b6854d", - "name": "pointOfContactForOpportunities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2e93a9a9-774b-43fd-8338-d54c29b8704c", - "type": "RELATION", - "name": "favorites", - "label": "Favorites", - "description": "Favorites linked to the opportunity", - "icon": "IconHeart", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2f07395e-a114-465c-a3a2-9c6b990d3dca", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "2e93a9a9-774b-43fd-8338-d54c29b8704c", - "name": "favorites" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4566e731-1922-4610-8e85-0beab7fc57be", - "nameSingular": "favorite", - "namePlural": "favorites" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2a725662-fe1a-44e8-af06-2ae21c9ae0c2", - "name": "opportunity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4e24ba90-8fcd-4df5-9fe8-48679c75d374", - "type": "RELATION", - "name": "timelineActivities", - "label": "Timeline Activities", - "description": "Timeline Activities linked to the opportunity.", - "icon": "IconTimelineEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "d8ae1b79-b532-412c-92cf-767a32e3cda2", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4e24ba90-8fcd-4df5-9fe8-48679c75d374", - "name": "timelineActivities" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "e095e196-08d4-493c-8a02-01c4a3decb5c", - "nameSingular": "timelineActivity", - "namePlural": "timelineActivities" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "987fd4f6-4c5f-48a4-82f3-fd769de80dc4", - "name": "opportunity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3d6fa6cd-b06a-4465-8033-dc0ed8abda9d", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "The opportunity name", - "icon": "IconTargetArrow", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7e6746ac-3625-4b14-86bc-11adc29e347c", - "type": "UUID", - "name": "companyId", - "label": "Company id (foreign key)", - "description": "Opportunity company id foreign key", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1bab9225-7390-43d7-a2c5-1d14f918efc0", - "type": "RELATION", - "name": "activityTargets", - "label": "Activities", - "description": "Activities tied to the opportunity", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2105fe76-e9fa-4610-992a-261d0f24722d", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "1bab9225-7390-43d7-a2c5-1d14f918efc0", - "name": "activityTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "948a52f8-eba6-4bb2-a3a7-b1aa61c0daf7", - "nameSingular": "activityTarget", - "namePlural": "activityTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "ddc84553-0678-4697-a8c2-06ddbc136cab", - "name": "opportunity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "809ebb3f-8c63-4009-a8b9-3e184d6f140c", - "type": "CURRENCY", - "name": "amount", - "label": "Amount", - "description": "Opportunity amount", - "icon": "IconCurrencyDollar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "amountMicros": null, - "currencyCode": "''" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "100f9c10-11c4-4fee-963a-f98a0e42d05d", - "type": "RELATION", - "name": "taskTargets", - "label": "Tasks", - "description": "Tasks tied to the opportunity", - "icon": "IconCheckbox", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "457627a4-8e4f-4720-80b4-b8c47a49a1d7", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "100f9c10-11c4-4fee-963a-f98a0e42d05d", - "name": "taskTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "5e92b318-bc10-4fe3-b997-de41b7e45c36", - "nameSingular": "taskTarget", - "namePlural": "taskTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "807cfd9f-4081-4027-b646-cf66d81aa8c6", - "name": "opportunity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "21626287-1f6d-4604-869f-59b77b98e529", - "type": "DATE_TIME", - "name": "closeDate", - "label": "Close date", - "description": "Opportunity close date", - "icon": "IconCalendarEvent", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a9d9912d-6f0d-4ddb-85ce-48acbba6ae00", - "type": "SELECT", - "name": "stage", - "label": "Stage", - "description": "Opportunity stage", - "icon": "IconProgressCheck", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'NEW'", - "options": [ - { - "id": "c8c92b5e-af59-4c21-a68c-c2dbdd513b2c", - "color": "red", - "label": "New", - "value": "NEW", - "position": 0 - }, - { - "id": "b33eff93-1fec-4275-97f0-03beb704a709", - "color": "purple", - "label": "Screening", - "value": "SCREENING", - "position": 1 - }, - { - "id": "d4fff9a7-53ea-4e10-a16e-b721133f9cda", - "color": "sky", - "label": "Meeting", - "value": "MEETING", - "position": 2 - }, - { - "id": "e0b16f06-4e59-4f8c-ac30-a6e3fc37eac8", - "color": "turquoise", - "label": "Proposal", - "value": "PROPOSAL", - "position": 3 - }, - { - "id": "a0ad0f8d-fed5-49c0-8dfd-00be4521db24", - "color": "yellow", - "label": "Customer", - "value": "CUSTOMER", - "position": 4 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1433c5e0-2dfc-404b-b04e-17126fd75e0a", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "727ec83b-93b7-4e6b-be22-6f00637ec3f5", - "type": "RELATION", - "name": "company", - "label": "Company", - "description": "Opportunity company", - "icon": "IconBuildingSkyscraper", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1ebadb76-46e6-4c57-b24f-441acecbd2d9", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "727ec83b-93b7-4e6b-be22-6f00637ec3f5", - "name": "company" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "701aecf9-eb1c-4d84-9d94-b954b231b64b", - "nameSingular": "company", - "namePlural": "companies" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "34aec238-a534-46e7-be64-d0680a12c8ec", - "name": "opportunities" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "89f08f6a-f94a-4f1c-823b-8e5513362c0b", - "type": "ACTOR", - "name": "createdBy", - "label": "Created by", - "description": "The creator of the record", - "icon": "IconCreativeCommonsSa", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": { - "name": "''", - "source": "'MANUAL'" - }, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8ac2f70d-3c5d-40a2-835c-849ae6c1ab81", - "type": "UUID", - "name": "pointOfContactId", - "label": "Point of Contact id (foreign key)", - "description": "Opportunity point of contact id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "095b38a6-1881-40b8-9849-cb80d19aa295", - "type": "RELATION", - "name": "attachments", - "label": "Attachments", - "description": "Attachments linked to the opportunity", - "icon": "IconFileImport", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c20ecc99-e48a-4311-b850-8fbf1a7b68ea", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "095b38a6-1881-40b8-9849-cb80d19aa295", - "name": "attachments" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "963747ea-45e2-4deb-b36d-73b014e17c42", - "nameSingular": "attachment", - "namePlural": "attachments" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "b4868b15-ff98-4f36-9f59-1dbf63052bb7", - "name": "opportunity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f74b36a5-caf7-4318-994b-251eb2be2668", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e6fe20c1-091e-418f-9ff0-8ea7cfb864f8", - "type": "RELATION", - "name": "noteTargets", - "label": "Notes", - "description": "Notes tied to the opportunity", - "icon": "IconNotes", - "isCustom": false, - "isActive": true, - "isSystem": false, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c0946e53-4cdd-46b4-b30a-9fce040b9a7a", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2dbf5d59-f03c-4578-8ff3-750f4bcdf8d0", - "nameSingular": "opportunity", - "namePlural": "opportunities" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "e6fe20c1-091e-418f-9ff0-8ea7cfb864f8", - "name": "noteTargets" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dcb774a3-71e8-44cc-bf53-7f195e0bfdb6", - "nameSingular": "noteTarget", - "namePlural": "noteTargets" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "5b2ec790-e8b8-4bd0-bf1b-db4ebc2b473a", - "name": "opportunity" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "206e678d-dc5d-4e69-b3e0-a16f9ec59676", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7717144e-ac08-4f29-8614-f12ad830ce5a", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "Opportunity record position", - "icon": "IconHierarchy2", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "view", - "namePlural": "views", - "labelSingular": "View", - "labelPlural": "Views", - "description": "(System) Views", - "icon": "IconLayoutCollage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "edfa9baf-6165-4be4-9f0f-0831c167cc43", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEz" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "92815f89-9c9d-4fc7-b0b0-dd9c0ca24fe7", - "type": "TEXT", - "name": "type", - "label": "Type", - "description": "View type", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'table'", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "15896bc9-fb20-4c1f-bc0d-f3abe768d388", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7a703490-9619-44e3-a1d7-95b37f128d7c", - "type": "SELECT", - "name": "key", - "label": "Key", - "description": "View key", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'INDEX'", - "options": [ - { - "id": "590b78a5-1851-436a-b696-acc719fa4be8", - "color": "red", - "label": "Index", - "value": "INDEX", - "position": 0 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "78afce65-d674-42b7-90dd-f61238fdbcf1", - "type": "TEXT", - "name": "icon", - "label": "Icon", - "description": "View icon", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c086b30a-0267-4857-9fe0-29a2bbaa8dc8", - "type": "RELATION", - "name": "viewFields", - "label": "View Fields", - "description": "View Fields", - "icon": "IconTag", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "ae731975-39ee-4387-a80c-de94dff0b760", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "nameSingular": "view", - "namePlural": "views" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "c086b30a-0267-4857-9fe0-29a2bbaa8dc8", - "name": "viewFields" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "c81903be-3be2-49af-82b3-d170cd35ac0f", - "nameSingular": "viewField", - "namePlural": "viewFields" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "337d9389-06a9-4cb1-9f2a-76dbb37a7576", - "name": "view" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "edfa9baf-6165-4be4-9f0f-0831c167cc43", - "type": "TEXT", - "name": "name", - "label": "Name", - "description": "View name", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5969cfdb-bf30-4a34-9b52-11b38945bbd0", - "type": "RELATION", - "name": "viewSorts", - "label": "View Sorts", - "description": "View Sorts", - "icon": "IconArrowsSort", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "fcf27acc-a651-4ac2-9f99-aba306756209", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "nameSingular": "view", - "namePlural": "views" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "5969cfdb-bf30-4a34-9b52-11b38945bbd0", - "name": "viewSorts" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "718779fd-d87d-4b99-8f6c-3042a6bb03a3", - "nameSingular": "viewSort", - "namePlural": "viewSorts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2c09d04d-007c-4652-9c90-c2cfa4696145", - "name": "view" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "563534f3-90f6-42f8-9dbc-0f067ca5424c", - "type": "POSITION", - "name": "position", - "label": "Position", - "description": "View position", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "8fd9225d-5f08-4e21-a0cf-d596d4ec1eaf", - "type": "UUID", - "name": "objectMetadataId", - "label": "Object Metadata Id", - "description": "View target object", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4f92f2f0-9204-4f23-afdc-894829664668", - "type": "RELATION", - "name": "viewFilters", - "label": "View Filters", - "description": "View Filters", - "icon": "IconFilterBolt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "7c42db51-2fcc-44b6-9a80-787b1967e69e", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "2c6e4a32-28cd-4a72-8ca6-915fd819ed32", - "nameSingular": "view", - "namePlural": "views" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "4f92f2f0-9204-4f23-afdc-894829664668", - "name": "viewFilters" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "816a7154-5111-47fa-9d8d-87ca2dafc521", - "nameSingular": "viewFilter", - "namePlural": "viewFilters" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "0f9c4eb8-501d-4861-827a-5ef45a01eba9", - "name": "view" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5c342176-2358-4269-9293-f7666691b1ff", - "type": "TEXT", - "name": "kanbanFieldMetadataId", - "label": "kanbanfieldMetadataId", - "description": "View Kanban column field", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "193fbb6a-6c71-4871-92dc-d4b71b856b83", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "87c37d2e-9457-47f3-bdf4-41879bb81b90", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "80e2dcf2-ffff-45db-adbc-174d739f1fb6", - "type": "BOOLEAN", - "name": "isCompact", - "label": "Compact View", - "description": "Describes if the view is in compact mode", - "icon": null, - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": false, - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "1f73c3c3-a356-4a70-8a91-948e70120fdf", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "messageThread", - "namePlural": "messageThreads", - "labelSingular": "Message Thread", - "labelPlural": "Message Threads", - "description": "Message Thread", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "408fe748-df73-4975-b473-8260e9da2e4b", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjQ=" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9016096d-93c4-495f-93d5-b966e5bedc74", - "type": "RELATION", - "name": "messages", - "label": "Messages", - "description": "Messages from the thread.", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "c958fe88-7d66-4c1b-87c7-55ab724f42c5", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1f73c3c3-a356-4a70-8a91-948e70120fdf", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "9016096d-93c4-495f-93d5-b966e5bedc74", - "name": "messages" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "nameSingular": "message", - "namePlural": "messages" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "114f853e-2684-4e62-92c9-0213ace3c498", - "name": "messageThread" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6fe89951-20b6-4ef3-81d8-08498b09954b", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "25bbf51f-17fa-4a2c-9636-3f3fdba41e08", - "type": "RELATION", - "name": "messageChannelMessageAssociations", - "label": "Message Channel Association", - "description": "Messages from the channel", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1d8cbabc-edf5-40c9-8bd5-d1e47a93d246", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "1f73c3c3-a356-4a70-8a91-948e70120fdf", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "25bbf51f-17fa-4a2c-9636-3f3fdba41e08", - "name": "messageChannelMessageAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "fcbef4a3-f1d9-4714-b7ea-f44816821d6e", - "name": "messageThread" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "aef283f1-b060-4de8-a5b9-f1ef4e4c0bb4", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "408fe748-df73-4975-b473-8260e9da2e4b", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "0e285964-d858-48bc-98ab-b8c6b1bd5d0b", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels", - "labelSingular": "Calendar Channel", - "labelPlural": "Calendar Channels", - "description": "Calendar Channels", - "icon": "IconCalendar", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "2cfd31ac-4391-4922-a640-38ac515564fc", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjE1" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "31ed435c-f012-4dfb-8825-35c522129243", - "type": "BOOLEAN", - "name": "isSyncEnabled", - "label": "Is Sync Enabled", - "description": "Is Sync Enabled", - "icon": "IconRefresh", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "4faab073-db9b-4644-8a16-57eb573c428d", - "type": "SELECT", - "name": "syncStage", - "label": "Sync stage", - "description": "Sync stage", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'FULL_CALENDAR_EVENT_LIST_FETCH_PENDING'", - "options": [ - { - "id": "74fb48bf-87b9-4094-8c46-4f74128e78d8", - "color": "blue", - "label": "Full calendar event list fetch pending", - "value": "FULL_CALENDAR_EVENT_LIST_FETCH_PENDING", - "position": 0 - }, - { - "id": "f0b2944c-b9e4-4889-b405-99e023b69a65", - "color": "blue", - "label": "Partial calendar event list fetch pending", - "value": "PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING", - "position": 1 - }, - { - "id": "ec321870-1e95-4a7e-bc9d-ea58d0020d43", - "color": "orange", - "label": "Calendar event list fetch ongoing", - "value": "CALENDAR_EVENT_LIST_FETCH_ONGOING", - "position": 2 - }, - { - "id": "bf48f26c-8ce9-43cf-a89a-53e7fbb1afc6", - "color": "blue", - "label": "Calendar events import pending", - "value": "CALENDAR_EVENTS_IMPORT_PENDING", - "position": 3 - }, - { - "id": "d3d349ad-b3bf-4762-8aad-92796cd86e08", - "color": "orange", - "label": "Calendar events import ongoing", - "value": "CALENDAR_EVENTS_IMPORT_ONGOING", - "position": 4 - }, - { - "id": "7a882ecb-67ae-42c4-bac7-216c6b8522c4", - "color": "red", - "label": "Failed", - "value": "FAILED", - "position": 5 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "b1d2c87b-9e88-44b7-9b2d-138d359c28df", - "type": "SELECT", - "name": "syncStatus", - "label": "Sync status", - "description": "Sync status", - "icon": "IconStatusChange", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": [ - { - "id": "ab4ddbc1-8af6-4f9e-a3e9-f87a18e47951", - "color": "yellow", - "label": "Ongoing", - "value": "ONGOING", - "position": 1 - }, - { - "id": "bee7589a-9d59-4b01-9d9b-ef45dd4c17ca", - "color": "blue", - "label": "Not Synced", - "value": "NOT_SYNCED", - "position": 2 - }, - { - "id": "11fad735-37f3-4297-9da4-0251355ff461", - "color": "green", - "label": "Active", - "value": "ACTIVE", - "position": 3 - }, - { - "id": "e7da6feb-df29-49f4-b5ba-a82534490e5c", - "color": "red", - "label": "Failed Insufficient Permissions", - "value": "FAILED_INSUFFICIENT_PERMISSIONS", - "position": 4 - }, - { - "id": "61ae12e3-22e5-4971-9044-3ed274567b29", - "color": "red", - "label": "Failed Unknown", - "value": "FAILED_UNKNOWN", - "position": 5 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3e26b011-c7eb-44ff-9599-15b85ef5576d", - "type": "SELECT", - "name": "visibility", - "label": "Visibility", - "description": "Visibility", - "icon": "IconEyeglass", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'SHARE_EVERYTHING'", - "options": [ - { - "id": "d27d8a0e-d892-4581-b982-500f33b53803", - "color": "green", - "label": "Metadata", - "value": "METADATA", - "position": 0 - }, - { - "id": "fa61ab2d-1e83-4a01-8526-81c8460cc7dc", - "color": "orange", - "label": "Share Everything", - "value": "SHARE_EVERYTHING", - "position": 1 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ceaf8f8e-297a-418b-a652-01f3eeb5c562", - "type": "RELATION", - "name": "connectedAccount", - "label": "Connected Account", - "description": "Connected Account", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "b6b75323-8790-4b3e-8798-e0af646bb9aa", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0e285964-d858-48bc-98ab-b8c6b1bd5d0b", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "ceaf8f8e-297a-418b-a652-01f3eeb5c562", - "name": "connectedAccount" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "66cd3a29-e2d8-4efa-8852-d17d7b538efa", - "nameSingular": "connectedAccount", - "namePlural": "connectedAccounts" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "bb10e69d-f049-4d97-84f4-09bce29cd401", - "name": "calendarChannels" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "284f9e52-0885-4860-98ea-2083911aa453", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3127b8eb-f998-4987-a797-01a8943e677a", - "type": "UUID", - "name": "connectedAccountId", - "label": "Connected Account id (foreign key)", - "description": "Connected Account id foreign key", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fd92e8cd-5f05-49e7-aa15-ae8c3a8905d6", - "type": "DATE_TIME", - "name": "syncStageStartedAt", - "label": "Sync stage started at", - "description": "Sync stage started at", - "icon": "IconHistory", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9867ad34-df58-4ad0-a459-cc283990b5e5", - "type": "RELATION", - "name": "calendarChannelEventAssociations", - "label": "Calendar Channel Event Associations", - "description": "Calendar Channel Event Associations", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "bf0f695a-08cd-4767-9a83-4fd09f617793", - "direction": "ONE_TO_MANY", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0e285964-d858-48bc-98ab-b8c6b1bd5d0b", - "nameSingular": "calendarChannel", - "namePlural": "calendarChannels" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "9867ad34-df58-4ad0-a459-cc283990b5e5", - "name": "calendarChannelEventAssociations" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "4fed9657-e68b-4856-8e6d-a1c860d16242", - "nameSingular": "calendarChannelEventAssociation", - "namePlural": "calendarChannelEventAssociations" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "d3039865-07b4-4114-bd78-18aa0be2a93b", - "name": "calendarChannel" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "1da10e0f-ddc1-441e-8d88-c2453ea397f7", - "type": "BOOLEAN", - "name": "isContactAutoCreationEnabled", - "label": "Is Contact Auto Creation Enabled", - "description": "Is Contact Auto Creation Enabled", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": true, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "2cfd31ac-4391-4922-a640-38ac515564fc", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c62bba75-6421-4fd0-966c-6749870b0b5b", - "type": "NUMBER", - "name": "throttleFailureCount", - "label": "Throttle Failure Count", - "description": "Throttle Failure Count", - "icon": "IconX", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": 0, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ae2fc211-7736-43b8-a7e2-b08476446ea5", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "e4ede7f5-3c11-4e77-9327-fe433429105b", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "ee994c99-3f91-4d2a-9956-449ab18af0e7", - "type": "SELECT", - "name": "contactAutoCreationPolicy", - "label": "Contact auto creation policy", - "description": "Automatically create records for people you participated with in an event.", - "icon": "IconUserCircle", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'AS_PARTICIPANT_AND_ORGANIZER'", - "options": [ - { - "id": "add1e0ce-43a9-457a-b7ba-8ddd71acff41", - "color": "green", - "label": "As Participant and Organizer", - "value": "AS_PARTICIPANT_AND_ORGANIZER", - "position": 0 - }, - { - "id": "93ef8799-ed7b-42f8-a4a4-b878c9599108", - "color": "orange", - "label": "As Participant", - "value": "AS_PARTICIPANT", - "position": 1 - }, - { - "id": "a94610a1-429d-449e-ae0b-67eaba058ef8", - "color": "blue", - "label": "As Organizer", - "value": "AS_ORGANIZER", - "position": 2 - }, - { - "id": "ca729343-a09c-486b-a240-5d80e7e076eb", - "color": "red", - "label": "None", - "value": "NONE", - "position": 3 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "c31280e5-0ed4-49e3-8f52-0d301698db1d", - "type": "TEXT", - "name": "syncCursor", - "label": "Sync Cursor", - "description": "Sync Cursor. Used for syncing events from the calendar provider", - "icon": "IconReload", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants", - "labelSingular": "Message Participant", - "labelPlural": "Message Participants", - "description": "Message Participants", - "icon": "IconUserCircle", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "3a3ffcd3-9a78-4eed-ae5b-6838156857fe", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEx" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "90e73039-1ac8-4d7c-9d65-301373ade28b", - "type": "TEXT", - "name": "displayName", - "label": "Display Name", - "description": "Display Name", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5b46d192-34b1-4122-801c-7b6f74b49743", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "5353cebc-8357-49bd-bcf2-292409d637ba", - "type": "UUID", - "name": "workspaceMemberId", - "label": "Workspace Member id (foreign key)", - "description": "Workspace member id foreign key", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "bc788a8f-8eb2-47bf-a02c-42f7de197ca8", - "type": "RELATION", - "name": "workspaceMember", - "label": "Workspace Member", - "description": "Workspace member", - "icon": "IconCircleUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "7d3faf56-e4bb-45ec-9b75-612ca6e9ae5a", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "bc788a8f-8eb2-47bf-a02c-42f7de197ca8", - "name": "workspaceMember" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "b87720c6-bead-46a9-8c1e-c8596bdb702e", - "nameSingular": "workspaceMember", - "namePlural": "workspaceMembers" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "c00ccd93-ebc7-4744-8cb3-797a752b4627", - "name": "messageParticipants" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f897f356-ea94-4bd4-869a-4c138e57704e", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3a3ffcd3-9a78-4eed-ae5b-6838156857fe", - "type": "TEXT", - "name": "handle", - "label": "Handle", - "description": "Handle", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "728a4a3f-1145-48cb-804e-b790fdf1c0f4", - "type": "UUID", - "name": "personId", - "label": "Person id (foreign key)", - "description": "Person id foreign key", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "52b01517-9ac7-48d9-a292-b969197a33f5", - "type": "UUID", - "name": "messageId", - "label": "Message id (foreign key)", - "description": "Message id foreign key", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "660b4257-010e-4039-897a-e274f2559ed5", - "type": "RELATION", - "name": "message", - "label": "Message", - "description": "Message", - "icon": "IconMessage", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "2180d888-98dc-428e-a157-c30ce7bf8ce4", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "660b4257-010e-4039-897a-e274f2559ed5", - "name": "message" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "nameSingular": "message", - "namePlural": "messages" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "15658254-6562-4fad-9ef3-393f913e95c2", - "name": "messageParticipants" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "362195e4-4dfb-49e1-b25b-fe3ffe7b7f14", - "type": "RELATION", - "name": "person", - "label": "Person", - "description": "Person", - "icon": "IconUser", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "5bb99199-6a3c-4947-b16b-6a90c6097eac", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0c0a3db9-f3ba-485a-8dff-488c477f3fa6", - "nameSingular": "messageParticipant", - "namePlural": "messageParticipants" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "362195e4-4dfb-49e1-b25b-fe3ffe7b7f14", - "name": "person" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "aeffaa4e-cae1-4dd8-b76e-5658eb73d0a9", - "nameSingular": "person", - "namePlural": "people" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "19f77ace-4b00-4fac-ba7d-8c7a3dde409b", - "name": "messageParticipants" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6dec8f79-0674-48e8-9b01-74585880e8a2", - "type": "SELECT", - "name": "role", - "label": "Role", - "description": "Role", - "icon": "IconAt", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "'from'", - "options": [ - { - "id": "f105c9e6-9a17-4cb0-bede-fd7dc67cdeb7", - "color": "green", - "label": "From", - "value": "from", - "position": 0 - }, - { - "id": "a8fa2086-b0bd-48cf-914d-247be3fef5fd", - "color": "blue", - "label": "To", - "value": "to", - "position": 1 - }, - { - "id": "2b763815-9fc7-4b47-a364-f6cb639ef609", - "color": "orange", - "label": "Cc", - "value": "cc", - "position": 2 - }, - { - "id": "23b26d79-b074-44d1-a910-25ecb3ff808a", - "color": "red", - "label": "Bcc", - "value": "bcc", - "position": 3 - } - ], - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "66a43663-68e0-404d-9e36-63be0bd5e406", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - }, - { - "__typename": "objectEdge", - "node": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "dataSourceId": "8b919f4b-aef5-40ba-aeeb-3f29b90e765f", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations", - "labelSingular": "Message Channel Message Association", - "labelPlural": "Message Channel Message Associations", - "description": "Message Synced with a Message Channel", - "icon": "IconMessage", - "isCustom": false, - "isRemote": false, - "isActive": true, - "isSystem": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "labelIdentifierFieldMetadataId": "6cb4b092-b140-4f6c-97d6-0f7f7d3ae6f7", - "imageIdentifierFieldMetadataId": null, - "fields": { - "__typename": "ObjectFieldsConnection", - "pageInfo": { - "__typename": "PageInfo", - "hasNextPage": false, - "hasPreviousPage": false, - "startCursor": "YXJyYXljb25uZWN0aW9uOjA=", - "endCursor": "YXJyYXljb25uZWN0aW9uOjEw" - }, - "edges": [ - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7f9297fa-ca06-4da9-9a6c-83028f25ba88", - "type": "UUID", - "name": "messageId", - "label": "Message Id id (foreign key)", - "description": "Message Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "9810950a-952a-412b-a590-be91bbb7efcd", - "type": "TEXT", - "name": "messageExternalId", - "label": "Message External Id", - "description": "Message id from the messaging provider", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6d269e30-23a1-4963-8c78-4bdf7108875c", - "type": "UUID", - "name": "messageChannelId", - "label": "Message Channel Id id (foreign key)", - "description": "Message Channel Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "f2b8af0c-5143-4715-b0ef-0ecf06874fa7", - "type": "TEXT", - "name": "messageThreadExternalId", - "label": "Thread External Id", - "description": "Thread id from the messaging provider", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "''", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "3f3c11b1-b1b5-4831-9a4e-963317c8c9a5", - "type": "DATE_TIME", - "name": "createdAt", - "label": "Creation date", - "description": "Creation date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "785c0609-42b8-4b0e-b7c2-4d54b6ed651f", - "type": "RELATION", - "name": "message", - "label": "Message Id", - "description": "Message Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "642b4d8c-f2f8-4590-abce-4b112d8689ba", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "785c0609-42b8-4b0e-b7c2-4d54b6ed651f", - "name": "message" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "dfdcf91e-f4b4-4460-8c89-919ef501fd79", - "nameSingular": "message", - "namePlural": "messages" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "2ac789bf-ce05-4f0e-9f04-f848f93c2f21", - "name": "messageChannelMessageAssociations" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "34479a8f-e7a4-4069-9f05-08d09113c8dc", - "type": "RELATION", - "name": "messageChannel", - "label": "Message Channel Id", - "description": "Message Channel Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "40e51c6c-0268-47ca-bab9-4a899391e74b", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "34479a8f-e7a4-4069-9f05-08d09113c8dc", - "name": "messageChannel" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "311ea123-5b30-4637-ae39-3e639e780c83", - "nameSingular": "messageChannel", - "namePlural": "messageChannels" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "bdd7ff46-118c-44e7-9b2e-cd522a248a8a", - "name": "messageChannelMessageAssociations" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "6cb4b092-b140-4f6c-97d6-0f7f7d3ae6f7", - "type": "UUID", - "name": "id", - "label": "Id", - "description": "Id", - "icon": "Icon123", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "uuid", - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "7d037a16-39b4-45ef-b06e-a8b99151d223", - "type": "UUID", - "name": "messageThreadId", - "label": "Message Thread Id id (foreign key)", - "description": "Message Thread Id id foreign key", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": null, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "fcbef4a3-f1d9-4714-b7ea-f44816821d6e", - "type": "RELATION", - "name": "messageThread", - "label": "Message Thread Id", - "description": "Message Thread Id", - "icon": "IconHash", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": true, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": null, - "options": null, - "relationDefinition": { - "__typename": "RelationDefinition", - "relationId": "1d8cbabc-edf5-40c9-8bd5-d1e47a93d246", - "direction": "MANY_TO_ONE", - "sourceObjectMetadata": { - "__typename": "object", - "id": "0985d46f-722d-468f-9fa6-efa219405aa7", - "nameSingular": "messageChannelMessageAssociation", - "namePlural": "messageChannelMessageAssociations" - }, - "sourceFieldMetadata": { - "__typename": "field", - "id": "fcbef4a3-f1d9-4714-b7ea-f44816821d6e", - "name": "messageThread" - }, - "targetObjectMetadata": { - "__typename": "object", - "id": "1f73c3c3-a356-4a70-8a91-948e70120fdf", - "nameSingular": "messageThread", - "namePlural": "messageThreads" - }, - "targetFieldMetadata": { - "__typename": "field", - "id": "25bbf51f-17fa-4a2c-9636-3f3fdba41e08", - "name": "messageChannelMessageAssociations" - } - }, - } - }, - { - "__typename": "fieldEdge", - "node": { - "__typename": "field", - "id": "a00f2265-a78a-4b12-9367-e034da304ac6", - "type": "DATE_TIME", - "name": "updatedAt", - "label": "Update date", - "description": "Update date", - "icon": "IconCalendar", - "isCustom": false, - "isActive": true, - "isSystem": true, - "isNullable": false, - "createdAt": "2024-08-02T16:00:05.938Z", - "updatedAt": "2024-08-02T16:00:05.938Z", - "defaultValue": "now", - "options": null, - "relationDefinition": null, - } - } - ] - } - } - } - ] -} - -} as ObjectMetadataItemsQuery; - diff --git a/packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts b/packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts similarity index 58% rename from packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts rename to packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts index a728493428ca..ea4a8fae3850 100644 --- a/packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts +++ b/packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts @@ -1,8 +1,14 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/standard-metadata-query-result'; +import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result'; export const generatedMockObjectMetadataItems: ObjectMetadataItem[] = mockedStandardObjectMetadataQueryResult.objects.edges.map((edge) => ({ ...edge.node, fields: edge.node.fields.edges.map((edge) => edge.node), + indexMetadatas: edge.node.indexMetadatas.edges.map((index) => ({ + ...index.node, + indexFieldMetadatas: index.node.indexFieldMetadatas?.edges.map( + (indexField) => indexField.node, + ), + })), })); 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 93635d405e94..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/standard-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/people.ts b/packages/twenty-front/src/testing/mock-data/people.ts index 439730e43ae8..9139aa194367 100644 --- a/packages/twenty-front/src/testing/mock-data/people.ts +++ b/packages/twenty-front/src/testing/mock-data/people.ts @@ -45,7 +45,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:52:46.814Z', city: 'ASd', - phone: '', + phones: { + primaryPhoneNumber: '781234562', + primaryPhoneCountryCode: '+33', + }, id: 'da3c2c4b-da01-4b81-9734-226069eb4cd0', jobTitle: '', position: 0, @@ -172,7 +175,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-01T09:50:00.000Z', city: 'Seattle', - phone: '+33789012345', + phones: { + primaryPhoneNumber: '781234562', + primaryPhoneCountryCode: '+33', + }, id: '20202020-1c0e-494c-a1b6-85b1c6fefaa5', jobTitle: '', position: 1, @@ -299,7 +305,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Los Angeles', - phone: '+33780123456', + phones: { + primaryPhoneNumber: '781234576', + primaryPhoneCountryCode: '+33', + }, id: '20202020-ac73-4797-824e-87a1f5aea9e0', jobTitle: '', position: 2, @@ -395,7 +404,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33789012345', + phones: { + primaryPhoneNumber: '781234545', + primaryPhoneCountryCode: '+33', + }, id: '20202020-f517-42fd-80ae-14173b3b70ae', jobTitle: '', position: 3, @@ -491,7 +503,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Los Angeles', - phone: '+33780123456', + phones: { + primaryPhoneNumber: '781234587', + primaryPhoneCountryCode: '+33', + }, id: '20202020-eee1-4690-ad2c-8619e5b56a2e', jobTitle: '', position: 4, @@ -587,7 +602,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33781234567', + phones: { + primaryPhoneNumber: '781234599', + primaryPhoneCountryCode: '+33', + }, id: '20202020-6784-4449-afdf-dc62cb8702f2', jobTitle: '', position: 5, @@ -683,7 +701,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'New York', - phone: '+33782345678', + phones: { + primaryPhoneNumber: '781234572', + primaryPhoneCountryCode: '+33', + }, id: '20202020-490f-4466-8391-733cfd66a0c8', jobTitle: '', position: 6, @@ -779,7 +800,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33783456789', + phones: { + primaryPhoneNumber: '781234582', + primaryPhoneCountryCode: '+33', + }, id: '20202020-80f1-4dff-b570-a74942528de3', jobTitle: '', position: 7, @@ -875,7 +899,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'New York', - phone: '+33784567890', + phones: { + primaryPhoneNumber: '781234569', + primaryPhoneCountryCode: '+33', + }, id: '20202020-338b-46df-8811-fa08c7d19d35', jobTitle: '', position: 8, @@ -971,7 +998,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'San Francisco', - phone: '+33785678901', + phones: { + primaryPhoneNumber: '781234962', + primaryPhoneCountryCode: '+33', + }, id: '20202020-64ad-4b0e-bbfd-e9fd795b7016', jobTitle: '', position: 9, @@ -1067,7 +1097,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'New York', - phone: '+33786789012', + phones: { + primaryPhoneNumber: '781234502', + primaryPhoneCountryCode: '+33', + }, id: '20202020-5d54-41b7-ba36-f0d20e1417ae', jobTitle: '', position: 10, @@ -1163,7 +1196,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Los Angeles', - phone: '+33787890123', + phones: { + primaryPhoneNumber: '781234563', + primaryPhoneCountryCode: '+33', + }, id: '20202020-623d-41fe-92e7-dd45b7c568e1', jobTitle: '', position: 11, @@ -1259,7 +1295,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33788901234', + phones: { + primaryPhoneNumber: '781234542', + primaryPhoneCountryCode: '+33', + }, id: '20202020-2d40-4e49-8df4-9c6a049190ef', jobTitle: '', position: 12, @@ -1355,7 +1394,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33788901234', + phones: { + primaryPhoneNumber: '782234562', + primaryPhoneCountryCode: '+33', + }, id: '20202020-2d40-4e49-8df4-9c6a049190df', jobTitle: '', position: 13, @@ -1451,7 +1493,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33788901234', + phones: { + primaryPhoneNumber: '781274562', + primaryPhoneCountryCode: '+33', + }, id: '20202020-2d40-4e49-8df4-9c6a049191de', jobTitle: '', position: 14, @@ -1547,7 +1592,10 @@ export const peopleQueryResult: { people: RecordGqlConnection } = { __typename: 'Person', createdAt: '2024-08-02T09:48:36.193Z', city: 'Seattle', - phone: '+33788901235', + phones: { + primaryPhoneNumber: '781239562', + primaryPhoneCountryCode: '+33', + }, id: '20202020-2d40-4e49-8df4-9c6a049191df', jobTitle: '', position: 15, 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/types/ExcludeLiteral.ts b/packages/twenty-front/src/types/ExcludeLiteral.ts new file mode 100644 index 000000000000..897d995b7a91 --- /dev/null +++ b/packages/twenty-front/src/types/ExcludeLiteral.ts @@ -0,0 +1 @@ +export type ExcludeLiteral<T, U extends T> = T extends U ? never : T; diff --git a/packages/twenty-front/src/types/PickLiteral.ts b/packages/twenty-front/src/types/PickLiteral.ts new file mode 100644 index 000000000000..545378336485 --- /dev/null +++ b/packages/twenty-front/src/types/PickLiteral.ts @@ -0,0 +1 @@ +export type PickLiteral<T, U extends T> = U; 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/array/sortByProperty.ts b/packages/twenty-front/src/utils/array/sortByProperty.ts new file mode 100644 index 000000000000..7cdb9b3b488b --- /dev/null +++ b/packages/twenty-front/src/utils/array/sortByProperty.ts @@ -0,0 +1,18 @@ +export const sortByProperty = + <T, K extends keyof T>(propertyName: K, sortBy: 'asc' | 'desc' = 'asc') => + (objectA: T, objectB: T) => { + const a = sortBy === 'asc' ? objectA : objectB; + const b = sortBy === 'asc' ? objectB : objectA; + + if (typeof a[propertyName] === 'string') { + return (a[propertyName] as string).localeCompare( + b[propertyName] as string, + ); + } else if (typeof a[propertyName] === 'number') { + return (a[propertyName] as number) - (b[propertyName] as number); + } else { + throw new Error( + 'Property type not supported in sortByProperty, only string and number are supported', + ); + } + }; 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/file/getFileNameAndExtension.ts b/packages/twenty-front/src/utils/file/getFileNameAndExtension.ts new file mode 100644 index 000000000000..231648cd5c93 --- /dev/null +++ b/packages/twenty-front/src/utils/file/getFileNameAndExtension.ts @@ -0,0 +1,8 @@ +export const getFileNameAndExtension = (filenameWithExtension: string) => { + const lastDotIndex = filenameWithExtension.lastIndexOf('.'); + + return { + name: filenameWithExtension.substring(0, lastDotIndex), + extension: filenameWithExtension.substring(lastDotIndex), + }; +}; 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-front/src/utils/getDisplayValueByUrlType.ts b/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts index 75cf16fa197d..5e5087d5c0ba 100644 --- a/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts +++ b/packages/twenty-front/src/utils/getDisplayValueByUrlType.ts @@ -16,7 +16,7 @@ export const getDisplayValueByUrlType = ({ /(?:https?:\/\/)?(?:www.)?linkedin.com\/(?:in|company|school)\/(.*)/, ); if (isDefined(matches?.[1])) { - return matches?.[1]; + return decodeURIComponent(matches?.[1]); } else { return 'LinkedIn'; } diff --git a/packages/twenty-front/src/utils/string/__tests__/turnIntoEmptyStringIfWhitespacesOnly.test.ts b/packages/twenty-front/src/utils/string/__tests__/turnIntoEmptyStringIfWhitespacesOnly.test.ts new file mode 100644 index 000000000000..84a8179ef036 --- /dev/null +++ b/packages/twenty-front/src/utils/string/__tests__/turnIntoEmptyStringIfWhitespacesOnly.test.ts @@ -0,0 +1,19 @@ +import { turnIntoEmptyStringIfWhitespacesOnly } from '../turnIntoEmptyStringIfWhitespacesOnly'; + +describe('turnIntoEmptyStringIfWhitespacesOnly', () => { + it('should return an empty string for whitespace-only input', () => { + expect(turnIntoEmptyStringIfWhitespacesOnly(' ')).toBe(''); + expect(turnIntoEmptyStringIfWhitespacesOnly('\t\n ')).toBe(''); + expect(turnIntoEmptyStringIfWhitespacesOnly(' \n\r\t')).toBe(''); + }); + + it('should return the original string for non-whitespace input', () => { + expect(turnIntoEmptyStringIfWhitespacesOnly('hello')).toBe('hello'); + expect(turnIntoEmptyStringIfWhitespacesOnly(' hello ')).toBe(' hello '); + expect(turnIntoEmptyStringIfWhitespacesOnly('123')).toBe('123'); + }); + + it('should handle empty string input', () => { + expect(turnIntoEmptyStringIfWhitespacesOnly('')).toBe(''); + }); +}); diff --git a/packages/twenty-front/src/utils/string/__tests__/turnIntoUndefinedIfWhitespacesOnly.test.ts b/packages/twenty-front/src/utils/string/__tests__/turnIntoUndefinedIfWhitespacesOnly.test.ts new file mode 100644 index 000000000000..fd1a2e105eaa --- /dev/null +++ b/packages/twenty-front/src/utils/string/__tests__/turnIntoUndefinedIfWhitespacesOnly.test.ts @@ -0,0 +1,19 @@ +import { turnIntoUndefinedIfWhitespacesOnly } from '../turnIntoUndefinedIfWhitespacesOnly'; + +describe('turnIntoUndefinedIfWhitespacesOnly', () => { + it('should return undefined for whitespace-only input', () => { + expect(turnIntoUndefinedIfWhitespacesOnly(' ')).toBeUndefined(); + expect(turnIntoUndefinedIfWhitespacesOnly('\t\n ')).toBeUndefined(); + expect(turnIntoUndefinedIfWhitespacesOnly(' \n\r\t')).toBeUndefined(); + }); + + it('should return the original string for non-whitespace input', () => { + expect(turnIntoUndefinedIfWhitespacesOnly('hello')).toBe('hello'); + expect(turnIntoUndefinedIfWhitespacesOnly(' hello ')).toBe(' hello '); + expect(turnIntoUndefinedIfWhitespacesOnly('123')).toBe('123'); + }); + + it('should handle empty string input', () => { + expect(turnIntoUndefinedIfWhitespacesOnly('')).toBeUndefined(); + }); +}); diff --git a/packages/twenty-front/src/utils/string/turnIntoUndefinedIfWhitespacesOnly.ts b/packages/twenty-front/src/utils/string/turnIntoUndefinedIfWhitespacesOnly.ts index 1f2d257a5917..5fb13d010bad 100644 --- a/packages/twenty-front/src/utils/string/turnIntoUndefinedIfWhitespacesOnly.ts +++ b/packages/twenty-front/src/utils/string/turnIntoUndefinedIfWhitespacesOnly.ts @@ -1,5 +1,5 @@ export const turnIntoUndefinedIfWhitespacesOnly = ( value: string, ): string | undefined => { - return value.trim() === '' ? undefined : value.trim(); + return value.trim() === '' ? undefined : value; }; diff --git a/packages/twenty-front/tsconfig.json b/packages/twenty-front/tsconfig.json index ec14700eb407..48b5bf3817a8 100644 --- a/packages/twenty-front/tsconfig.json +++ b/packages/twenty-front/tsconfig.json @@ -28,7 +28,6 @@ } }, "files": [], - "exclude": ["**/object-metadata/utils/getObjectMetadataItemsMock.ts"], "include": [], "references": [ { diff --git a/packages/twenty-postgres/linux/provision-postgres-linux.sh b/packages/twenty-postgres/linux/provision-postgres-linux.sh index 0652e3bda34e..84f047a72a82 100755 --- a/packages/twenty-postgres/linux/provision-postgres-linux.sh +++ b/packages/twenty-postgres/linux/provision-postgres-linux.sh @@ -20,7 +20,7 @@ handle_error () { exit 1 } -read -p "This script uses sudo to install postgresql, curl and change different settings, do you want to run this script? [y/N]" AGREEMENT +read -p "This script uses sudo to install PostgreSQL, curl, and configure the system. Do you want to run this script? [y/N] " AGREEMENT if ! echo "$AGREEMENT" | grep -iq "^y"; then exit 1 @@ -55,28 +55,68 @@ echo_header $BLUE " DATABASE SETUP" PG_MAIN_VERSION=15 PG_GRAPHQL_VERSION=1.5.6 -TARGETARCH=$(dpkg --print-architecture) - -# Install PostgresSQL -echo_header $GREEN "Step [1/4]: Installing PostgreSQL..." -sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' -wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null -sudo apt update -y || handle_error "Failed to update package list." -sudo apt install -y postgresql-$PG_MAIN_VERSION postgresql-contrib-$PG_MAIN_VERSION || handle_error "Failed to install PostgreSQL." -sudo apt install -y curl || handle_error "Failed to install curl." - -# Install pg_graphql extensions -echo_header $GREEN "Step [2/4]: Installing GraphQL for PostgreSQL..." -curl -L https://github.com/supabase/pg_graphql/releases/download/v$PG_GRAPHQL_VERSION/pg_graphql-v$PG_GRAPHQL_VERSION-pg$PG_MAIN_VERSION-$TARGETARCH-linux-gnu.deb -o pg_graphql.deb || handle_error "Failed to download pg_graphql package." -sudo dpkg --install pg_graphql.deb || handle_error "Failed to install pg_graphql package." -rm pg_graphql.deb - -# Start postgresql service -echo_header $GREEN "Step [3/4]: Starting PostgreSQL service..." -if sudo service postgresql start; then - echo "PostgreSQL service started successfully." + +if command -v dpkg &> /dev/null; then + TARGETARCH=$(dpkg --print-architecture) +else + TARGETARCH=$(uname -m) +fi + +# Detect package manager and set up PostgreSQL and curl +if command -v dpkg &> /dev/null; then + PACKAGE_MANAGER="dpkg" +elif command -v pacman &> /dev/null; then + PACKAGE_MANAGER="pacman" else - handle_error "Failed to start PostgreSQL service." + handle_error "Unsupported package manager. This script only supports dpkg and pacman." +fi + +# Installation for Debian/Ubuntu +if [ "$PACKAGE_MANAGER" = "dpkg" ]; then + echo_header $GREEN "Step [1/4]: Installing PostgreSQL on Debian/Ubuntu..." + sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null + sudo apt update -y || handle_error "Failed to update package list." + sudo apt install -y postgresql-$PG_MAIN_VERSION postgresql-contrib-$PG_MAIN_VERSION curl || handle_error "Failed to install PostgreSQL or curl." + + echo_header $GREEN "Step [2/4]: Installing GraphQL for PostgreSQL on Debian/Ubuntu..." + curl -L https://github.com/supabase/pg_graphql/releases/download/v$PG_GRAPHQL_VERSION/pg_graphql-v$PG_GRAPHQL_VERSION-pg$PG_MAIN_VERSION-$TARGETARCH-linux-gnu.deb -o pg_graphql.deb || handle_error "Failed to download pg_graphql package." + sudo dpkg --install pg_graphql.deb || handle_error "Failed to install pg_graphql package." + rm pg_graphql.deb + + echo_header $GREEN "Step [3/4]: Starting PostgreSQL service..." + if sudo service postgresql start; then + echo "PostgreSQL service started successfully." + else + handle_error "Failed to start PostgreSQL service." + fi + +# Installation for Arch +elif [ "$PACKAGE_MANAGER" = "pacman" ]; then + echo_header $GREEN "Step [1/4]: Installing PostgreSQL on Arch..." + sudo pacman -Syu --noconfirm || handle_error "Failed to update package list." + sudo pacman -S postgresql postgresql-libs curl --noconfirm || handle_error "Failed to install PostgreSQL or curl." + + echo_header $GREEN "Step [2/4]: Installing GraphQL for PostgreSQL on Arch..." + if ! yay -S --noconfirm pg_graphql && ! paru -S --noconfirm pg_graphql; then + handle_error "Failed to install pg_graphql package from AUR." + fi + + echo_header $GREEN "Step [3/4]: Initializing and starting PostgreSQL service..." + if sudo -u postgres sh -c 'test "$(ls -A /var/lib/postgres/data 2>/dev/null)"'; then + echo "PostgreSQL data directory already contains data. Skipping initdb." + else + sudo -iu postgres initdb --locale en_US.UTF-8 -D /var/lib/postgres/data || handle_error "Failed to initialize PostgreSQL database." + fi + + if [ "$(ps -p 1 -o comm=)" = "systemd" ]; then + sudo systemctl enable postgresql + sudo systemctl start postgresql || handle_error "Failed to start PostgreSQL service." + else + sudo mkdir -p /run/postgresql + sudo chown postgres:postgres /run/postgresql + sudo -iu postgres pg_ctl -D /var/lib/postgres/data -l /var/lib/postgres/logfile start || handle_error "Failed to start PostgreSQL service." + fi fi # Run the init.sql to setup database diff --git a/packages/twenty-server/felix b/packages/twenty-server/felix new file mode 160000 index 000000000000..a33b01797795 --- /dev/null +++ b/packages/twenty-server/felix @@ -0,0 +1 @@ +Subproject commit a33b01797795419edef84f122b5214472648d1ce 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/nest-cli.json b/packages/twenty-server/nest-cli.json index ce6375096831..992b41ad22ec 100644 --- a/packages/twenty-server/nest-cli.json +++ b/packages/twenty-server/nest-cli.json @@ -6,17 +6,21 @@ "builder": "swc", "typeCheck": true, "assets": [ + { + "include": "**/serverless/drivers/constants/base-typescript-project/**", + "outDir": "dist/assets" + }, { "include": "**/serverless/drivers/layers/*/package.json", - "outDir": "dist/src" + "outDir": "dist/assets" }, { "include": "**/serverless/drivers/layers/*/yarn.lock", - "outDir": "dist/src" + "outDir": "dist/assets" }, { "include": "**/serverless/drivers/layers/engine/**", - "outDir": "dist/src" + "outDir": "dist/assets" } ], "watchAssets": true diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 1a60930c4a18..7cedf04348bf 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -1,6 +1,6 @@ { "name": "twenty-server", - "version": "0.24.2", + "version": "0.32.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 new file mode 100644 index 000000000000..04766287433a --- /dev/null +++ b/packages/twenty-server/src/constants/assets-path.ts @@ -0,0 +1,8 @@ +import path from 'path'; + +// 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/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts index dce5a07d99fa..5f212a025a1d 100644 --- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts @@ -12,6 +12,7 @@ import { getDevSeedCompanyCustomFields, getDevSeedPeopleCustomFields, } from 'src/database/typeorm-seeds/metadata/fieldsMetadata'; +import { getDevSeedCustomObjects } from 'src/database/typeorm-seeds/metadata/objectsMetadata'; import { seedCalendarChannels } from 'src/database/typeorm-seeds/workspace/calendar-channel'; import { seedCalendarChannelEventAssociations } from 'src/database/typeorm-seeds/workspace/calendar-channel-event-association'; import { seedCalendarEventParticipants } from 'src/database/typeorm-seeds/workspace/calendar-event-participants'; @@ -39,6 +40,7 @@ import { DataSourceService } from 'src/engine/metadata-modules/data-source/data- import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; +import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { viewPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/view'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; @@ -150,6 +152,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner { objectMetadataMap[STANDARD_OBJECT_IDS.person], workspaceId, ); + await this.seedCustomObjects(workspaceId, dataSourceMetadata.id); await workspaceDataSource.transaction( async (entityManager: EntityManager) => { @@ -217,7 +220,14 @@ export class DataSeedWorkspaceCommand extends CommandRunner { await seedWorkspaceFavorites( viewDefinitionsWithId - .filter((view) => view.key === 'INDEX') + .filter( + (view) => + view.key === 'INDEX' && + shouldSeedWorkspaceFavorite( + view.objectMetadataId, + objectMetadataMap, + ), + ) .map((view) => view.id), entityManager, dataSourceMetadata.schema, @@ -282,4 +292,15 @@ export class DataSeedWorkspaceCommand extends CommandRunner { }); } } + + async seedCustomObjects(workspaceId: string, dataSourceId: string) { + const devSeedCustomObjects = getDevSeedCustomObjects( + workspaceId, + dataSourceId, + ); + + for (const customObject of devSeedCustomObjects) { + await this.objectMetadataService.createOne(customObject); + } + } } diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index 9d98d33abd75..f8207c318b24 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -7,8 +7,7 @@ import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-de import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question'; -import { UpgradeTo0_30CommandModule } from 'src/database/commands/upgrade-version/0-30/0-30-upgrade-version.module'; -import { UpgradeTo0_31CommandModule } from 'src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module'; +import { UpgradeTo0_32CommandModule } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @@ -47,8 +46,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp DataSeedDemoWorkspaceModule, WorkspaceCacheStorageModule, WorkspaceMetadataVersionModule, - UpgradeTo0_30CommandModule, - UpgradeTo0_31CommandModule, + UpgradeTo0_32CommandModule, FeatureFlagModule, ], providers: [ diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-fix-email-field-migration.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-fix-email-field-migration.command.ts deleted file mode 100644 index 9356d7d920c1..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-fix-email-field-migration.command.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { isDefined } from 'class-validator'; -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; -import { PERSON_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { ViewService } from 'src/modules/view/services/view.service'; -@Command({ - name: 'upgrade-0.30:fix-email-field-migration', - description: - 'Fix migration - delete deprecated email fields and add emails to person views', -}) -export class FixEmailFieldsToEmailsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository<Workspace>, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>, - private readonly fieldMetadataService: FieldMetadataService, - private readonly typeORMService: TypeORMService, - private readonly dataSourceService: DataSourceService, - private readonly viewService: ViewService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise<void> { - this.logger.log('Running command to fix migration'); - - for (const workspaceId of workspaceIds) { - let dataSourceMetadata; - - this.logger.log(`Running command for workspace ${workspaceId}`); - try { - dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( - workspaceId, - ); - - if (!dataSourceMetadata) { - throw new Error( - `Could not find dataSourceMetadata for workspace ${workspaceId}`, - ); - } - - const workspaceDataSource = - await this.typeORMService.connectToDataSource(dataSourceMetadata); - - if (!workspaceDataSource) { - throw new Error( - `Could not connect to dataSource for workspace ${workspaceId}`, - ); - } - } catch (error) { - this.logger.log( - chalk.red( - `Could not connect to workspace data source for workspace ${workspaceId}`, - ), - ); - continue; - } - - try { - const deprecatedPersonEmailFieldsMetadata = - await this.fieldMetadataRepository.findBy({ - standardId: PERSON_STANDARD_FIELD_IDS.email, - workspaceId: workspaceId, - }); - - const migratedEmailFieldMetadata = await this.fieldMetadataRepository - .findBy({ - standardId: PERSON_STANDARD_FIELD_IDS.emails, - workspaceId: workspaceId, - }) - .then((fields) => fields[0]); - - const personEmailFieldWasMigratedButHasDuplicate = - deprecatedPersonEmailFieldsMetadata.length > 0 && - isDefined(migratedEmailFieldMetadata); - - if (!personEmailFieldWasMigratedButHasDuplicate) { - this.logger.log( - chalk.yellow('No fields to migrate for workspace ' + workspaceId), - ); - continue; - } - - for (const deprecatedEmailFieldMetadata of deprecatedPersonEmailFieldsMetadata) { - await this.fieldMetadataService.deleteOneField( - { id: deprecatedEmailFieldMetadata.id }, - workspaceId, - ); - this.logger.log( - chalk.green(`Deleted email field for workspace ${workspaceId}.`), - ); - } - - const personObjectMetadaIdForWorkspace = - migratedEmailFieldMetadata.objectMetadataId; - - if (!isDefined(personObjectMetadaIdForWorkspace)) { - this.logger.log( - chalk.red( - `Could not find person object for workspace ${workspaceId}. Could not add emails to person view`, - ), - ); - continue; - } - - const personViewsIds = - await this.viewService.getViewsIdsForObjectMetadataId({ - workspaceId, - objectMetadataId: personObjectMetadaIdForWorkspace as string, - }); - - await this.viewService.addFieldToViews({ - workspaceId: workspaceId, - fieldId: migratedEmailFieldMetadata.id, - viewsIds: personViewsIds, - positions: personViewsIds.reduce((acc, personView) => { - if (!personView.id) { - return acc; - } - acc[personView.id] = 4; - - return acc; - }, []), - }); - this.logger.log(chalk.green(`Added emails to view ${workspaceId}.`)); - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-migrate-email-fields-to-emails.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-migrate-email-fields-to-emails.command.ts deleted file mode 100644 index 3841adc67d3c..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-migrate-email-fields-to-emails.command.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { QueryRunner, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; -import { - FieldMetadataEntity, - FieldMetadataType, -} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; -import { PERSON_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { ViewService } from 'src/modules/view/services/view.service'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; -@Command({ - name: 'upgrade-0.30:migrate-email-fields-to-emails', - description: 'Migrating fields of deprecated type EMAIL to type EMAILS', -}) -export class MigrateEmailFieldsToEmailsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository<Workspace>, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, - private readonly fieldMetadataService: FieldMetadataService, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly typeORMService: TypeORMService, - private readonly dataSourceService: DataSourceService, - private readonly viewService: ViewService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise<void> { - this.logger.log( - 'Running command to migrate email type fields to emails type', - ); - - for (const workspaceId of workspaceIds) { - let dataSourceMetadata; - let workspaceQueryRunner; - - this.logger.log(`Running command for workspace ${workspaceId}`); - try { - dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( - workspaceId, - ); - - if (!dataSourceMetadata) { - throw new Error( - `Could not find dataSourceMetadata for workspace ${workspaceId}`, - ); - } - - const workspaceDataSource = - await this.typeORMService.connectToDataSource(dataSourceMetadata); - - if (!workspaceDataSource) { - throw new Error( - `Could not connect to dataSource for workspace ${workspaceId}`, - ); - } - - workspaceQueryRunner = workspaceDataSource.createQueryRunner(); - - await workspaceQueryRunner.connect(); - } catch (error) { - this.logger.log( - chalk.red( - `Could not connect to workspace data source for workspace ${workspaceId}`, - ), - ); - continue; - } - - try { - const customFieldsWithEmailType = - await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.EMAIL, - isCustom: true, - }, - }); - - await this.migratePersonEmailFieldToEmailsField( - workspaceId, - workspaceQueryRunner, - dataSourceMetadata, - ); - - for (const customFieldWithEmailType of customFieldsWithEmailType) { - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { id: customFieldWithEmailType.objectMetadataId }, - }); - - if (!objectMetadata) { - throw new Error( - `Could not find objectMetadata for field ${customFieldWithEmailType.name}`, - ); - } - - this.logger.log( - `Attempting to migrate custom field ${customFieldWithEmailType.name} on ${objectMetadata.nameSingular}.`, - ); - - const fieldName = customFieldWithEmailType.name; - const { id: _id, ...fieldWithEmailTypeWithoutId } = - customFieldWithEmailType; - - const emailDefaultValue = fieldWithEmailTypeWithoutId.defaultValue; - - const defaultValueForEmailsField = { - primaryEmail: emailDefaultValue, - additionalEmails: null, - }; - - try { - const tmpNewEmailsField = await this.fieldMetadataService.createOne( - { - ...fieldWithEmailTypeWithoutId, - type: FieldMetadataType.EMAILS, - defaultValue: defaultValueForEmailsField, - name: `${fieldName}Tmp`, - } satisfies CreateFieldInput, - ); - - const tableName = computeTableName( - objectMetadata.nameSingular, - objectMetadata.isCustom, - ); - - // Migrate data from email to emails.primaryEmail - await this.migrateDataWithinTable({ - sourceColumnName: `${customFieldWithEmailType.name}`, - targetColumnName: `${tmpNewEmailsField.name}PrimaryEmail`, - tableName, - workspaceQueryRunner, - dataSourceMetadata, - }); - - // Duplicate email field's views behaviour for new emails field - await this.viewService.removeFieldFromViews({ - workspaceId: workspaceId, - fieldId: tmpNewEmailsField.id, - }); - - const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFieldWorkspaceEntity>( - workspaceId, - 'viewField', - ); - const viewFieldsWithDeprecatedField = - await viewFieldRepository.find({ - where: { - fieldMetadataId: customFieldWithEmailType.id, - isVisible: true, - }, - }); - - await this.viewService.addFieldToViews({ - workspaceId: workspaceId, - fieldId: tmpNewEmailsField.id, - viewsIds: viewFieldsWithDeprecatedField - .filter((viewField) => viewField.viewId !== null) - .map((viewField) => viewField.viewId as string), - positions: viewFieldsWithDeprecatedField.reduce( - (acc, viewField) => { - if (!viewField.viewId) { - return acc; - } - acc[viewField.viewId] = viewField.position; - - return acc; - }, - [], - ), - }); - - // Delete email field - await this.fieldMetadataService.deleteOneField( - { id: customFieldWithEmailType.id }, - workspaceId, - ); - - // Rename temporary emails field - await this.fieldMetadataService.updateOne(tmpNewEmailsField.id, { - id: tmpNewEmailsField.id, - workspaceId: tmpNewEmailsField.workspaceId, - name: `${fieldName}`, - isCustom: false, - }); - - this.logger.log( - `Migration of ${customFieldWithEmailType.name} on ${objectMetadata.nameSingular} done!`, - ); - } catch (error) { - this.logger.log( - `Failed to migrate field ${customFieldWithEmailType.name} on ${objectMetadata.nameSingular}, rolling back.`, - ); - - // Re-create initial field if it was deleted - const initialField = - await this.fieldMetadataService.findOneWithinWorkspace( - workspaceId, - { - where: { - name: `${customFieldWithEmailType.name}`, - objectMetadataId: customFieldWithEmailType.objectMetadataId, - }, - }, - ); - - const tmpNewEmailsField = - await this.fieldMetadataService.findOneWithinWorkspace( - workspaceId, - { - where: { - name: `${customFieldWithEmailType.name}Tmp`, - objectMetadataId: customFieldWithEmailType.objectMetadataId, - }, - }, - ); - - if (!initialField) { - this.logger.log( - `Re-creating initial Email field ${customFieldWithEmailType.name} but of type emails`, // Cannot create email fields anymore - ); - const restoredField = await this.fieldMetadataService.createOne({ - ...customFieldWithEmailType, - defaultValue: defaultValueForEmailsField, - type: FieldMetadataType.EMAILS, - }); - const tableName = computeTableName( - objectMetadata.nameSingular, - objectMetadata.isCustom, - ); - - if (tmpNewEmailsField) { - this.logger.log( - `Restoring data in field ${customFieldWithEmailType.name}`, - ); - await this.migrateDataWithinTable({ - sourceColumnName: `${tmpNewEmailsField.name}PrimaryEmail`, - targetColumnName: `${restoredField.name}PrimaryEmail`, - tableName, - workspaceQueryRunner, - dataSourceMetadata, - }); - } else { - this.logger.log( - `Failed to restore data in link field ${customFieldWithEmailType.name}`, - ); - } - } - - if (tmpNewEmailsField) { - await this.fieldMetadataService.deleteOneField( - { id: tmpNewEmailsField.id }, - workspaceId, - ); - } - } - } - } catch (error) { - await workspaceQueryRunner.release(); - - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - await workspaceQueryRunner.release(); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } - - private async migratePersonEmailFieldToEmailsField( - workspaceId: string, - workspaceQueryRunner: any, - dataSourceMetadata: any, - ) { - this.logger.log(`Migrating person email field of type EMAIL to EMAILS`); - - await this.migrateDataWithinTable({ - sourceColumnName: 'email', - targetColumnName: 'emailsPrimaryEmail', - tableName: 'person', - workspaceQueryRunner, - dataSourceMetadata, - }); - - const personEmailFieldMetadata = await this.fieldMetadataRepository.findOne( - { - where: { - workspaceId, - standardId: PERSON_STANDARD_FIELD_IDS.email, - }, - }, - ); - - if (personEmailFieldMetadata) { - await this.fieldMetadataService.deleteOneField( - { - id: personEmailFieldMetadata.id, - }, - workspaceId, - ); - } - } - - private async migrateDataWithinTable({ - sourceColumnName, - targetColumnName, - tableName, - workspaceQueryRunner, - dataSourceMetadata, - }: { - sourceColumnName: string; - targetColumnName: string; - tableName: string; - workspaceQueryRunner: QueryRunner; - dataSourceMetadata: DataSourceEntity; - }) { - await workspaceQueryRunner.query( - `UPDATE "${dataSourceMetadata.schema}"."${tableName}" SET "${targetColumnName}" = "${sourceColumnName}"`, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-set-stale-message-sync-back-to-pending.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-set-stale-message-sync-back-to-pending.ts deleted file mode 100644 index 3d1744910f48..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-set-stale-message-sync-back-to-pending.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { IsNull, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { - MessageChannelSyncStage, - MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -@Command({ - name: 'upgrade-0.30:set-stale-message-sync-back-to-pending', - description: 'Set stale message sync back to pending', -}) -export class SetStaleMessageSyncBackToPendingCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository<Workspace>, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise<void> { - this.logger.log( - 'Running command to set stale message sync back to pending', - ); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - workspaceId, - ); - - const messageChannelRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>( - workspaceId, - 'messageChannel', - ); - - dataSource.transaction(async (entityManager) => { - await messageChannelRepository.update( - { - syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, - syncStageStartedAt: IsNull(), - }, - { - syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING, - }, - entityManager, - ); - - await messageChannelRepository.update( - { - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, - syncStageStartedAt: IsNull(), - }, - { - syncStage: - MessageChannelSyncStage.PARTIAL_MESSAGE_LIST_FETCH_PENDING, - }, - entityManager, - ); - }); - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-upgrade-version.command.ts deleted file mode 100644 index 7bf2a9ddd6be..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-upgrade-version.command.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { FixEmailFieldsToEmailsCommand } from 'src/database/commands/upgrade-version/0-30/0-30-fix-email-field-migration.command'; -import { MigrateEmailFieldsToEmailsCommand } from 'src/database/commands/upgrade-version/0-30/0-30-migrate-email-fields-to-emails.command'; -import { SetStaleMessageSyncBackToPendingCommand } from 'src/database/commands/upgrade-version/0-30/0-30-set-stale-message-sync-back-to-pending'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; - -interface UpdateTo0_30CommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.30', - description: 'Upgrade to 0.30', -}) -export class UpgradeTo0_30Command extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository<Workspace>, - private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, - private readonly migrateEmailFieldsToEmails: MigrateEmailFieldsToEmailsCommand, - private readonly setStaleMessageSyncBackToPendingCommand: SetStaleMessageSyncBackToPendingCommand, - private readonly fixEmailFieldsToEmailsCommand: FixEmailFieldsToEmailsCommand, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - passedParam: string[], - options: UpdateTo0_30CommandOptions, - workspaceIds: string[], - ): Promise<void> { - await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand( - passedParam, - { - ...options, - force: true, - }, - workspaceIds, - ); - await this.migrateEmailFieldsToEmails.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - await this.setStaleMessageSyncBackToPendingCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - await this.fixEmailFieldsToEmailsCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-upgrade-version.module.ts deleted file mode 100644 index 7938ea4f902e..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-30/0-30-upgrade-version.module.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FixEmailFieldsToEmailsCommand } from 'src/database/commands/upgrade-version/0-30/0-30-fix-email-field-migration.command'; -import { MigrateEmailFieldsToEmailsCommand } from 'src/database/commands/upgrade-version/0-30/0-30-migrate-email-fields-to-emails.command'; -import { SetStaleMessageSyncBackToPendingCommand } from 'src/database/commands/upgrade-version/0-30/0-30-set-stale-message-sync-back-to-pending'; -import { UpgradeTo0_30Command } from 'src/database/commands/upgrade-version/0-30/0-30-upgrade-version.command'; -import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; -import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; -import { ViewModule } from 'src/modules/view/view.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([Workspace], 'core'), - WorkspaceSyncMetadataCommandsModule, - DataSourceModule, - WorkspaceMetadataVersionModule, - FieldMetadataModule, - TypeOrmModule.forFeature( - [FieldMetadataEntity, ObjectMetadataEntity], - 'metadata', - ), - TypeORMModule, - ViewModule, - ], - providers: [ - UpgradeTo0_30Command, - MigrateEmailFieldsToEmailsCommand, - SetStaleMessageSyncBackToPendingCommand, - FixEmailFieldsToEmailsCommand, - ], -}) -export class UpgradeTo0_30CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts deleted file mode 100644 index ed94537b093f..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { In, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@Command({ - name: 'upgrade-0.31:backfill-workspace-favorites-migration', - description: 'Create a workspace favorite for all workspace views', -}) -export class BackfillWorkspaceFavoritesCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository<Workspace>, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise<void> { - this.logger.log('Running command to fix migration'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - const allWorkspaceIndexViews = await this.getIndexViews(workspaceId); - - const activeWorkspaceIndexViews = - await this.filterViewsWithoutObjectMetadata( - workspaceId, - allWorkspaceIndexViews, - ); - - await this.createViewWorkspaceFavorites( - workspaceId, - activeWorkspaceIndexViews.map((view) => view.id), - ); - - this.logger.log( - chalk.green(`Backfilled workspace favorites to ${workspaceId}.`), - ); - - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } - - private async getIndexViews( - workspaceId: string, - ): Promise<ViewWorkspaceEntity[]> { - const viewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>( - workspaceId, - 'view', - false, - ); - - return viewRepository.find({ - where: { - key: 'INDEX', - }, - }); - } - - private async filterViewsWithoutObjectMetadata( - workspaceId: string, - views: ViewWorkspaceEntity[], - ): Promise<ViewWorkspaceEntity[]> { - const viewObjectMetadataIds = views.map((view) => view.objectMetadataId); - - const objectMetadataEntities = await this.objectMetadataRepository.find({ - where: { - workspaceId, - id: In(viewObjectMetadataIds), - }, - }); - - const objectMetadataIds = new Set( - objectMetadataEntities.map((entity) => entity.id), - ); - - return views.filter((view) => objectMetadataIds.has(view.objectMetadataId)); - } - - private async createViewWorkspaceFavorites( - workspaceId: string, - viewIds: string[], - ) { - const favoriteRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace<FavoriteWorkspaceEntity>( - workspaceId, - 'favorite', - ); - - let nextFavoritePosition = await favoriteRepository.count(); - - for (const viewId of viewIds) { - const existingFavorites = await favoriteRepository.find({ - where: { - viewId, - }, - }); - - if (existingFavorites.length) { - continue; - } - - await favoriteRepository.insert( - favoriteRepository.create({ - viewId, - position: nextFavoritePosition, - }), - ); - - nextFavoritePosition++; - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command.ts deleted file mode 100644 index 727d2a8647fa..000000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { In, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@Command({ - name: 'upgrade-0.31:clean-views-with-deleted-object-metadata', - description: 'Clean views with deleted object metadata', -}) -export class CleanViewsWithDeletedObjectMetadataCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository<Workspace>, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise<void> { - this.logger.log('Running command to fix migration'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - this.logger.log(chalk.green(`Cleaning views of ${workspaceId}.`)); - - await this.cleanViewsWithDeletedObjectMetadata( - workspaceId, - _options.dryRun ?? false, - ); - - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } - - private async cleanViewsWithDeletedObjectMetadata( - workspaceId: string, - dryRun: boolean, - ): Promise<void> { - const viewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>( - workspaceId, - 'view', - false, - ); - - const allViews = await viewRepository.find(); - - const viewObjectMetadataIds = allViews.map((view) => view.objectMetadataId); - - const objectMetadataEntities = await this.objectMetadataRepository.find({ - where: { - id: In(viewObjectMetadataIds), - }, - }); - - const validObjectMetadataIds = new Set( - objectMetadataEntities.map((entity) => entity.id), - ); - - const viewIdsToDelete = allViews - .filter((view) => !validObjectMetadataIds.has(view.objectMetadataId)) - .map((view) => view.id); - - if (dryRun) { - this.logger.log( - chalk.green( - `Found ${viewIdsToDelete.length} views to clean in workspace ${workspaceId}.`, - ), - ); - } - - if (viewIdsToDelete.length > 0 && !dryRun) { - await viewRepository.delete(viewIdsToDelete); - this.logger.log(chalk.green(`Cleaning ${viewIdsToDelete.length} views.`)); - } - - if (viewIdsToDelete.length === 0) { - this.logger.log(chalk.green(`No views to clean.`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-enforce-unique-constraints.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-enforce-unique-constraints.command.ts new file mode 100644 index 000000000000..70576e9bef24 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-enforce-unique-constraints.command.ts @@ -0,0 +1,303 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command, Option } from 'nest-commander'; +import { IsNull, Repository } from 'typeorm'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +interface EnforceUniqueConstraintsCommandOptions + extends ActiveWorkspacesCommandOptions { + person?: boolean; + company?: boolean; + viewField?: boolean; + viewSort?: boolean; +} + +@Command({ + name: 'upgrade-0.32:enforce-unique-constraints', + description: + 'Enforce unique constraints on company domainName, person emailsPrimaryEmail, ViewField, and ViewSort', +}) +export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository<Workspace>, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository); + } + + @Option({ + flags: '--person', + description: 'Enforce unique constraints on person emailsPrimaryEmail', + }) + parsePerson() { + return true; + } + + @Option({ + flags: '--company', + description: 'Enforce unique constraints on company domainName', + }) + parseCompany() { + return true; + } + + @Option({ + flags: '--view-field', + description: 'Enforce unique constraints on ViewField', + }) + parseViewField() { + return true; + } + + @Option({ + flags: '--view-sort', + description: 'Enforce unique constraints on ViewSort', + }) + parseViewSort() { + return true; + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + options: EnforceUniqueConstraintsCommandOptions, + workspaceIds: string[], + ): Promise<void> { + this.logger.log('Running command to enforce unique constraints'); + + for (const workspaceId of workspaceIds) { + this.logger.log(`Running command for workspace ${workspaceId}`); + + try { + await this.enforceUniqueConstraintsForWorkspace( + workspaceId, + options.dryRun ?? false, + options, + ); + + await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( + workspaceId, + ); + } catch (error) { + this.logger.log( + chalk.red( + `Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`, + ), + ); + continue; + } finally { + this.logger.log( + chalk.green(`Finished running command for workspace ${workspaceId}.`), + ); + } + } + + this.logger.log(chalk.green(`Command completed!`)); + } + + private async enforceUniqueConstraintsForWorkspace( + workspaceId: string, + dryRun: boolean, + options: EnforceUniqueConstraintsCommandOptions, + ): Promise<void> { + if (options.company) { + await this.enforceUniqueCompanyDomainName(workspaceId, dryRun); + } + if (options.person) { + await this.enforceUniquePersonEmail(workspaceId, dryRun); + } + if (options.viewField) { + await this.enforceUniqueViewField(workspaceId, dryRun); + } + if (options.viewSort) { + await this.enforceUniqueViewSort(workspaceId, dryRun); + } + } + + private async enforceUniqueCompanyDomainName( + workspaceId: string, + dryRun: boolean, + ): Promise<void> { + const companyRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'company', + ); + + const duplicates = await companyRepository + .createQueryBuilder('company') + .select('company.domainNamePrimaryLinkUrl') + .addSelect('COUNT(*)', 'count') + .where('company.deletedAt IS NULL') + .where('company.domainNamePrimaryLinkUrl IS NOT NULL') + .where("company.domainNamePrimaryLinkUrl != ''") + .groupBy('company.domainNamePrimaryLinkUrl') + .having('COUNT(*) > 1') + .getRawMany(); + + for (const duplicate of duplicates) { + const { company_domainNamePrimaryLinkUrl } = duplicate; + const companies = await companyRepository.find({ + where: { + domainName: { + primaryLinkUrl: company_domainNamePrimaryLinkUrl, + }, + deletedAt: IsNull(), + }, + order: { createdAt: 'DESC' }, + }); + + for (let i = 1; i < companies.length; i++) { + const newdomainNamePrimaryLinkUrl = `${company_domainNamePrimaryLinkUrl}${i}`; + + if (!dryRun) { + await companyRepository.update(companies[i].id, { + domainNamePrimaryLinkUrl: newdomainNamePrimaryLinkUrl, + }); + } + this.logger.log( + chalk.yellow( + `Updated company ${companies[i].id} domainName from ${company_domainNamePrimaryLinkUrl} to ${newdomainNamePrimaryLinkUrl}`, + ), + ); + } + } + } + + private async enforceUniquePersonEmail( + workspaceId: string, + dryRun: boolean, + ): Promise<void> { + const personRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'person', + ); + + const duplicates = await personRepository + .createQueryBuilder('person') + .select('person.emailsPrimaryEmail') + .addSelect('COUNT(*)', 'count') + .where('person.deletedAt IS NULL') + .where('person.emailsPrimaryEmail IS NOT NULL') + .where("person.emailsPrimaryEmail != ''") + .groupBy('person.emailsPrimaryEmail') + .having('COUNT(*) > 1') + .getRawMany(); + + for (const duplicate of duplicates) { + const { person_emailsPrimaryEmail } = duplicate; + const persons = await personRepository.find({ + where: { + emails: { + primaryEmail: person_emailsPrimaryEmail, + }, + deletedAt: IsNull(), + }, + order: { createdAt: 'DESC' }, + }); + + for (let i = 1; i < persons.length; i++) { + const newEmail = person_emailsPrimaryEmail?.includes('@') + ? `${person_emailsPrimaryEmail.split('@')[0]}+${i}@${person_emailsPrimaryEmail.split('@')[1]}` + : `${person_emailsPrimaryEmail}+${i}`; + + if (!dryRun) { + await personRepository.update(persons[i].id, { + emailsPrimaryEmail: newEmail, + }); + } + this.logger.log( + chalk.yellow( + `Updated person ${persons[i].id} emailsPrimaryEmail from ${person_emailsPrimaryEmail} to ${newEmail}`, + ), + ); + } + } + } + + private async enforceUniqueViewField( + workspaceId: string, + dryRun: boolean, + ): Promise<void> { + const viewFieldRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewField', + ); + + const duplicates = await viewFieldRepository + .createQueryBuilder('viewField') + .select(['viewField.fieldMetadataId', 'viewField.viewId']) + .addSelect('COUNT(*)', 'count') + .where('viewField.deletedAt IS NULL') + .groupBy('viewField.fieldMetadataId, viewField.viewId') + .having('COUNT(*) > 1') + .getRawMany(); + + for (const duplicate of duplicates) { + const { fieldMetadataId, viewId } = duplicate; + const viewFields = await viewFieldRepository.find({ + where: { fieldMetadataId, viewId, deletedAt: IsNull() }, + order: { createdAt: 'DESC' }, + }); + + for (let i = 1; i < viewFields.length; i++) { + if (!dryRun) { + await viewFieldRepository.softDelete(viewFields[i].id); + } + this.logger.log( + chalk.yellow( + `Soft deleted duplicate ViewField ${viewFields[i].id} for fieldMetadataId ${fieldMetadataId} and viewId ${viewId}`, + ), + ); + } + } + } + + private async enforceUniqueViewSort( + workspaceId: string, + dryRun: boolean, + ): Promise<void> { + const viewSortRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewSort', + ); + + const duplicates = await viewSortRepository + .createQueryBuilder('viewSort') + .select(['viewSort.fieldMetadataId', 'viewSort.viewId']) + .addSelect('COUNT(*)', 'count') + .where('viewSort.deletedAt IS NULL') + .groupBy('viewSort.fieldMetadataId, viewSort.viewId') + .having('COUNT(*) > 1') + .getRawMany(); + + for (const duplicate of duplicates) { + const { fieldMetadataId, viewId } = duplicate; + const viewSorts = await viewSortRepository.find({ + where: { fieldMetadataId, viewId, deletedAt: IsNull() }, + order: { createdAt: 'DESC' }, + }); + + for (let i = 1; i < viewSorts.length; i++) { + if (!dryRun) { + await viewSortRepository.softDelete(viewSorts[i].id); + } + this.logger.log( + chalk.yellow( + `Soft deleted duplicate ViewSort ${viewSorts[i].id} for fieldMetadataId ${fieldMetadataId} and viewId ${viewId}`, + ), + ); + } + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts similarity index 54% rename from packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts index e53dd5e4b7f6..44d5c4a639ef 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts @@ -4,33 +4,32 @@ import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BackfillWorkspaceFavoritesCommand } from 'src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command'; -import { CleanViewsWithDeletedObjectMetadataCommand } from 'src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; -interface UpdateTo0_31CommandOptions { +import { EnforceUniqueConstraintsCommand } from './0-32-enforce-unique-constraints.command'; + +interface UpdateTo0_32CommandOptions { workspaceId?: string; } @Command({ - name: 'upgrade-0.31', - description: 'Upgrade to 0.31', + name: 'upgrade-0.32', + description: 'Upgrade to 0.32', }) -export class UpgradeTo0_31Command extends ActiveWorkspacesCommandRunner { +export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository<Workspace>, private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, - private readonly backfillWorkspaceFavoritesCommand: BackfillWorkspaceFavoritesCommand, - private readonly cleanViewsWithDeletedObjectMetadataCommand: CleanViewsWithDeletedObjectMetadataCommand, + private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand, ) { super(workspaceRepository); } async executeActiveWorkspacesCommand( passedParam: string[], - options: UpdateTo0_31CommandOptions, + options: UpdateTo0_32CommandOptions, workspaceIds: string[], ): Promise<void> { await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand( @@ -41,12 +40,8 @@ export class UpgradeTo0_31Command extends ActiveWorkspacesCommandRunner { }, workspaceIds, ); - await this.cleanViewsWithDeletedObjectMetadataCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - await this.backfillWorkspaceFavoritesCommand.executeActiveWorkspacesCommand( + + await this.enforceUniqueConstraintsCommand.executeActiveWorkspacesCommand( passedParam, options, workspaceIds, diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts similarity index 51% rename from packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts rename to packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts index 32623fe43207..1a6ec704f424 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { BackfillWorkspaceFavoritesCommand } from 'src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command'; -import { CleanViewsWithDeletedObjectMetadataCommand } from 'src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command'; -import { UpgradeTo0_31Command } from 'src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command'; +import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-32/0-32-enforce-unique-constraints.command'; +import { UpgradeTo0_32Command } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; @@ -14,10 +13,6 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'), WorkspaceSyncMetadataCommandsModule, ], - providers: [ - UpgradeTo0_31Command, - BackfillWorkspaceFavoritesCommand, - CleanViewsWithDeletedObjectMetadataCommand, - ], + providers: [UpgradeTo0_32Command, EnforceUniqueConstraintsCommand], }) -export class UpgradeTo0_31CommandModule {} +export class UpgradeTo0_32CommandModule {} diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index 8d3036235454..c618677d3733 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -43,7 +43,7 @@ export const seedFeatureFlags = async ( { key: FeatureFlagKey.IsWorkflowEnabled, workspaceId: workspaceId, - value: false, + value: true, }, { key: FeatureFlagKey.IsMessageThreadSubscriberEnabled, @@ -53,13 +53,33 @@ export const seedFeatureFlags = async ( { key: FeatureFlagKey.IsWorkspaceFavoriteEnabled, workspaceId: workspaceId, - value: false, + value: true, + }, + { + key: FeatureFlagKey.IsSearchEnabled, + workspaceId: workspaceId, + value: true, + }, + { + key: FeatureFlagKey.IsWorkspaceMigratedForSearch, + workspaceId: workspaceId, + value: true, + }, + { + key: FeatureFlagKey.IsAnalyticsV2Enabled, + workspaceId: workspaceId, + value: true, }, { - key: FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + key: FeatureFlagKey.IsGmailSendEmailScopeEnabled, workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IsUniqueIndexesEnabled, + workspaceId: workspaceId, + value: false, + }, ]) .execute(); }; 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 ec81351541be..a0b8081c9d59 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts @@ -15,6 +15,8 @@ export const getDevSeedCompanyCustomFields = ( icon: 'IconAdCircle', isActive: true, isNullable: false, + isUnique: false, + defaultValue: "''", objectMetadataId, }, { @@ -26,6 +28,7 @@ export const getDevSeedCompanyCustomFields = ( icon: 'IconVideo', isActive: true, isNullable: true, + isUnique: false, objectMetadataId, }, { @@ -37,6 +40,7 @@ export const getDevSeedCompanyCustomFields = ( icon: 'IconHome', isActive: true, isNullable: true, + isUnique: false, objectMetadataId, options: [ { @@ -68,6 +72,7 @@ export const getDevSeedCompanyCustomFields = ( icon: 'IconBrandVisa', isActive: true, isNullable: true, + isUnique: false, objectMetadataId, defaultValue: false, }, @@ -88,28 +93,38 @@ export const getDevSeedPeopleCustomFields = ( icon: 'IconNote', isActive: true, isNullable: true, + isUnique: false, objectMetadataId, }, { workspaceId, - type: FieldMetadataType.PHONE, + type: FieldMetadataType.PHONES, name: 'whatsapp', label: 'Whatsapp', description: "Contact's Whatsapp Number", icon: 'IconBrandWhatsapp', isActive: true, isNullable: false, + isUnique: false, + defaultValue: [ + { + primaryPhoneNumber: '', + primaryPhoneCountryCode: '', + additionalPhones: {}, + }, + ], objectMetadataId, }, { workspaceId, type: FieldMetadataType.MULTI_SELECT, - name: 'workPrefereance', + name: 'workPreference', label: 'Work Preference', description: "Person's Work Preference", icon: 'IconHome', isActive: true, isNullable: true, + isUnique: false, objectMetadataId, options: [ { @@ -141,6 +156,7 @@ export const getDevSeedPeopleCustomFields = ( icon: 'IconStars', isActive: true, isNullable: true, + isUnique: false, objectMetadataId, options: [ { diff --git a/packages/twenty-server/src/database/typeorm-seeds/metadata/objectsMetadata.ts b/packages/twenty-server/src/database/typeorm-seeds/metadata/objectsMetadata.ts new file mode 100644 index 000000000000..640b0b4c1d37 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm-seeds/metadata/objectsMetadata.ts @@ -0,0 +1,20 @@ +import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; + +export const getDevSeedCustomObjects = ( + workspaceId: string, + dataSourceId: string, +): CreateObjectInput[] => { + return [ + { + workspaceId, + dataSourceId, + labelPlural: 'Rockets', + labelSingular: 'Rocket', + namePlural: 'rockets', + nameSingular: 'rocket', + description: 'A rocket', + icon: 'IconRocket', + isRemote: false, + }, + ]; +}; diff --git a/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-channel-event-association.ts b/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-channel-event-association.ts index 59530a45edd3..7e20a6ab5a33 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-channel-event-association.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-channel-event-association.ts @@ -14,6 +14,7 @@ export const seedCalendarChannelEventAssociations = async ( 'calendarChannelId', 'calendarEventId', 'eventExternalId', + 'recurringEventExternalId', ]) .orIgnore() .values([ @@ -22,6 +23,7 @@ export const seedCalendarChannelEventAssociations = async ( calendarChannelId: '59efdefe-a40f-4faf-bb9f-c6f9945b8203', calendarEventId: '86083141-1c0e-494c-a1b6-85b1c6fefaa5', eventExternalId: 'exampleExternalId', + recurringEventExternalId: 'exampleRecurringExternalId', }, ]) .execute(); diff --git a/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-events.ts b/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-events.ts index 7624c290cb7d..6969213f7689 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-events.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/workspace/calendar-events.ts @@ -24,7 +24,6 @@ export const seedCalendarEvents = async ( 'conferenceSolution', 'conferenceLinkPrimaryLinkLabel', 'conferenceLinkPrimaryLinkUrl', - 'recurringEventExternalId', ]) .orIgnore() .values([ @@ -43,7 +42,6 @@ export const seedCalendarEvents = async ( conferenceSolution: 'Zoom', conferenceLinkPrimaryLinkLabel: 'https://zoom.us/j/1234567890', conferenceLinkPrimaryLinkUrl: 'https://zoom.us/j/1234567890', - recurringEventExternalId: 'recurring1', }, ]) .execute(); diff --git a/packages/twenty-server/src/database/typeorm-seeds/workspace/people.ts b/packages/twenty-server/src/database/typeorm-seeds/workspace/people.ts index 6624fd50331e..22adfe0142e9 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/workspace/people.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/workspace/people.ts @@ -34,12 +34,14 @@ export const seedPeople = async ( 'id', 'nameFirstName', 'nameLastName', - 'phone', + 'phonesPrimaryPhoneCountryCode', + 'phonesPrimaryPhoneNumber', 'city', 'companyId', 'emailsPrimaryEmail', 'position', - 'whatsapp', + 'whatsappPrimaryPhoneCountryCode', + 'whatsappPrimaryPhoneNumber', 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', @@ -50,12 +52,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.CHRISTOPH, nameFirstName: 'Christoph', nameLastName: 'Callisto', - phone: '+33789012345', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '789012345', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.LINKEDIN, emailsPrimaryEmail: 'christoph.calisto@linkedin.com', position: 1, - whatsapp: '+33789012345', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '789012345', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -64,12 +68,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.SYLVIE, nameFirstName: 'Sylvie', nameLastName: 'Palmer', - phone: '+33780123456', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '780123456', city: 'Los Angeles', companyId: DEV_SEED_COMPANY_IDS.LINKEDIN, emailsPrimaryEmail: 'sylvie.palmer@linkedin.com', position: 2, - whatsapp: '+33780123456', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '780123456', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -78,12 +84,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.CHRISTOPHER_G, nameFirstName: 'Christopher', nameLastName: 'Gonzalez', - phone: '+33789012345', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '789012345', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.QONTO, emailsPrimaryEmail: 'christopher.gonzalez@qonto.com', position: 3, - whatsapp: '+33789012345', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '789012345', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -92,12 +100,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.ASHLEY, nameFirstName: 'Ashley', nameLastName: 'Parker', - phone: '+33780123456', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '780123456', city: 'Los Angeles', companyId: DEV_SEED_COMPANY_IDS.QONTO, emailsPrimaryEmail: 'ashley.parker@qonto.com', position: 4, - whatsapp: '+33780123456', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '780123456', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -106,12 +116,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.NICHOLAS, nameFirstName: 'Nicholas', nameLastName: 'Wright', - phone: '+33781234567', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '781234567', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.MICROSOFT, emailsPrimaryEmail: 'nicholas.wright@microsoft.com', position: 5, - whatsapp: '+33781234567', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '781234567', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -120,12 +132,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.ISABELLA, nameFirstName: 'Isabella', nameLastName: 'Scott', - phone: '+33782345678', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '782345678', city: 'New York', companyId: DEV_SEED_COMPANY_IDS.MICROSOFT, emailsPrimaryEmail: 'isabella.scott@microsoft.com', position: 6, - whatsapp: '+33782345678', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '782345678', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -134,12 +148,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.MATTHEW, nameFirstName: 'Matthew', nameLastName: 'Green', - phone: '+33783456789', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '783456789', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.MICROSOFT, emailsPrimaryEmail: 'matthew.green@microsoft.com', position: 7, - whatsapp: '+33783456789', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '783456789', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -148,12 +164,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.ELIZABETH, nameFirstName: 'Elizabeth', nameLastName: 'Baker', - phone: '+33784567890', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '784567890', city: 'New York', companyId: DEV_SEED_COMPANY_IDS.AIRBNB, emailsPrimaryEmail: 'elizabeth.baker@airbnb.com', position: 8, - whatsapp: '+33784567890', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '784567890', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -162,12 +180,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.CHRISTOPHER_N, nameFirstName: 'Christopher', nameLastName: 'Nelson', - phone: '+33785678901', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '785678901', city: 'San Francisco', companyId: DEV_SEED_COMPANY_IDS.AIRBNB, emailsPrimaryEmail: 'christopher.nelson@airbnb.com', position: 9, - whatsapp: '+33785678901', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '785678901', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -176,12 +196,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.AVERY, nameFirstName: 'Avery', nameLastName: 'Carter', - phone: '+33786789012', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '786789012', city: 'New York', companyId: DEV_SEED_COMPANY_IDS.AIRBNB, emailsPrimaryEmail: 'avery.carter@airbnb.com', position: 10, - whatsapp: '+33786789012', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '786789012', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -190,12 +212,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.ETHAN, nameFirstName: 'Ethan', nameLastName: 'Mitchell', - phone: '+33787890123', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '787890123', city: 'Los Angeles', companyId: DEV_SEED_COMPANY_IDS.GOOGLE, emailsPrimaryEmail: 'ethan.mitchell@google.com', position: 11, - whatsapp: '+33787890123', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '787890123', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -204,12 +228,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.MADISON, nameFirstName: 'Madison', nameLastName: 'Perez', - phone: '+33788901234', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '788901234', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.GOOGLE, emailsPrimaryEmail: 'madison.perez@google.com', position: 12, - whatsapp: '+33788901234', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '788901234', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -218,12 +244,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.BERTRAND, nameFirstName: 'Bertrand', nameLastName: 'Voulzy', - phone: '+33788901234', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '788901234', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.GOOGLE, emailsPrimaryEmail: 'bertrand.voulzy@google.com', position: 13, - whatsapp: '+33788901234', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '788901234', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -232,12 +260,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.LOUIS, nameFirstName: 'Louis', nameLastName: 'Duss', - phone: '+33788901234', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '789012345', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.GOOGLE, emailsPrimaryEmail: 'louis.duss@google.com', position: 14, - whatsapp: '+33788901234', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '789012345', createdBySource: 'MANUAL', createdByWorkspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM, createdByName: 'Tim Apple', @@ -246,12 +276,14 @@ export const seedPeople = async ( id: DEV_SEED_PERSON_IDS.LORIE, nameFirstName: 'Lorie', nameLastName: 'Vladim', - phone: '+33788901235', + phonePrimaryPhoneCountryCode: '+33', + phonePrimaryPhoneNumber: '788901235', city: 'Seattle', companyId: DEV_SEED_COMPANY_IDS.GOOGLE, emailsPrimaryEmail: 'lorie.vladim@google.com', position: 15, - whatsapp: '+33788901235', + whatsappPrimaryPhoneCountryCode: '+33', + whatsappPrimaryPhoneNumber: '788901235', createdBySource: 'MANUAL', createdByWorkspaceMemberId: null, createdByName: '', diff --git a/packages/twenty-server/src/database/typeorm/core/core.datasource.ts b/packages/twenty-server/src/database/typeorm/core/core.datasource.ts index 0395f8cf2ae1..1f12bb4cd12f 100644 --- a/packages/twenty-server/src/database/typeorm/core/core.datasource.ts +++ b/packages/twenty-server/src/database/typeorm/core/core.datasource.ts @@ -17,6 +17,7 @@ export const typeORMCoreModuleOptions: TypeOrmModuleOptions = { synchronize: false, migrationsRun: false, migrationsTableName: '_typeorm_migrations', + metadataTableName: '_typeorm_generated_columns_and_materialized_views', migrations: [ `${isJest ? '' : 'dist/'}src/database/typeorm/core/migrations/*{.ts,.js}`, ], diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/1728314605995-add_typeormGeneratedColumnsAndMaterializedViews.ts b/packages/twenty-server/src/database/typeorm/core/migrations/1728314605995-add_typeormGeneratedColumnsAndMaterializedViews.ts new file mode 100644 index 000000000000..28c56236e658 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/1728314605995-add_typeormGeneratedColumnsAndMaterializedViews.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTypeormGeneratedColumns1728314605995 + implements MigrationInterface +{ + name = 'AddTypeormGeneratedColumnsAndMaterializedViews1728314605995'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "core"."_typeorm_generated_columns_and_materialized_views" ( + "type" character varying NOT NULL, + "database" character varying, + "schema" character varying, + "table" character varying, + "name" character varying, + "value" text + ) + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `DROP TABLE "core"."_typeorm_generated_columns_and_materialized_views"`, + ); + } +} 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 new file mode 100644 index 000000000000..59a1828627ea --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIndexType1725893697807 implements MigrationInterface { + name = 'AddIndexType1725893697807'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TYPE "metadata"."indexMetadata_indextype_enum" AS ENUM('BTREE', 'GIN')`, + ); + + await queryRunner.query(` + ALTER TABLE metadata."indexMetadata" + ADD COLUMN "indexType" metadata."indexMetadata_indextype_enum" NOT NULL DEFAULT 'BTREE'; + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE metadata."indexMetadata" DROP COLUMN "indexType" + `); + + await queryRunner.query( + `DROP TYPE metadata."indexMetadata_indextype_enum"`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1726240847733-removeServerlessSourceCodeHashColumn.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726240847733-removeServerlessSourceCodeHashColumn.ts new file mode 100644 index 000000000000..277c739a3bcb --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726240847733-removeServerlessSourceCodeHashColumn.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveServerlessSourceCodeHashColumn1726240847733 + implements MigrationInterface +{ + name = 'RemoveServerlessSourceCodeHashColumn1726240847733'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."serverlessFunction" DROP COLUMN "sourceCodeHash"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."serverlessFunction" ADD "sourceCodeHash" character varying NOT NULL`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368824-migrationDebt.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368824-migrationDebt.ts new file mode 100644 index 000000000000..06eb90d488b3 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368824-migrationDebt.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MigrationDebt1726757368824 implements MigrationInterface { + name = 'MigrationDebt1726757368824'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TYPE "metadata"."relationMetadata_ondeleteaction_enum" RENAME TO "relationMetadata_ondeleteaction_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "metadata"."relationMetadata_ondeleteaction_enum" AS ENUM('CASCADE', 'RESTRICT', 'SET_NULL', 'NO_ACTION')`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ALTER COLUMN "onDeleteAction" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ALTER COLUMN "onDeleteAction" TYPE "metadata"."relationMetadata_ondeleteaction_enum" USING "onDeleteAction"::"text"::"metadata"."relationMetadata_ondeleteaction_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ALTER COLUMN "onDeleteAction" SET DEFAULT 'SET_NULL'`, + ); + await queryRunner.query( + `DROP TYPE "metadata"."relationMetadata_ondeleteaction_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."workspaceMigration" ALTER COLUMN "name" SET NOT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."workspaceMigration" ALTER COLUMN "name" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE TYPE "metadata"."relationMetadata_ondeleteaction_enum_old" AS ENUM('CASCADE', 'RESTRICT', 'SET_NULL')`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ALTER COLUMN "onDeleteAction" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ALTER COLUMN "onDeleteAction" TYPE "metadata"."relationMetadata_ondeleteaction_enum_old" USING "onDeleteAction"::"text"::"metadata"."relationMetadata_ondeleteaction_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ALTER COLUMN "onDeleteAction" SET DEFAULT 'SET_NULL'`, + ); + await queryRunner.query( + `DROP TYPE "metadata"."relationMetadata_ondeleteaction_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "metadata"."relationMetadata_ondeleteaction_enum_old" RENAME TO "relationMetadata_ondeleteaction_enum"`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368825-addIsUniqueToIndexMetadata.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368825-addIsUniqueToIndexMetadata.ts new file mode 100644 index 000000000000..5f00a1d18ae7 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726757368825-addIsUniqueToIndexMetadata.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIsUniqueToIndexMetadata1726757368825 + implements MigrationInterface +{ + name = 'AddIsUniqueToIndexMetadata1726757368825'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."indexMetadata" ADD "isUnique" boolean NOT NULL DEFAULT false`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."indexMetadata" DROP COLUMN "isUnique"`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1726766871572-addWhereToIndexMetadata.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726766871572-addWhereToIndexMetadata.ts new file mode 100644 index 000000000000..2869ad54c53d --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1726766871572-addWhereToIndexMetadata.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddWhereToIndexMetadata1726766871572 + implements MigrationInterface +{ + name = 'AddWhereToIndexMetadata1726766871572'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."indexMetadata" ADD "indexWhereClause" text`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."indexMetadata" DROP COLUMN "indexWhereClause"`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1727699709905-addIsCustomColumnToIndexMetadata.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1727699709905-addIsCustomColumnToIndexMetadata.ts new file mode 100644 index 000000000000..e40465ddad72 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1727699709905-addIsCustomColumnToIndexMetadata.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIsCustomColumnToIndexMetadata1727699709905 + implements MigrationInterface +{ + name = 'AddIsCustomColumnToIndexMetadata1727699709905'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "metadata"."indexMetadata" + ADD COLUMN "isCustom" BOOLEAN + NOT NULL + DEFAULT FALSE; + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "metadata"."indexMetadata" + DROP COLUMN "isCustom" + `); + } +} diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1728563893694-addIsUniqueToFields.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1728563893694-addIsUniqueToFields.ts new file mode 100644 index 000000000000..2a6203c5d61b --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1728563893694-addIsUniqueToFields.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIsUniqueToFields1728563893694 implements MigrationInterface { + name = 'AddIsUniqueToFields1728563893694'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."fieldMetadata" ADD "isUnique" boolean DEFAULT false`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "metadata"."indexMetadata" DROP COLUMN "indexWhereClause"`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/typeorm.service.ts b/packages/twenty-server/src/database/typeorm/typeorm.service.ts index 0e8b97094b58..46b546d65ee5 100644 --- a/packages/twenty-server/src/database/typeorm/typeorm.service.ts +++ b/packages/twenty-server/src/database/typeorm/typeorm.service.ts @@ -2,17 +2,17 @@ import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; import { PostgresCredentials } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; @Injectable() export class TypeORMService implements OnModuleInit, OnModuleDestroy { @@ -37,6 +37,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy { BillingSubscriptionItem, PostgresCredentials, ], + metadataTableName: '_typeorm_generated_columns_and_materialized_views', ssl: environmentService.get('PG_SSL_ALLOW_SELF_SIGNED') ? { rejectUnauthorized: false, diff --git a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts index 337cdda3e2ae..cc23b7e26a99 100644 --- a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts +++ b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts @@ -104,20 +104,6 @@ const fieldUuidMock = { defaultValue: null, }; -const fieldPhoneMock = { - name: 'fieldPhone', - type: FieldMetadataType.PHONE, - isNullable: true, - defaultValue: null, -}; - -const fieldEmailMock = { - name: 'fieldEmail', - type: FieldMetadataType.EMAIL, - isNullable: true, - defaultValue: null, -}; - const fieldDateTimeMock = { name: 'fieldDateTime', type: FieldMetadataType.DATE_TIME, @@ -253,9 +239,7 @@ const fieldPhonesMock = { export const fields = [ fieldUuidMock, fieldTextMock, - fieldPhoneMock, fieldPhonesMock, - fieldEmailMock, fieldEmailsMock, fieldDateTimeMock, fieldDateMock, 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..e3ada8ac7cea --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts @@ -0,0 +1,54 @@ +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 { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-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<ResolverArgs, any> { + 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 'destroyMany': + return this.moduleRef.get(GraphqlQueryDestroyManyResolverService); + 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<any>, objectNameSingular: string, - filter: RecordFilter, + filter: Partial<RecordFilter>, ): SelectQueryBuilder<any> { 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..9b1d1020b7bd 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 @@ -1,4 +1,4 @@ -import { ObjectLiteral, WhereExpressionBuilder } from 'typeorm'; +import { WhereExpressionBuilder } from 'typeorm'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; @@ -6,17 +6,13 @@ import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { capitalize } from 'src/utils/capitalize'; -type WhereConditionParts = { - sql: string; - params: ObjectLiteral; -}; - export class GraphqlQueryFilterFieldParser { private fieldMetadataMap: FieldMetadataMap; @@ -57,8 +53,7 @@ export class GraphqlQueryFilterFieldParser { } } - const { sql, params } = this.computeWhereConditionParts( - fieldMetadata, + const { sql, params } = computeWhereConditionParts( operator, objectNameSingular, key, @@ -72,84 +67,6 @@ export class GraphqlQueryFilterFieldParser { } } - private computeWhereConditionParts( - fieldMetadata: FieldMetadataInterface, - operator: string, - objectNameSingular: string, - key: string, - value: any, - ): WhereConditionParts { - const uuid = Math.random().toString(36).slice(2, 7); - - switch (operator) { - case 'eq': - return { - sql: `${objectNameSingular}.${key} = :${key}${uuid}`, - params: { [`${key}${uuid}`]: value }, - }; - case 'neq': - return { - sql: `${objectNameSingular}.${key} != :${key}${uuid}`, - params: { [`${key}${uuid}`]: value }, - }; - case 'gt': - return { - sql: `${objectNameSingular}.${key} > :${key}${uuid}`, - params: { [`${key}${uuid}`]: value }, - }; - case 'gte': - return { - sql: `${objectNameSingular}.${key} >= :${key}${uuid}`, - params: { [`${key}${uuid}`]: value }, - }; - case 'lt': - return { - sql: `${objectNameSingular}.${key} < :${key}${uuid}`, - params: { [`${key}${uuid}`]: value }, - }; - case 'lte': - return { - sql: `${objectNameSingular}.${key} <= :${key}${uuid}`, - params: { [`${key}${uuid}`]: value }, - }; - case 'in': - return { - sql: `${objectNameSingular}.${key} IN (:...${key}${uuid})`, - params: { [`${key}${uuid}`]: value }, - }; - case 'is': - return { - sql: `${objectNameSingular}.${key} IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}`, - params: {}, - }; - case 'like': - return { - sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`, - params: { [`${key}${uuid}`]: `${value}` }, - }; - case 'ilike': - return { - sql: `${objectNameSingular}.${key} ILIKE :${key}${uuid}`, - params: { [`${key}${uuid}`]: `${value}` }, - }; - case 'startsWith': - return { - sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`, - params: { [`${key}${uuid}`]: `${value}` }, - }; - case 'endsWith': - return { - sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`, - params: { [`${key}${uuid}`]: `${value}` }, - }; - default: - throw new GraphqlQueryRunnerException( - `Operator "${operator}" is not supported`, - GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR, - ); - } - } - private parseCompositeFieldForFilter( queryBuilder: WhereExpressionBuilder, fieldMetadata: FieldMetadataInterface, @@ -184,8 +101,7 @@ export class GraphqlQueryFilterFieldParser { subFieldFilter as Record<string, any>, ); - const { sql, params } = this.computeWhereConditionParts( - fieldMetadata, + const { sql, params } = computeWhereConditionParts( 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 157187fd0bd2..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<any>, objectNameSingular: string, - recordFilter: RecordFilter, + recordFilter: Partial<RecordFilter>, ): SelectQueryBuilder<any> { return this.filterConditionParser.parse( queryBuilder, @@ -51,7 +51,7 @@ export class GraphqlQueryParser { ); } - applyDeletedAtToBuilder( + public applyDeletedAtToBuilder( queryBuilder: SelectQueryBuilder<any>, recordFilter: RecordFilter, ): SelectQueryBuilder<any> { @@ -62,9 +62,9 @@ export class GraphqlQueryParser { return queryBuilder; } - private checkForDeletedAtFilter( + private checkForDeletedAtFilter = ( filter: FindOptionsWhere<ObjectLiteral> | FindOptionsWhere<ObjectLiteral>[], - ): boolean { + ): boolean => { if (Array.isArray(filter)) { return filter.some((subFilter) => this.checkForDeletedAtFilter(subFilter), @@ -86,9 +86,9 @@ export class GraphqlQueryParser { } return false; - } + }; - applyOrderToBuilder( + public applyOrderToBuilder( queryBuilder: SelectQueryBuilder<any>, 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<Record<string, any>>, ): { select: Record<string, any>; relations: Record<string, any> } { 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 96f15862e392..642e11c81b1f 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,12 +1,45 @@ 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 { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-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 = [ + GraphqlQueryCreateManyResolverService, + GraphqlQueryDestroyManyResolverService, + GraphqlQueryDestroyOneResolverService, + GraphqlQueryFindDuplicatesResolverService, + GraphqlQueryFindManyResolverService, + GraphqlQueryFindOneResolverService, + GraphqlQuerySearchResolverService, + GraphqlQueryUpdateManyResolverService, + GraphqlQueryUpdateOneResolverService, +]; @Module({ - imports: [WorkspaceQueryHookModule, WorkspaceQueryRunnerModule], - providers: [GraphqlQueryRunnerService], + imports: [ + WorkspaceQueryHookModule, + WorkspaceQueryRunnerModule, + FeatureFlagModule, + ], + 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 cd1337b458c7..7eb55d60ae95 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,266 +6,407 @@ 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, + DestroyManyResolverArgs, 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 { 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 { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory'; 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 { 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 workspaceQueryHookService: WorkspaceQueryHookService, private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, + private readonly queryResultGettersFactory: QueryResultGettersFactory, @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<ObjectRecord extends IRecord, Filter extends RecordFilter>( args: FindOneResolverArgs<Filter>, options: WorkspaceQueryRunnerOptions, - ): Promise<ObjectRecord | undefined> { - 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<ObjectRecord> { + return this.executeQuery<FindOneResolverArgs<Filter>, ObjectRecord>( + 'findOne', + args, options, - ResolverArgsType.FindOne, - )) as FindOneResolverArgs<Filter>; - - 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<Filter, OrderBy>, options: WorkspaceQueryRunnerOptions, + ): Promise<IConnection<ObjectRecord, IEdge<ObjectRecord>>> { + return this.executeQuery< + FindManyResolverArgs<Filter, OrderBy>, + IConnection<ObjectRecord, IEdge<ObjectRecord>> + >('findMany', args, options); + } + + @LogExecutionTime() + async findDuplicates<ObjectRecord extends IRecord>( + args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<IConnection<ObjectRecord>[]> { + return this.executeQuery< + FindDuplicatesResolverArgs<Partial<ObjectRecord>>, + IConnection<ObjectRecord>[] + >('findDuplicates', args, options); + } + + @LogExecutionTime() + async search<ObjectRecord extends IRecord = IRecord>( + args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, ): Promise<IConnection<ObjectRecord>> { - const graphqlQueryFindManyResolverService = - new GraphqlQueryFindManyResolverService(this.twentyORMGlobalManager); + return this.executeQuery<SearchResolverArgs, IConnection<ObjectRecord>>( + 'search', + args, + options, + ); + } - const { authContext, objectMetadataItem } = options; + /** MUTATIONS */ - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findMany', - args, + @LogExecutionTime() + async createOne<ObjectRecord extends IRecord>( + args: CreateOneResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord> { + const results = await this.executeQuery< + CreateManyResolverArgs<Partial<ObjectRecord>>, + 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<Filter, OrderBy>; + return results[0]; + } + + @LogExecutionTime() + async createMany<ObjectRecord extends IRecord>( + args: CreateManyResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord[]> { + const results = await this.executeQuery< + CreateManyResolverArgs<Partial<ObjectRecord>>, + ObjectRecord[] + >('createMany', args, options); + + if (results) { + this.apiEventEmitterService.emitCreateEvents( + results, + options.authContext, + options.objectMetadataItem, + ); + } - return graphqlQueryFindManyResolverService.findMany(computedArgs, options); + return results; } @LogExecutionTime() - async createOne<ObjectRecord extends IRecord = IRecord>( - args: CreateOneResolverArgs<Partial<ObjectRecord>>, + public async updateOne<ObjectRecord extends IRecord>( + args: UpdateOneResolverArgs<Partial<ObjectRecord>>, options: WorkspaceQueryRunnerOptions, - ): Promise<ObjectRecord | undefined> { - const graphqlQueryCreateManyResolverService = - new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + ): Promise<ObjectRecord> { + const existingRecord = await this.executeQuery< + FindOneResolverArgs, + ObjectRecord + >( + 'findOne', + { + filter: { id: { eq: args.id } }, + }, + options, + ); - const { authContext, objectMetadataItem } = options; + const result = await this.executeQuery< + UpdateOneResolverArgs<Partial<ObjectRecord>>, + ObjectRecord + >('updateOne', args, options); + + this.apiEventEmitterService.emitUpdateEvents( + [existingRecord], + [result], + Object.keys(args.data), + options.authContext, + options.objectMetadataItem, + ); - assertMutationNotOnRemoteObject(objectMetadataItem); + return result; + } - if (args.data.id) { - assertIsValidUuid(args.data.id); - } + @LogExecutionTime() + public async updateMany<ObjectRecord extends IRecord>( + args: UpdateManyResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord[]> { + const existingRecords = await this.executeQuery< + FindManyResolverArgs, + IConnection<ObjectRecord, IEdge<ObjectRecord>> + >( + 'findMany', + { + filter: args.filter, + }, + options, + ); - const createManyArgs = { - data: [args.data], - upsert: args.upsert, - } as CreateManyResolverArgs<ObjectRecord>; + const result = await this.executeQuery< + UpdateManyResolverArgs<Partial<ObjectRecord>>, + ObjectRecord[] + >('updateMany', args, options); + + this.apiEventEmitterService.emitUpdateEvents( + existingRecords.edges.map((edge) => edge.node), + 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 deleteOne<ObjectRecord extends IRecord & { deletedAt?: Date }>( + args: DeleteOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord> { + const result = await this.executeQuery< + UpdateOneResolverArgs<Partial<ObjectRecord>>, + ObjectRecord + >( + 'deleteOne', + { + id: args.id, + data: { deletedAt: new Date() } as Partial<ObjectRecord>, + }, options, - ResolverArgsType.CreateMany, - )) as CreateManyResolverArgs<ObjectRecord>; + ); - const results = (await graphqlQueryCreateManyResolverService.createMany( - computedArgs, - options, - )) as ObjectRecord[]; + this.apiEventEmitterService.emitDeletedEvents( + [result], + options.authContext, + options.objectMetadataItem, + ); - await this.triggerWebhooks<ObjectRecord>( - results, - CallWebhookJobsJobOperation.create, + return result; + } + + @LogExecutionTime() + public async deleteMany<ObjectRecord extends IRecord & { deletedAt?: Date }>( + args: DeleteManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord[]> { + const result = await this.executeQuery< + UpdateManyResolverArgs<Partial<ObjectRecord>>, + ObjectRecord[] + >( + 'deleteMany', + { + filter: args.filter, + + data: { deletedAt: new Date() } as Partial<ObjectRecord>, + }, options, ); - this.emitCreateEvents<ObjectRecord>( - results, - authContext, - objectMetadataItem, + this.apiEventEmitterService.emitDeletedEvents( + result, + options.authContext, + options.objectMetadataItem, ); - return results?.[0] as ObjectRecord; + return result; } @LogExecutionTime() - async createMany<ObjectRecord extends IRecord = IRecord>( - args: CreateManyResolverArgs<Partial<ObjectRecord>>, + async destroyOne<ObjectRecord extends IRecord>( + args: DestroyOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord> { + const result = await this.executeQuery< + DestroyOneResolverArgs, + ObjectRecord + >('destroyOne', args, options); + + this.apiEventEmitterService.emitDestroyEvents( + [result], + options.authContext, + options.objectMetadataItem, + ); + + return result; + } + + @LogExecutionTime() + async destroyMany<ObjectRecord extends IRecord>( + args: DestroyManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise<ObjectRecord[] | undefined> { - const graphqlQueryCreateManyResolverService = - new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + ): Promise<ObjectRecord[]> { + const result = await this.executeQuery< + DestroyManyResolverArgs, + ObjectRecord[] + >('destroyMany', args, options); + + this.apiEventEmitterService.emitDestroyEvents( + result, + options.authContext, + options.objectMetadataItem, + ); + + return result; + } + + @LogExecutionTime() + public async restoreMany<ObjectRecord extends IRecord>( + args: RestoreManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord> { + const result = await this.executeQuery< + UpdateManyResolverArgs<Partial<ObjectRecord>>, + ObjectRecord + >( + 'restoreMany', + { + filter: args.filter, + data: { deletedAt: null } as Partial<ObjectRecord>, + }, + options, + ); + + return result; + } + private async executeQuery<Input extends ResolverArgs, Response>( + operationName: WorkspaceResolverBuilderMethodNames, + args: Input, + options: WorkspaceQueryRunnerOptions, + ): Promise<Response> { 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<ObjectRecord>; + ResolverArgsType[capitalize(operationName)], + ); - const results = (await graphqlQueryCreateManyResolverService.createMany( - computedArgs, - options, - )) as ObjectRecord[]; + const results = await resolver.resolve(computedArgs as Input, options); + + const resultWithGetters = await this.queryResultGettersFactory.create( + results, + objectMetadataItem, + authContext.workspace.id, + ); await this.workspaceQueryHookService.executePostQueryHooks( authContext, objectMetadataItem.nameSingular, - 'createMany', - results, + operationName, + Array.isArray(resultWithGetters) + ? resultWithGetters + : [resultWithGetters], ); - await this.triggerWebhooks<ObjectRecord>( - results, - CallWebhookJobsJobOperation.create, - options, - ); + const jobOperation = this.operationNameToJobOperation(operationName); - this.emitCreateEvents<ObjectRecord>( - results, - authContext, - objectMetadataItem, - ); + if (jobOperation) { + await this.triggerWebhooks(resultWithGetters, jobOperation, options); + } - return results; + return resultWithGetters; } - private emitCreateEvents<BaseRecord extends IRecord = IRecord>( - 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<any>, - ), - 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<Record>( - jobsData: Record[] | undefined, + private async triggerWebhooks<T>( + jobsData: T[] | undefined, operation: CallWebhookJobsJobOperation, options: WorkspaceQueryRunnerOptions, - ) { - if (!Array.isArray(jobsData)) { - return; - } + ): Promise<void> { + if (!jobsData || !Array.isArray(jobsData)) return; + jobsData.forEach((jobData) => { this.messageQueueService.add<CallWebhookJobsJobData>( CallWebhookJobsJob.name, @@ -279,15 +420,4 @@ export class GraphqlQueryRunnerService { ); }); } - - @LogExecutionTime() - async destroyOne<ObjectRecord extends IRecord = IRecord>( - args: DestroyOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise<ObjectRecord> { - const graphqlQueryDestroyOneResolverService = - new GraphqlQueryDestroyOneResolverService(this.twentyORMGlobalManager); - - return graphqlQueryDestroyOneResolverService.destroyOne(args, options); - } } 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 78% 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..54220315345a 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,32 +20,41 @@ 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) { this.objectMetadataMap = objectMetadataMap; } - public createConnection<ObjectRecord extends IRecord = IRecord>( - objectRecords: ObjectRecord[], - objectName: string, - take: number, - totalCount: number, - order: RecordOrderBy | undefined, - hasNextPage: boolean, - hasPreviousPage: boolean, + public createConnection<ObjectRecord extends IRecord = IRecord>({ + objectRecords, + objectName, + take, + totalCount, + order, + hasNextPage, + hasPreviousPage, depth = 0, - ): IConnection<ObjectRecord> { + }: { + objectRecords: ObjectRecord[]; + objectName: string; + take: number; + totalCount: number; + order?: RecordOrderBy; + hasNextPage: boolean; + hasPreviousPage: boolean; + depth?: number; + }): IConnection<ObjectRecord> { const edges = (objectRecords ?? []).map((objectRecord) => ({ - node: this.processRecord( + node: this.processRecord({ objectRecord, objectName, take, totalCount, order, depth, - ), + }), cursor: encodeCursor(objectRecord, order), })); @@ -61,14 +70,21 @@ export class ObjectRecordsToGraphqlConnectionMapper { }; } - public processRecord<T extends Record<string, any>>( - objectRecord: T, - objectName: string, - take: number, - totalCount: number, - order?: RecordOrderBy, + public processRecord<T extends Record<string, any>>({ + objectRecord, + objectName, + take, + totalCount, + order, depth = 0, - ): T { + }: { + objectRecord: T; + objectName: string; + take: number; + totalCount: number; + order?: RecordOrderBy; + depth?: number; + }): T { if (depth >= CONNECTION_MAX_DEPTH) { throw new GraphqlQueryRunnerException( `Maximum depth of ${CONNECTION_MAX_DEPTH} reached`, @@ -97,27 +113,31 @@ export class ObjectRecordsToGraphqlConnectionMapper { if (isRelationFieldMetadataType(fieldMetadata.type)) { if (Array.isArray(value)) { - processedObjectRecord[key] = this.createConnection( - value, - getRelationObjectMetadata(fieldMetadata, this.objectMetadataMap) - .nameSingular, + processedObjectRecord[key] = this.createConnection({ + objectRecords: value, + objectName: getRelationObjectMetadata( + fieldMetadata, + this.objectMetadataMap, + ).nameSingular, take, - value.length, + totalCount: value.length, order, - false, - false, - depth + 1, - ); + hasNextPage: false, + hasPreviousPage: false, + depth: depth + 1, + }); } else if (isPlainObject(value)) { - processedObjectRecord[key] = this.processRecord( - value, - getRelationObjectMetadata(fieldMetadata, this.objectMetadataMap) - .nameSingular, + processedObjectRecord[key] = this.processRecord({ + objectRecord: value, + objectName: getRelationObjectMetadata( + fieldMetadata, + this.objectMetadataMap, + ).nameSingular, take, totalCount, order, - depth + 1, - ); + depth: depth + 1, + }); } } else if (isCompositeFieldMetadataType(fieldMetadata.type)) { processedObjectRecord[key] = this.processCompositeField( 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<ObjectRecord extends IRecord = IRecord>( + objectMetadataMap: ObjectMetadataMap, + parentObjectMetadataItem: ObjectMetadataMapItem, + parentObjectRecords: ObjectRecord[], + relations: Record<string, FindOptionsRelations<ObjectLiteral>>, + limit: number, + authContext: any, + dataSource: DataSource, + ): Promise<void> { + 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<ObjectRecord extends IRecord = IRecord>( + private async processRelation<ObjectRecord extends IRecord = IRecord>( objectMetadataMap: ObjectMetadataMap, parentObjectMetadataItem: ObjectMetadataMapItem, parentObjectRecords: ObjectRecord[], @@ -35,49 +57,71 @@ export class ProcessNestedRelationsHelper { limit: number, authContext: any, dataSource: DataSource, - ) { + ): Promise<void> { 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<ObjectRecord extends IRecord = IRecord>( + objectMetadataMap: ObjectMetadataMap, + parentObjectMetadataItem: ObjectMetadataMapItem, + parentObjectRecords: ObjectRecord[], + relationName: string, + nestedRelations: any, + limit: number, + authContext: any, + dataSource: DataSource, + ): Promise<void> { + 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<string, FindOptionsRelations<ObjectLiteral>>, limit, @@ -96,48 +140,37 @@ export class ProcessNestedRelationsHelper { limit: number, authContext: any, dataSource: DataSource, - ) { - const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; - - const referenceObjectMetadata = getRelationObjectMetadata( - relationFieldMetadata, + ): Promise<void> { + 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<string, FindOptionsRelations<ObjectLiteral>>, limit, @@ -147,48 +180,71 @@ export class ProcessNestedRelationsHelper { } } - public async processNestedRelations<ObjectRecord extends IRecord = IRecord>( + private getRelationMetadata( objectMetadataMap: ObjectMetadataMap, parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], - relations: Record<string, FindOptionsRelations<ObjectLiteral>>, - 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<any>, + field: string, + ids: any[], + limit: number, + ): Promise<any[]> { + 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<ResolverArgs, T> { + resolve: ( + args: ResolverArgs, + options: WorkspaceQueryRunnerOptions, + ) => Promise<T>; + validate: ( + args: ResolverArgs, + options: WorkspaceQueryRunnerOptions, + ) => Promise<void>; +} 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..6cd7a111138e 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<CreateManyResolverArgs, IRecord[]> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async createMany<ObjectRecord extends IRecord = IRecord>( + async resolve<ObjectRecord extends IRecord = IRecord>( args: CreateManyResolverArgs<Partial<ObjectRecord>>, options: WorkspaceQueryRunnerOptions, - ): Promise<ObjectRecord[] | undefined> { - const { authContext, objectMetadataItem, objectMetadataCollection, info } = + ): Promise<ObjectRecord[]> { + 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,60 @@ 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, - 1, - 1, - ), + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }), ); } + + async validate<ObjectRecord extends IRecord>( + args: CreateManyResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + 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-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts new file mode 100644 index 000000000000..04ceddf9ac9d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -0,0 +1,108 @@ +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 { DestroyManyResolverArgs } 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryDestroyManyResolverService + implements ResolverService<DestroyManyResolverArgs, IRecord[]> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve<ObjectRecord extends IRecord = IRecord>( + args: DestroyManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord[]> { + 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 nonFormattedDeletedObjectRecords = await withFilterQueryBuilder + .delete() + .returning('*') + .execute(); + + const deletedRecords = formatResult( + nonFormattedDeletedObjectRecords.raw, + objectMetadataMapItem, + objectMetadataMap, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + deletedRecords, + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return deletedRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }), + ); + } + + async validate( + args: DestroyManyResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + if (!args.filter) { + throw new Error('Filter is required'); + } + } +} 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..5467d6c0ff0a 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,118 @@ +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 { DestroyOneResolverArgs } 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 { 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<DestroyOneResolverArgs, IRecord> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async destroyOne<ObjectRecord extends IRecord = IRecord>( + async resolve<ObjectRecord extends IRecord = IRecord>( args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise<ObjectRecord> { - const { authContext, objectMetadataItem } = options; - const repository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, - objectMetadataItem.nameSingular, ); - const record = await repository.findOne({ - where: { id: args.id }, - }); + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); - await repository.delete(args.id); + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); + + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const nonFormattedDeletedObjectRecords = await queryBuilder + .where(`"${objectMetadataMapItem.nameSingular}".id = :id`, { + id: args.id, + }) + .take(1) + .delete() + .returning('*') + .execute(); + + if (!nonFormattedDeletedObjectRecords.affected) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } - return record as ObjectRecord; + const recordBeforeDeletion = formatResult( + nonFormattedDeletedObjectRecords.raw, + objectMetadataMapItem, + objectMetadataMap, + )[0]; + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + [recordBeforeDeletion], + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return typeORMObjectRecordsParser.processRecord({ + objectRecord: recordBeforeDeletion, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }); + } + + async validate( + args: DestroyOneResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + 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..d3bc72fa8220 --- /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<FindDuplicatesResolverArgs, IConnection<IRecord>[]> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve<ObjectRecord extends IRecord = IRecord>( + args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<IConnection<ObjectRecord>[]> { + 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<ObjectRecord>[] = []; + + 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<ObjectRecord>[] = await Promise.all( + objectRecords.map(async (record) => { + const duplicateConditions = this.buildDuplicateConditions( + objectMetadataMapItem, + [record], + record.id, + ); + + if (isEmpty(duplicateConditions)) { + return typeORMObjectRecordsParser.createConnection({ + objectRecords: [], + objectName: objectMetadataMapItem.nameSingular, + take: 0, + totalCount: 0, + order: [{ id: OrderByDirection.AscNullsFirst }], + hasNextPage: false, + hasPreviousPage: 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({ + objectRecords: duplicates, + objectName: objectMetadataMapItem.nameSingular, + take: duplicates.length, + totalCount: duplicates.length, + order: [{ id: OrderByDirection.AscNullsFirst }], + hasNextPage: false, + hasPreviousPage: false, + }); + }), + ); + + return duplicateConnections; + } + + private buildDuplicateConditions( + objectMetadataMapItem: ObjectMetadataMapItem, + records?: Partial<IRecord>[] | undefined, + filteringByExistingRecordId?: string, + ): Partial<RecordFilter> { + 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<RecordFilter> = {}; + + 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<void> { + 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..9411c5502103 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<FindManyResolverArgs, IConnection<IRecord>> +{ + 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<Filter, OrderBy>, options: WorkspaceQueryRunnerOptions, ): Promise<IConnection<ObjectRecord>> { - 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, - limit, + objectName: objectMetadataMapItem.nameSingular, + take: limit, totalCount, - orderByWithIdCondition, + order: orderByWithIdCondition, hasNextPage, hasPreviousPage, - ); + }); + + return result; } - private validateArgsOrThrow(args: FindManyResolverArgs<any, any>) { + async validate<Filter extends RecordFilter>( + args: FindManyResolverArgs<Filter>, + _options: WorkspaceQueryRunnerOptions, + ): Promise<void> { 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<any, any>, - ): Record<string, any> | undefined { - if (args.after) return decodeCursor(args.after); - if (args.before) return decodeCursor(args.before); - - return undefined; - } - - private addOrderByColumnsToSelect( - order: Record<string, any>, - select: Record<string, boolean>, - ) { - for (const column of Object.keys(order || {})) { - if (!select[column]) { - select[column] = true; - } - } - } - - private addForeingKeyColumnsToSelect( - relations: Record<string, any>, - select: Record<string, boolean>, - 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..42c8daae8079 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<FindOneResolverArgs, IRecord> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async findOne< + async resolve< ObjectRecord extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, >( args: FindOneResolverArgs<Filter>, options: WorkspaceQueryRunnerOptions, - ): Promise<ObjectRecord | undefined> { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = + ): Promise<ObjectRecord> { + 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); - - return typeORMObjectRecordsParser.processRecord( - objectRecords[0], - objectMetadataItem.nameSingular, - 1, - 1, - ) as ObjectRecord; + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return typeORMObjectRecordsParser.processRecord({ + objectRecord: objectRecords[0], + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }) as ObjectRecord; + } + + async validate<Filter extends RecordFilter>( + args: FindOneResolverArgs<Filter>, + _options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + 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 new file mode 100644 index 000000000000..a0fdbf0d377d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@nestjs/common'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + Record as IRecord, + OrderByDirection, +} 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 { SearchResolverArgs } 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 { 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +@Injectable() +export class GraphqlQuerySearchResolverService + implements ResolverService<SearchResolverArgs, IConnection<IRecord>> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly featureFlagService: FeatureFlagService, + ) {} + + async resolve<ObjectRecord extends IRecord = IRecord>( + args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<IConnection<ObjectRecord>> { + const { authContext, objectMetadataItem, objectMetadataMap } = options; + + const repository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + authContext.workspace.id, + objectMetadataItem.nameSingular, + ); + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + if (!args.searchInput) { + return typeORMObjectRecordsParser.createConnection({ + objectRecords: [], + objectName: objectMetadataItem.nameSingular, + take: 0, + totalCount: 0, + order: [{ id: OrderByDirection.AscNullsFirst }], + hasNextPage: false, + hasPreviousPage: false, + }); + } + const searchTerms = this.formatSearchTerms(args.searchInput); + + const limit = args?.limit ?? QUERY_MAX_RECORDS; + + const resultsWithTsVector = (await repository + .createQueryBuilder() + .where(`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`, { + searchTerms, + }) + .orderBy( + `ts_rank("${SEARCH_VECTOR_FIELD.name}", to_tsquery(:searchTerms))`, + 'DESC', + ) + .setParameter('searchTerms', searchTerms) + .take(limit) + .getMany()) as ObjectRecord[]; + + const objectRecords = await repository.formatResult(resultsWithTsVector); + + const totalCount = await repository.count(); + const order = undefined; + + return typeORMObjectRecordsParser.createConnection({ + objectRecords: objectRecords ?? [], + objectName: objectMetadataItem.nameSingular, + take: limit, + totalCount, + order, + hasNextPage: false, + hasPreviousPage: false, + }); + } + + private formatSearchTerms(searchTerm: string) { + const words = searchTerm.trim().split(/\s+/); + const formattedWords = words.map((word) => { + const escapedWord = word.replace(/[\\:'&|!()]/g, '\\$&'); + + return `${escapedWord}:*`; + }); + + return formattedWords.join(' | '); + } + + async validate( + _args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + const featureFlagsForWorkspace = + await this.featureFlagService.getWorkspaceFeatureFlags( + options.authContext.workspace.id, + ); + + const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED; + + if (!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..020bf08fa722 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -0,0 +1,123 @@ +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'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; + +@Injectable() +export class GraphqlQueryUpdateManyResolverService + implements ResolverService<UpdateManyResolverArgs, IRecord[]> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve<ObjectRecord extends IRecord = IRecord>( + args: UpdateManyResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord[]> { + 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 tableName = computeTableName( + objectMetadataMapItem.nameSingular, + objectMetadataMapItem.isCustom, + ); + + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + tableName, + args.filter, + ); + + const data = formatData(args.data, objectMetadataMapItem); + + const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder + .update(data) + .returning('*') + .execute(); + + const updatedRecords = formatResult( + nonFormattedUpdatedObjectRecords.raw, + 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({ + objectRecord: record, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }), + ); + } + + async validate<ObjectRecord extends IRecord = IRecord>( + args: UpdateManyResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + if (!args.filter) { + throw new Error('Filter is required'); + } + + 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..8fe4396d2413 --- /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<UpdateOneResolverArgs, IRecord> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve<ObjectRecord extends IRecord = IRecord>( + args: UpdateOneResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<ObjectRecord> { + 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<ObjectRecord>({ + objectRecord: updatedRecord, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }); + } + + async validate<ObjectRecord extends IRecord = IRecord>( + args: UpdateOneResolverArgs<Partial<ObjectRecord>>, + options: WorkspaceQueryRunnerOptions, + ): Promise<void> { + 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<T extends IRecord>( + 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<T extends IRecord>( + 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<T extends IRecord>( + 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<T extends IRecord>( + 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<ObjectRecord extends IRecord>( + 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/compute-where-condition-parts.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts new file mode 100644 index 000000000000..ef8d4680ebb3 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts @@ -0,0 +1,88 @@ +import { ObjectLiteral } from 'typeorm'; + +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; + +type WhereConditionParts = { + sql: string; + params: ObjectLiteral; +}; + +export const computeWhereConditionParts = ( + operator: string, + objectNameSingular: string, + key: string, + value: any, +): WhereConditionParts => { + const uuid = Math.random().toString(36).slice(2, 7); + + switch (operator) { + case 'eq': + return { + sql: `"${objectNameSingular}"."${key}" = :${key}${uuid}`, + params: { [`${key}${uuid}`]: value }, + }; + case 'neq': + return { + sql: `"${objectNameSingular}"."${key}" != :${key}${uuid}`, + params: { [`${key}${uuid}`]: value }, + }; + case 'gt': + return { + sql: `"${objectNameSingular}"."${key}" > :${key}${uuid}`, + params: { [`${key}${uuid}`]: value }, + }; + case 'gte': + return { + sql: `"${objectNameSingular}"."${key}" >= :${key}${uuid}`, + params: { [`${key}${uuid}`]: value }, + }; + case 'lt': + return { + sql: `"${objectNameSingular}"."${key}" < :${key}${uuid}`, + params: { [`${key}${uuid}`]: value }, + }; + case 'lte': + return { + sql: `"${objectNameSingular}"."${key}" <= :${key}${uuid}`, + params: { [`${key}${uuid}`]: value }, + }; + case 'in': + return { + sql: `"${objectNameSingular}"."${key}" IN (:...${key}${uuid})`, + params: { [`${key}${uuid}`]: value }, + }; + case 'is': + return { + sql: `"${objectNameSingular}"."${key}" IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}`, + params: {}, + }; + case 'like': + return { + sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`, + params: { [`${key}${uuid}`]: `${value}` }, + }; + case 'ilike': + return { + sql: `"${objectNameSingular}"."${key}" ILIKE :${key}${uuid}`, + params: { [`${key}${uuid}`]: `${value}` }, + }; + case 'startsWith': + return { + sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`, + params: { [`${key}${uuid}`]: `${value}` }, + }; + case 'endsWith': + return { + sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`, + params: { [`${key}${uuid}`]: `${value}` }, + }; + default: + throw new GraphqlQueryRunnerException( + `Operator "${operator}" is not supported`, + GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR, + ); + } +}; 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 = <ObjectRecord extends IRecord = IRecord>( return Buffer.from(JSON.stringify(cursorData)).toString('base64'); }; + +export const getCursor = ( + args: FindManyResolverArgs<any, any>, +): Record<string, any> | 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-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/factories.ts index e10fab44e300..58c97cd2675f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/factories.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/factories.ts @@ -2,18 +2,18 @@ import { ForeignDataWrapperServerQueryFactory } from 'src/engine/api/graphql/wor import { ArgsAliasFactory } from './args-alias.factory'; import { ArgsStringFactory } from './args-string.factory'; -import { RelationFieldAliasFactory } from './relation-field-alias.factory'; import { CreateManyQueryFactory } from './create-many-query.factory'; +import { DeleteManyQueryFactory } from './delete-many-query.factory'; import { DeleteOneQueryFactory } from './delete-one-query.factory'; import { FieldAliasFactory } from './field-alias.factory'; import { FieldsStringFactory } from './fields-string.factory'; +import { FindDuplicatesQueryFactory } from './find-duplicates-query.factory'; import { FindManyQueryFactory } from './find-many-query.factory'; import { FindOneQueryFactory } from './find-one-query.factory'; -import { UpdateOneQueryFactory } from './update-one-query.factory'; -import { UpdateManyQueryFactory } from './update-many-query.factory'; -import { DeleteManyQueryFactory } from './delete-many-query.factory'; -import { FindDuplicatesQueryFactory } from './find-duplicates-query.factory'; import { RecordPositionQueryFactory } from './record-position-query.factory'; +import { RelationFieldAliasFactory } from './relation-field-alias.factory'; +import { UpdateManyQueryFactory } from './update-many-query.factory'; +import { UpdateOneQueryFactory } from './update-one-query.factory'; export const workspaceQueryBuilderFactories = [ ArgsAliasFactory, 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/jobs/call-webhook-jobs.job.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job.ts index d0bbc6872c06..a1b43eb22034 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job.ts @@ -20,6 +20,7 @@ export enum CallWebhookJobsJobOperation { create = 'create', update = 'update', delete = 'delete', + destroy = 'destroy', } export type CallWebhookJobsJobData = { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook.job.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook.job.ts index bbaff49a4951..e03c83204e85 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook.job.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/call-webhook.job.ts @@ -1,6 +1,7 @@ import { HttpService } from '@nestjs/axios'; import { Logger } from '@nestjs/common'; +import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -18,17 +19,41 @@ export type CallWebhookJobData = { @Processor(MessageQueue.webhookQueue) export class CallWebhookJob { private readonly logger = new Logger(CallWebhookJob.name); - - constructor(private readonly httpService: HttpService) {} + constructor( + private readonly httpService: HttpService, + private readonly analyticsService: AnalyticsService, + ) {} @Process(CallWebhookJob.name) async handle(data: CallWebhookJobData): Promise<void> { try { - await this.httpService.axiosRef.post(data.targetUrl, data); - this.logger.log( - `CallWebhookJob successfully called on targetUrl '${data.targetUrl}'`, + const response = await this.httpService.axiosRef.post( + data.targetUrl, + data, ); + const eventInput = { + action: 'webhook.response', + payload: { + status: response.status, + url: data.targetUrl, + webhookId: data.webhookId, + eventName: data.eventName, + }, + }; + + this.analyticsService.create(eventInput, 'webhook', data.workspaceId); } catch (err) { + const eventInput = { + action: 'webhook.response', + payload: { + status: err.response.status, + url: data.targetUrl, + webhookId: data.webhookId, + eventName: data.eventName, + }, + }; + + this.analyticsService.create(eventInput, 'webhook', data.workspaceId); this.logger.error( `Error calling webhook on targetUrl '${data.targetUrl}': ${err}`, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/workspace-query-runner-job.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/workspace-query-runner-job.module.ts index 8104cf075cf3..c7e93901b008 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/workspace-query-runner-job.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/workspace-query-runner-job.module.ts @@ -5,6 +5,7 @@ import { CallWebhookJobsJob } from 'src/engine/api/graphql/workspace-query-runne import { CallWebhookJob } from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook.job'; import { RecordPositionBackfillJob } from 'src/engine/api/graphql/workspace-query-runner/jobs/record-position-backfill.job'; import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module'; +import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; @@ -14,6 +15,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works DataSourceModule, RecordPositionBackfillModule, HttpModule, + AnalyticsModule, ], providers: [CallWebhookJobsJob, CallWebhookJob, RecordPositionBackfillJob], }) diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts index eb9ddbf06a6f..8eb8be61f774 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts @@ -49,6 +49,13 @@ export class EntityEventsToDbListener { return this.handle(payload); } + @OnEvent('*.destroyed') + async handleDestroy( + payload: WorkspaceEventBatch<ObjectRecordUpdateEvent<any>>, + ) { + return this.handle(payload); + } + private async handle(payload: WorkspaceEventBatch<ObjectRecordBaseEvent>) { const filteredEvents = payload.events.filter( (event) => event.objectMetadata?.isAuditLogged, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts index 2afa537a687e..f627caf47a42 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts @@ -2,15 +2,15 @@ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; +import { TelemetryService } from 'src/engine/core-modules/telemetry/telemetry.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/workspace-event.type'; @Injectable() export class TelemetryListener { constructor( private readonly analyticsService: AnalyticsService, - private readonly environmentService: EnvironmentService, + private readonly telemetryService: TelemetryService, ) {} @OnEvent('*.created') @@ -21,16 +21,11 @@ export class TelemetryListener { payload.events.map((eventPayload) => this.analyticsService.create( { - type: 'track', - data: { - eventName: payload.name, - }, + action: payload.name, + payload: {}, }, eventPayload.userId, payload.workspaceId, - '', // voluntarily not retrieving this - '', // to avoid slowing down - this.environmentService.get('SERVER_URL'), ), ), ); @@ -41,21 +36,29 @@ export class TelemetryListener { payload: WorkspaceEventBatch<ObjectRecordCreateEvent<any>>, ) { await Promise.all( - payload.events.map((eventPayload) => + payload.events.map(async (eventPayload) => { this.analyticsService.create( { - type: 'track', - data: { - eventName: 'user.signup', + action: 'user.signup', + payload: {}, + }, + eventPayload.userId, + payload.workspaceId, + ); + + this.telemetryService.create( + { + action: 'user.signup', + payload: { + payload, + userId: undefined, + workspaceId: undefined, }, }, eventPayload.userId, payload.workspaceId, - '', - '', - this.environmentService.get('SERVER_URL'), - ), - ), + ); + }), ); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts index 0a05be29da2e..fc2f0fbbaef9 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts @@ -1,3 +1,7 @@ +import { QueryFailedError } from 'typeorm'; + +import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; + import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, @@ -16,7 +20,51 @@ import { export const workspaceQueryRunnerGraphqlApiExceptionHandler = ( error: Error, + context: WorkspaceSchemaBuilderContext, ) => { + if (error instanceof QueryFailedError) { + if ( + error.message.includes('duplicate key value violates unique constraint') + ) { + const indexNameMatch = error.message.match(/"([^"]+)"/); + + if (indexNameMatch) { + const indexName = indexNameMatch[1]; + + const deletedAtFieldMetadata = context.objectMetadataItem.fields.find( + (field) => field.name === 'deletedAt', + ); + + const affectedColumns = context.objectMetadataItem.indexMetadatas + .find((index) => index.name === indexName) + ?.indexFieldMetadatas?.filter( + (field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id, + ) + .map((indexField) => { + const fieldMetadata = context.objectMetadataItem.fields.find( + (objectField) => indexField.fieldMetadataId === objectField.id, + ); + + return fieldMetadata?.label; + }); + + const columnNames = affectedColumns?.join(', '); + + if (affectedColumns?.length === 1) { + throw new UserInputError( + `Duplicate ${columnNames}. Please set a unique one.`, + ); + } + + throw new UserInputError( + `A duplicate entry was detected. The combination of ${columnNames} must be unique.`, + ); + } + } + + throw error; + } + if (error instanceof WorkspaceQueryRunnerException) { switch (error.code) { case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND: 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 f155475877e0..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 @@ -4,10 +4,12 @@ import { DeleteManyResolverArgs, DeleteOneResolverArgs, DestroyManyResolverArgs, + DestroyOneResolverArgs, FindDuplicatesResolverArgs, FindManyResolverArgs, FindOneResolverArgs, RestoreManyResolverArgs, + SearchResolverArgs, UpdateManyResolverArgs, UpdateOneResolverArgs, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -39,4 +41,8 @@ export type WorkspacePreQueryHookPayload<T> = T extends 'createMany' ? RestoreManyResolverArgs : T extends 'destroyMany' ? DestroyManyResolverArgs - : never; + : T extends 'destroyOne' + ? DestroyOneResolverArgs + : T extends 'search' + ? SearchResolverArgs + : never; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts index 5d1771aad84a..1e166806be8d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts @@ -12,6 +12,7 @@ import { DuplicateModule } from 'src/engine/core-modules/duplicate/duplicate.mod import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FileModule } from 'src/engine/core-modules/file/file.module'; +import { TelemetryModule } from 'src/engine/core-modules/telemetry/telemetry.module'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -29,6 +30,7 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), AnalyticsModule, + TelemetryModule, DuplicateModule, FileModule, FeatureFlagModule, 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..c6d3303fb42f 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, @@ -9,9 +10,6 @@ import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace- 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 CreateManyResolverFactory @@ -20,8 +18,6 @@ export class CreateManyResolverFactory public static methodName = 'createMany' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, - private readonly featureFlagService: FeatureFlagService, private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} @@ -32,27 +28,19 @@ 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 = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, - internalContext.authContext.workspace.id, - ); - - if (isQueryRunnerTwentyORMEnabled) { - return await this.graphqlQueryRunnerService.createMany(args, options); - } - - return await this.workspaceQueryRunnerService.createMany(args, options); + return await this.graphqlQueryRunnerService.createMany(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, context); } }; } 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..3c2d9095e62c 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, @@ -9,9 +10,6 @@ import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace- 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 CreateOneResolverFactory @@ -20,8 +18,6 @@ export class CreateOneResolverFactory public static methodName = 'createOne' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, - private readonly featureFlagService: FeatureFlagService, private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} @@ -32,33 +28,19 @@ 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 = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, - internalContext.authContext.workspace.id, - ); - - if (isQueryRunnerTwentyORMEnabled) { - 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.graphqlQueryRunnerService.createOne(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..191514f86309 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,8 @@ 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'; @Injectable() export class DeleteManyResolverFactory @@ -17,7 +18,7 @@ export class DeleteManyResolverFactory public static methodName = 'deleteMany' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,19 @@ 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, + }; + + return await this.graphqlQueryRunnerService.deleteMany(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..7cbd7bf3bddd 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,8 @@ 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'; @Injectable() export class DeleteOneResolverFactory @@ -17,7 +18,7 @@ export class DeleteOneResolverFactory public static methodName = 'deleteOne' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,19 @@ 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, + }; + + return await this.graphqlQueryRunnerService.deleteOne(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..80da084e630c 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, @@ -7,8 +8,8 @@ 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'; @Injectable() export class DestroyManyResolverFactory @@ -17,7 +18,7 @@ export class DestroyManyResolverFactory public static methodName = 'destroyMany' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,19 @@ 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.graphqlQueryRunnerService.destroyMany(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..c3dd4416918b 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,15 +28,19 @@ 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); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts index 1724242e866d..b728ef8988e2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts @@ -1,6 +1,7 @@ import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory'; +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 { CreateManyResolverFactory } from './create-many-resolver.factory'; @@ -25,6 +26,7 @@ export const workspaceResolverBuilderFactories = [ DestroyOneResolverFactory, DestroyManyResolverFactory, RestoreManyResolverFactory, + SearchResolverFactory, ]; export const workspaceResolverBuilderMethodNames = { @@ -32,6 +34,7 @@ export const workspaceResolverBuilderMethodNames = { FindManyResolverFactory.methodName, FindOneResolverFactory.methodName, FindDuplicatesResolverFactory.methodName, + SearchResolverFactory.methodName, ], mutations: [ CreateManyResolverFactory.methodName, 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..154c2c88646e 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,8 @@ 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'; @Injectable() export class FindDuplicatesResolverFactory @@ -17,7 +18,7 @@ export class FindDuplicatesResolverFactory public static methodName = 'findDuplicates' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,22 @@ 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, + }; + + return await this.graphqlQueryRunnerService.findDuplicates( + args, + options, + ); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..d46db50962b8 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,17 +28,19 @@ 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); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..7543d59eccd3 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,17 +28,19 @@ 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); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..709dcc40d312 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,8 @@ 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'; @Injectable() export class RestoreManyResolverFactory @@ -17,7 +18,7 @@ export class RestoreManyResolverFactory public static methodName = 'restoreMany' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,19 @@ 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, + }; + + return await this.graphqlQueryRunnerService.restoreMany(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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 new file mode 100644 index 000000000000..35520538b0f1 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts @@ -0,0 +1,45 @@ +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, + SearchResolverArgs, +} 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'; + +@Injectable() +export class SearchResolverFactory + implements WorkspaceResolverBuilderFactoryInterface +{ + public static methodName = 'search' as const; + + constructor( + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + ) {} + + create(context: WorkspaceSchemaBuilderContext): Resolver<SearchResolverArgs> { + const internalContext = context; + + return async (_source, args, _context, info) => { + try { + 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, internalContext); + } + }; + } +} 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..af9f0935eeb4 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,8 @@ 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'; @Injectable() export class UpdateManyResolverFactory @@ -17,7 +18,7 @@ export class UpdateManyResolverFactory public static methodName = 'updateMany' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,19 @@ 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, + }; + + return await this.graphqlQueryRunnerService.updateMany(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } 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..b1198cf1a361 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,8 @@ 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'; @Injectable() export class UpdateOneResolverFactory @@ -17,7 +18,7 @@ export class UpdateOneResolverFactory public static methodName = 'updateOne' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,15 +28,19 @@ 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, + }; + + return await this.graphqlQueryRunnerService.updateOne(args, options); } catch (error) { - workspaceQueryRunnerGraphqlApiExceptionHandler(error); + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } }; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts index 4e2a0af85196..219b185c451e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts @@ -22,6 +22,7 @@ export enum ResolverArgsType { DeleteMany = 'DeleteMany', RestoreMany = 'RestoreMany', DestroyMany = 'DestroyMany', + DestroyOne = 'DestroyOne', } export interface FindManyResolverArgs< @@ -47,6 +48,11 @@ export interface FindDuplicatesResolverArgs< data?: Data[]; } +export interface SearchResolverArgs { + searchInput?: string; + limit?: number; +} + export interface CreateOneResolverArgs< Data extends Partial<Record> = Partial<Record>, > { @@ -122,4 +128,5 @@ export type ResolverArgs = | UpdateManyResolverArgs | UpdateOneResolverArgs | DestroyManyResolverArgs - | RestoreManyResolverArgs; + | RestoreManyResolverArgs + | SearchResolverArgs; 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 6c06b85d9ff0..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 @@ -8,8 +8,10 @@ import { DeleteManyResolverFactory } from 'src/engine/api/graphql/workspace-reso import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory'; +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'; @@ -42,11 +44,13 @@ export class WorkspaceResolverFactory { private readonly deleteManyResolverFactory: DeleteManyResolverFactory, private readonly restoreManyResolverFactory: RestoreManyResolverFactory, private readonly destroyManyResolverFactory: DestroyManyResolverFactory, + private readonly searchResolverFactory: SearchResolverFactory, ) {} async create( authContext: AuthContext, objectMetadataCollection: ObjectMetadataInterface[], + objectMetadataMap: ObjectMetadataMap, workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods, ): Promise<IResolvers> { const factories = new Map< @@ -65,6 +69,7 @@ export class WorkspaceResolverFactory { ['deleteMany', this.deleteManyResolverFactory], ['restoreMany', this.restoreManyResolverFactory], ['destroyMany', this.destroyManyResolverFactory], + ['search', this.searchResolverFactory], ]); const resolvers: IResolvers = { Query: {}, @@ -91,7 +96,9 @@ export class WorkspaceResolverFactory { authContext, objectMetadataItem: objectMetadata, fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection: objectMetadataCollection, + objectMetadataCollection, + objectMetadataMap, + objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], }); } @@ -114,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/factories/connection-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts index d6400e7cb0d8..99ce89573c38 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts @@ -5,12 +5,12 @@ import { GraphQLOutputType } from 'graphql'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { PageInfoType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/object'; import { TypeMapperService, TypeOptions, } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage'; -import { PageInfoType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/object'; import { ConnectionTypeDefinitionKind } from './connection-type-definition.factory'; import { ObjectTypeDefinitionKind } from './object-type-definition.factory'; @@ -27,7 +27,7 @@ export class ConnectionTypeFactory { public create( objectMetadata: ObjectMetadataInterface, kind: ConnectionTypeDefinitionKind, - buildOtions: WorkspaceBuildSchemaOptions, + buildOptions: WorkspaceBuildSchemaOptions, typeOptions: TypeOptions, ): GraphQLOutputType { if (kind === ConnectionTypeDefinitionKind.PageInfo) { @@ -44,7 +44,7 @@ export class ConnectionTypeFactory { `Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`, { objectMetadata, - buildOtions, + buildOptions, }, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts index 30ddfd0166a9..3adeeb2d187f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts @@ -5,15 +5,15 @@ import { GraphQLOutputType } from 'graphql'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { CursorScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { TypeMapperService, TypeOptions, } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage'; -import { CursorScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; -import { ObjectTypeDefinitionKind } from './object-type-definition.factory'; import { EdgeTypeDefinitionKind } from './edge-type-definition.factory'; +import { ObjectTypeDefinitionKind } from './object-type-definition.factory'; @Injectable() export class EdgeTypeFactory { @@ -27,7 +27,7 @@ export class EdgeTypeFactory { public create( objectMetadata: ObjectMetadataInterface, kind: EdgeTypeDefinitionKind, - buildOtions: WorkspaceBuildSchemaOptions, + buildOptions: WorkspaceBuildSchemaOptions, typeOptions: TypeOptions, ): GraphQLOutputType { if (kind === EdgeTypeDefinitionKind.Cursor) { @@ -44,7 +44,7 @@ export class EdgeTypeFactory { `Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`, { objectMetadata, - buildOtions, + buildOptions, }, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts index 7db06ad369bf..67b3ee17683c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts @@ -1,14 +1,12 @@ import { Inject, Injectable, forwardRef } from '@nestjs/common'; -import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } from 'graphql'; +import { GraphQLInputObjectType } from 'graphql'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; -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 { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; +import { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils'; import { pascalCase } from 'src/utils/pascal-case'; import { InputTypeFactory } from './input-type.factory'; @@ -55,7 +53,12 @@ export class InputTypeDefinitionFactory { }); return { - ...this.generateFields(objectMetadata, kind, options), + ...generateFields( + objectMetadata, + kind, + options, + this.inputTypeFactory, + ), and: { type: andOrType, }, @@ -73,7 +76,12 @@ export class InputTypeDefinitionFactory { * Other input types are generated with fields only */ default: - return this.generateFields(objectMetadata, kind, options); + return generateFields( + objectMetadata, + kind, + options, + this.inputTypeFactory, + ); } }, }); @@ -84,46 +92,4 @@ export class InputTypeDefinitionFactory { type: inputType, }; } - - private generateFields( - objectMetadata: ObjectMetadataInterface, - kind: InputTypeDefinitionKind, - options: WorkspaceBuildSchemaOptions, - ): GraphQLInputFieldConfigMap { - const fields: GraphQLInputFieldConfigMap = {}; - - for (const fieldMetadata of objectMetadata.fields) { - // Relation field types are generated during extension of object type definition - if (isRelationFieldMetadataType(fieldMetadata.type)) { - continue; - } - - const target = isCompositeFieldMetadataType(fieldMetadata.type) - ? fieldMetadata.type.toString() - : fieldMetadata.id; - - const isIdField = fieldMetadata.name === 'id'; - - const type = this.inputTypeFactory.create( - target, - fieldMetadata.type, - kind, - options, - { - nullable: fieldMetadata.isNullable, - defaultValue: fieldMetadata.defaultValue, - isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT, - settings: fieldMetadata.settings, - isIdField, - }, - ); - - fields[fieldMetadata.name] = { - type, - description: fieldMetadata.description, - }; - } - - return fields; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts index 1dbb46799b60..cf2efd3a733d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts @@ -1,14 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql'; +import { GraphQLObjectType } from 'graphql'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils'; import { pascalCase } from 'src/utils/pascal-case'; -import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.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 { OutputTypeFactory } from './output-type.factory'; @@ -39,48 +37,13 @@ export class ObjectTypeDefinitionFactory { type: new GraphQLObjectType({ name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`, description: objectMetadata.description, - fields: this.generateFields(objectMetadata, kind, options), + fields: generateFields( + objectMetadata, + kind, + options, + this.outputTypeFactory, + ), }), }; } - - private generateFields( - objectMetadata: ObjectMetadataInterface, - kind: ObjectTypeDefinitionKind, - options: WorkspaceBuildSchemaOptions, - ): GraphQLFieldConfigMap<any, any> { - const fields: GraphQLFieldConfigMap<any, any> = {}; - - for (const fieldMetadata of objectMetadata.fields) { - // Relation field types are generated during extension of object type definition - if (isRelationFieldMetadataType(fieldMetadata.type)) { - continue; - } - - const target = isCompositeFieldMetadataType(fieldMetadata.type) - ? fieldMetadata.type.toString() - : fieldMetadata.id; - - const type = this.outputTypeFactory.create( - target, - fieldMetadata.type, - kind, - options, - { - nullable: fieldMetadata.isNullable, - isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT, - settings: fieldMetadata.settings, - // Scalar type is already defined in the entity itself. - isIdField: false, - }, - ); - - fields[fieldMetadata.name] = { - type, - description: fieldMetadata.description, - }; - } - - return fields; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts index 1b8ef4c1cbb2..ea3bdda4c21c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts @@ -26,7 +26,7 @@ export class OutputTypeFactory { target: string, type: FieldMetadataType, kind: ObjectTypeDefinitionKind, - buildOtions: WorkspaceBuildSchemaOptions, + buildOptions: WorkspaceBuildSchemaOptions, typeOptions: TypeOptions, ): GraphQLOutputType { let gqlType: GraphQLOutputType | undefined = @@ -40,8 +40,9 @@ export class OutputTypeFactory { if (!gqlType) { this.logger.error(`Could not find a GraphQL type for ${target}`, { + kind, type, - buildOtions, + buildOptions, typeOptions, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts index c39111d3ff18..9f92a2e58d15 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts @@ -74,9 +74,7 @@ export class RootTypeFactory { const args = getResolverArgs(methodName); const objectType = this.typeDefinitionsStorage.getObjectTypeByKey( objectMetadata.id, - ['findMany', 'findDuplicates'].includes(methodName) - ? ObjectTypeDefinitionKind.Connection - : ObjectTypeDefinitionKind.Plain, + this.getObjectTypeDefinitionKindByMethodName(methodName), ); const argsType = this.argsFactory.create( { @@ -124,4 +122,17 @@ export class RootTypeFactory { return fieldConfigMap; } + + private getObjectTypeDefinitionKindByMethodName( + methodName: WorkspaceResolverBuilderMethodNames, + ): ObjectTypeDefinitionKind { + switch (methodName) { + case 'findMany': + case 'findDuplicates': + case 'search': + return ObjectTypeDefinitionKind.Connection; + default: + return ObjectTypeDefinitionKind.Plain; + } + } } 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-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index fb6597e19be9..429cd195faca 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -61,8 +61,6 @@ export class TypeMapperService { const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([ [FieldMetadataType.UUID, UUIDScalarType], [FieldMetadataType.TEXT, GraphQLString], - [FieldMetadataType.PHONE, GraphQLString], - [FieldMetadataType.EMAIL, GraphQLString], [FieldMetadataType.DATE_TIME, GraphQLISODateTime], [FieldMetadataType.DATE, GraphQLISODateTime], [FieldMetadataType.BOOLEAN, GraphQLBoolean], @@ -81,6 +79,7 @@ export class TypeMapperService { StringArrayScalarType as unknown as GraphQLScalarType, ], [FieldMetadataType.RICH_TEXT, GraphQLString], + [FieldMetadataType.TS_VECTOR, GraphQLString], ]); return typeScalarMapping.get(fieldMetadataType); @@ -101,8 +100,6 @@ export class TypeMapperService { >([ [FieldMetadataType.UUID, IDFilterType], [FieldMetadataType.TEXT, StringFilterType], - [FieldMetadataType.PHONE, StringFilterType], - [FieldMetadataType.EMAIL, StringFilterType], [FieldMetadataType.DATE_TIME, DateFilterType], [FieldMetadataType.DATE, DateFilterType], [FieldMetadataType.BOOLEAN, BooleanFilterType], @@ -118,6 +115,7 @@ export class TypeMapperService { [FieldMetadataType.RAW_JSON, RawJsonFilterType], [FieldMetadataType.RICH_TEXT, StringFilterType], [FieldMetadataType.ARRAY, ArrayFilterType], + [FieldMetadataType.TS_VECTOR, StringFilterType], // TODO: Add TSVectorFilterType ]); return typeFilterMapping.get(fieldMetadataType); @@ -129,8 +127,6 @@ export class TypeMapperService { const typeOrderByMapping = new Map<FieldMetadataType, GraphQLEnumType>([ [FieldMetadataType.UUID, OrderByDirectionType], [FieldMetadataType.TEXT, OrderByDirectionType], - [FieldMetadataType.PHONE, OrderByDirectionType], - [FieldMetadataType.EMAIL, OrderByDirectionType], [FieldMetadataType.DATE_TIME, OrderByDirectionType], [FieldMetadataType.DATE, OrderByDirectionType], [FieldMetadataType.BOOLEAN, OrderByDirectionType], @@ -143,6 +139,7 @@ export class TypeMapperService { [FieldMetadataType.RAW_JSON, OrderByDirectionType], [FieldMetadataType.RICH_TEXT, OrderByDirectionType], [FieldMetadataType.ARRAY, OrderByDirectionType], + [FieldMetadataType.TS_VECTOR, OrderByDirectionType], // TODO: Add TSVectorOrderByType ]); return typeOrderByMapping.get(fieldMetadataType); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils.ts new file mode 100644 index 000000000000..88e8091a0433 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils.ts @@ -0,0 +1,97 @@ +import { + GraphQLFieldConfigMap, + GraphQLInputFieldConfigMap, + GraphQLInputType, + GraphQLOutputType, +} from 'graphql'; + +import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory'; +import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory'; +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 { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; + +type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> = + { + create: ( + target: string, + fieldType: FieldMetadataType, + kind: T, + options: WorkspaceBuildSchemaOptions, + additionalOptions: { + nullable?: boolean; + defaultValue?: any; + isArray: boolean; + settings: any; + isIdField: boolean; + }, + ) => T extends InputTypeDefinitionKind + ? GraphQLInputType + : GraphQLOutputType; + }; + +export const generateFields = < + T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind, +>( + objectMetadata: ObjectMetadataInterface, + kind: T, + options: WorkspaceBuildSchemaOptions, + typeFactory: TypeFactory<T>, +): T extends InputTypeDefinitionKind + ? GraphQLInputFieldConfigMap + : GraphQLFieldConfigMap<any, any> => { + const fields = {}; + + for (const fieldMetadata of objectMetadata.fields) { + if (isRelationFieldMetadataType(fieldMetadata.type)) { + continue; + } + + const target = isCompositeFieldMetadataType(fieldMetadata.type) + ? fieldMetadata.type.toString() + : fieldMetadata.id; + + const typeFactoryOptions = isInputTypeDefinitionKind(kind) + ? { + nullable: fieldMetadata.isNullable, + defaultValue: fieldMetadata.defaultValue, + isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT, + settings: fieldMetadata.settings, + isIdField: fieldMetadata.name === 'id', + } + : { + nullable: fieldMetadata.isNullable, + isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT, + settings: fieldMetadata.settings, + // Scalar type is already defined in the entity itself. + isIdField: false, + }; + + const type = typeFactory.create( + target, + fieldMetadata.type, + kind, + options, + typeFactoryOptions, + ); + + fields[fieldMetadata.name] = { + type, + description: fieldMetadata.description, + }; + } + + return fields; +}; + +// Type guard +const isInputTypeDefinitionKind = ( + kind: InputTypeDefinitionKind | ObjectTypeDefinitionKind, +): kind is InputTypeDefinitionKind => { + return Object.values(InputTypeDefinitionKind).includes( + kind as InputTypeDefinitionKind, + ); +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts index c829e0e4088f..7e1755218730 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts @@ -137,6 +137,17 @@ export const getResolverArgs = ( isNullable: false, }, }; + case 'search': + return { + searchInput: { + type: GraphQLString, + isNullable: true, + }, + limit: { + type: GraphQLInt, + isNullable: true, + }, + }; default: throw new Error(`Unknown resolver type: ${type}`); } 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..558ece2b4768 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 @@ -76,6 +76,7 @@ export class WorkspaceSchemaFactory { (objectMetadataItem) => ({ ...objectMetadataItem, fields: Object.values(objectMetadataItem.fields), + indexes: objectMetadataItem.indexMetadatas, }), ); @@ -117,6 +118,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/map-field-metadata-to-graphql-query.utils.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts index 6976586f5a18..a5ce9aa30023 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts @@ -18,8 +18,6 @@ export const mapFieldMetadataToGraphqlQuery = ( const fieldIsSimpleValue = [ FieldMetadataType.UUID, FieldMetadataType.TEXT, - FieldMetadataType.PHONE, - FieldMetadataType.EMAIL, FieldMetadataType.DATE_TIME, FieldMetadataType.DATE, FieldMetadataType.BOOLEAN, @@ -32,6 +30,7 @@ export const mapFieldMetadataToGraphqlQuery = ( FieldMetadataType.RAW_JSON, FieldMetadataType.RICH_TEXT, FieldMetadataType.ARRAY, + FieldMetadataType.TS_VECTOR, ].includes(fieldType); if (fieldIsSimpleValue) { @@ -89,14 +88,6 @@ export const mapFieldMetadataToGraphqlQuery = ( } } }`; - } else if (fieldType === FieldMetadataType.LINK) { - return ` - ${field.name} - { - label - url - } - `; } else if (fieldType === FieldMetadataType.LINKS) { return ` ${field.name} diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts b/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts index 7ce66bafdd0f..2b4c8705d62d 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts +++ b/packages/twenty-server/src/engine/core-modules/analytics/analytics.module.ts @@ -1,14 +1,16 @@ -import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; -import { AnalyticsService } from './analytics.service'; import { AnalyticsResolver } from './analytics.resolver'; +import { AnalyticsService } from './analytics.service'; + +const TINYBIRD_BASE_URL = 'https://api.eu-central-1.aws.tinybird.co/v0'; @Module({ providers: [AnalyticsResolver, AnalyticsService], imports: [ HttpModule.register({ - baseURL: 'https://t.twenty.com/api/v1/s2s', + baseURL: TINYBIRD_BASE_URL, }), ], exports: [AnalyticsService], diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts b/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts index 06cd9aabed3b..71e76f8bd80a 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/analytics/analytics.resolver.ts @@ -1,6 +1,4 @@ -import { Args, Context, Mutation, Resolver } from '@nestjs/graphql'; - -import { Request } from 'express'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { User } from 'src/engine/core-modules/user/user.entity'; @@ -25,15 +23,11 @@ export class AnalyticsResolver { @Args() createAnalyticsInput: CreateAnalyticsInput, @AuthWorkspace() workspace: Workspace | undefined, @AuthUser({ allowUndefined: true }) user: User | undefined, - @Context('req') request: Request, ) { return this.analyticsService.create( createAnalyticsInput, user?.id, workspace?.id, - workspace?.displayName, - workspace?.domainName, - this.environmentService.get('SERVER_URL') ?? request.hostname, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts b/packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts index 085f4437837f..2b2eeb3d683e 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts +++ b/packages/twenty-server/src/engine/core-modules/analytics/analytics.service.ts @@ -1,16 +1,19 @@ -import { Injectable, Logger } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; +import { Injectable, Logger } from '@nestjs/common'; + +import { AxiosRequestConfig } from 'axios'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; type CreateEventInput = { - type: string; - data: object; + action: string; + payload: object; }; @Injectable() export class AnalyticsService { private readonly logger = new Logger(AnalyticsService.name); + private readonly defaultDatasource = 'event'; constructor( private readonly environmentService: EnvironmentService, @@ -21,30 +24,62 @@ export class AnalyticsService { createEventInput: CreateEventInput, userId: string | null | undefined, workspaceId: string | null | undefined, - workspaceDisplayName: string | undefined, - workspaceDomainName: string | undefined, - hostName: string | undefined, ) { - if (!this.environmentService.get('TELEMETRY_ENABLED')) { + if (!this.environmentService.get('ANALYTICS_ENABLED')) { return { success: true }; } - const data = { - type: createEventInput.type, - data: { - hostname: hostName, - userUUID: userId, - workspaceUUID: workspaceId, - workspaceDisplayName: workspaceDisplayName, - workspaceDomainName: workspaceDomainName, - ...createEventInput.data, + let data; + + switch (createEventInput.action) { + case 'pageview': + data = { + timestamp: new Date().toISOString(), + version: '1', + userId: userId, + workspaceId: workspaceId, + ...createEventInput.payload, + }; + break; + default: + data = { + action: createEventInput.action, + timestamp: new Date().toISOString(), + version: '1', + userId: userId, + workspaceId: workspaceId, + payload: { + ...createEventInput.payload, + }, + }; + break; + } + + const config: AxiosRequestConfig = { + headers: { + Authorization: + 'Bearer ' + this.environmentService.get('TINYBIRD_TOKEN'), }, }; + const datasource = + createEventInput.action === 'pageview' + ? 'pageview' + : this.defaultDatasource; + try { - await this.httpService.axiosRef.post('/v1', data); - } catch { - this.logger.error('Failed to send analytics event'); + await this.httpService.axiosRef.post( + `/events?name=${datasource}`, + data, + config, + ); + } catch (error) { + this.logger.error('Error occurred:', error); + if (error.response) { + this.logger.error( + `Error response body: ${JSON.stringify(error.response.data)}`, + ); + } return { success: false }; } diff --git a/packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts b/packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts index a870673fe17c..5e887dca0567 100644 --- a/packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts +++ b/packages/twenty-server/src/engine/core-modules/analytics/dtos/create-analytics.input.ts @@ -1,16 +1,16 @@ import { ArgsType, Field } from '@nestjs/graphql'; +import { IsNotEmpty, IsObject, IsString } from 'class-validator'; import graphqlTypeJson from 'graphql-type-json'; -import { IsNotEmpty, IsString, IsObject } from 'class-validator'; @ArgsType() export class CreateAnalyticsInput { @Field({ description: 'Type of the event' }) @IsNotEmpty() @IsString() - type: string; + action: string; - @Field(() => graphqlTypeJson, { description: 'Event data in JSON format' }) + @Field(() => graphqlTypeJson, { description: 'Event payload in JSON format' }) @IsObject() - data: JSON; + payload: JSON; } diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts index 1560c7f97ced..708472826d10 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts @@ -24,11 +24,10 @@ import { UserModule } from 'src/engine/core-modules/user/user.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { AuthResolver } from './auth.resolver'; @@ -47,9 +46,6 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; [Workspace, User, AppToken, FeatureFlagEntity], 'core', ), - ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, - ]), HttpModule, TokenModule, UserWorkspaceModule, @@ -57,6 +53,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; OnboardingModule, WorkspaceDataSourceModule, ConnectedAccountModule, + FeatureFlagModule, ], controllers: [ GoogleAuthController, diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-exchange-code-for-token.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-exchange-code-for-token.guard.ts index 77c6b41ce1d0..08baa4ff5a51 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-exchange-code-for-token.guard.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-exchange-code-for-token.guard.ts @@ -8,18 +8,33 @@ import { import { GoogleAPIsOauthExchangeCodeForTokenStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy'; import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { TokenService } from 'src/engine/core-modules/auth/token/services/token.service'; @Injectable() export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard( 'google-apis', ) { - constructor(private readonly environmentService: EnvironmentService) { + constructor( + private readonly environmentService: EnvironmentService, + private readonly featureFlagService: FeatureFlagService, + private readonly tokenService: TokenService, + ) { super(); } async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const state = JSON.parse(request.query.state); + const { workspaceId } = await this.tokenService.verifyTransientToken( + state.transientToken, + ); + const isGmailSendEmailScopeEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsGmailSendEmailScopeEnabled, + workspaceId, + ); if ( !this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') && @@ -34,6 +49,7 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard( new GoogleAPIsOauthExchangeCodeForTokenStrategy( this.environmentService, {}, + isGmailSendEmailScopeEnabled, ); setRequestExtraParams(request, { diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-request-code.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-request-code.guard.ts index 04d860d5ebc5..9b0e8f26062a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-request-code.guard.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/google-apis-oauth-request-code.guard.ts @@ -8,10 +8,17 @@ import { import { GoogleAPIsOauthRequestCodeStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy'; import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { TokenService } from 'src/engine/core-modules/auth/token/services/token.service'; @Injectable() export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') { - constructor(private readonly environmentService: EnvironmentService) { + constructor( + private readonly environmentService: EnvironmentService, + private readonly featureFlagService: FeatureFlagService, + private readonly tokenService: TokenService, + ) { super({ prompt: 'select_account', }); @@ -20,6 +27,15 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') { async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); + const { workspaceId } = await this.tokenService.verifyTransientToken( + request.query.transientToken, + ); + const isGmailSendEmailScopeEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsGmailSendEmailScopeEnabled, + workspaceId, + ); + if ( !this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') && !this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') @@ -30,12 +46,17 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') { ); } - new GoogleAPIsOauthRequestCodeStrategy(this.environmentService, {}); + new GoogleAPIsOauthRequestCodeStrategy( + this.environmentService, + {}, + isGmailSendEmailScopeEnabled, + ); setRequestExtraParams(request, { transientToken: request.query.transientToken, redirectLocation: request.query.redirectLocation, calendarVisibility: request.query.calendarVisibility, messageVisibility: request.query.messageVisibility, + loginHint: request.query.loginHint, }); const activate = (await super.canActivate(context)) as boolean; diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts index b20072ed54f1..04c77d2d9c19 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts @@ -7,7 +7,6 @@ import { EnvironmentService } from 'src/engine/core-modules/environment/environm 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 { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarEventListFetchJob, @@ -17,7 +16,6 @@ import { CalendarChannelVisibility, CalendarChannelWorkspaceEntity, } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; import { ConnectedAccountProvider, @@ -35,6 +33,9 @@ import { MessagingMessageListFetchJobData, } from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; +import { getGoogleApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @Injectable() export class GoogleAPIsService { @@ -45,9 +46,8 @@ export class GoogleAPIsService { @InjectMessageQueue(MessageQueue.calendarQueue) private readonly calendarQueueService: MessageQueueService, private readonly environmentService: EnvironmentService, - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, private readonly accountsToReconnectService: AccountsToReconnectService, + private readonly featureFlagService: FeatureFlagService, ) {} async refreshGoogleRefreshToken(input: { @@ -71,14 +71,17 @@ export class GoogleAPIsService { 'CALENDAR_PROVIDER_GOOGLE_ENABLED', ); - const connectedAccounts = - await this.connectedAccountRepository.getAllByHandleAndWorkspaceMemberId( - handle, - workspaceMemberId, + const connectedAccountRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>( workspaceId, + 'connectedAccount', ); - const existingAccountId = connectedAccounts?.[0]?.id; + const connectedAccount = await connectedAccountRepository.findOne({ + where: { handle, accountOwnerId: workspaceMemberId }, + }); + + const existingAccountId = connectedAccount?.id; const newOrExistingConnectedAccountId = existingAccountId ?? v4(); const calendarChannelRepository = @@ -96,9 +99,16 @@ export class GoogleAPIsService { const workspaceDataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId); + const isGmailSendEmailScopeEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsGmailSendEmailScopeEnabled, + workspaceId, + ); + const scopes = getGoogleApisOauthScopes(isGmailSendEmailScopeEnabled); + await workspaceDataSource.transaction(async (manager: EntityManager) => { if (!existingAccountId) { - await this.connectedAccountRepository.create( + await connectedAccountRepository.save( { id: newOrExistingConnectedAccountId, handle, @@ -106,8 +116,9 @@ export class GoogleAPIsService { accessToken: input.accessToken, refreshToken: input.refreshToken, accountOwnerId: workspaceMemberId, + scopes, }, - workspaceId, + {}, manager, ); @@ -140,11 +151,15 @@ export class GoogleAPIsService { ); } } else { - await this.connectedAccountRepository.updateAccessTokenAndRefreshToken( - input.accessToken, - input.refreshToken, - newOrExistingConnectedAccountId, - workspaceId, + await connectedAccountRepository.update( + { + id: newOrExistingConnectedAccountId, + }, + { + accessToken: input.accessToken, + refreshToken: input.refreshToken, + scopes, + }, manager, ); diff --git a/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-common.auth.strategy.ts b/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-common.auth.strategy.ts index 8636924735f9..addf4b6e78cd 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-common.auth.strategy.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-common.auth.strategy.ts @@ -4,6 +4,7 @@ import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-google-oauth20'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; +import { getGoogleApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes'; export type GoogleAPIScopeConfig = { isCalendarEnabled?: boolean; @@ -18,14 +19,9 @@ export class GoogleAPIsOauthCommonStrategy extends PassportStrategy( constructor( environmentService: EnvironmentService, scopeConfig: GoogleAPIScopeConfig, + isGmailSendEmailScopeEnabled = false, ) { - const scopes = [ - 'email', - 'profile', - 'https://www.googleapis.com/auth/gmail.readonly', - 'https://www.googleapis.com/auth/calendar.events', - 'https://www.googleapis.com/auth/profile.emails.read', - ]; + const scopes = getGoogleApisOauthScopes(isGmailSendEmailScopeEnabled); super({ clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'), diff --git a/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy.ts b/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy.ts index 244b1066d846..c8559bd141f2 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy.ts @@ -15,8 +15,9 @@ export class GoogleAPIsOauthExchangeCodeForTokenStrategy extends GoogleAPIsOauth constructor( environmentService: EnvironmentService, scopeConfig: GoogleAPIScopeConfig, + isGmailSendEmailScopeEnabled = false, ) { - super(environmentService, scopeConfig); + super(environmentService, scopeConfig, isGmailSendEmailScopeEnabled); } async validate( diff --git a/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy.ts b/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy.ts index f93642bc7ece..ee0782b9cd8b 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy.ts @@ -13,8 +13,9 @@ export class GoogleAPIsOauthRequestCodeStrategy extends GoogleAPIsOauthCommonStr constructor( environmentService: EnvironmentService, scopeConfig: GoogleAPIScopeConfig, + isGmailSendEmailScopeEnabled = false, ) { - super(environmentService, scopeConfig); + super(environmentService, scopeConfig, isGmailSendEmailScopeEnabled); } authenticate(req: any, options: any) { @@ -22,6 +23,7 @@ export class GoogleAPIsOauthRequestCodeStrategy extends GoogleAPIsOauthCommonStr ...options, accessType: 'offline', prompt: 'consent', + loginHint: req.params.loginHint, state: JSON.stringify({ transientToken: req.params.transientToken, redirectLocation: req.params.redirectLocation, diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts new file mode 100644 index 000000000000..e532c3cdf405 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts @@ -0,0 +1,17 @@ +export const getGoogleApisOauthScopes = ( + isGmailSendEmailScopeEnabled = false, +) => { + const scopes = [ + 'email', + 'profile', + 'https://www.googleapis.com/auth/gmail.readonly', + 'https://www.googleapis.com/auth/calendar.events', + 'https://www.googleapis.com/auth/profile.emails.read', + ]; + + if (isGmailSendEmailScopeEnabled) { + scopes.push('https://www.googleapis.com/auth/gmail.send'); + } + + return scopes; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util.ts index b6549ceff071..76743c04d0b4 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util.ts @@ -9,6 +9,7 @@ type GoogleAPIsRequestExtraParams = { redirectLocation?: string; calendarVisibility?: string; messageVisibility?: string; + loginHint?: string; }; export const setRequestExtraParams = ( @@ -20,6 +21,7 @@ export const setRequestExtraParams = ( redirectLocation, calendarVisibility, messageVisibility, + loginHint, } = params; if (!transientToken) { @@ -42,4 +44,7 @@ export const setRequestExtraParams = ( if (messageVisibility) { request.params.messageVisibility = messageVisibility; } + if (loginHint) { + request.params.loginHint = loginHint; + } }; diff --git a/packages/twenty-server/src/engine/core-modules/cache-storage/commands/flush-cache.command.ts b/packages/twenty-server/src/engine/core-modules/cache-storage/commands/flush-cache.command.ts index c07565936aa9..0a4d5135709f 100644 --- a/packages/twenty-server/src/engine/core-modules/cache-storage/commands/flush-cache.command.ts +++ b/packages/twenty-server/src/engine/core-modules/cache-storage/commands/flush-cache.command.ts @@ -1,15 +1,14 @@ import { Logger } from '@nestjs/common'; -import { Command, CommandRunner } from 'nest-commander'; +import { Command, CommandRunner, Option } from 'nest-commander'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -// TODO: implement dry-run @Command({ name: 'cache:flush', - description: 'Completely flush cache', + description: 'Flush cache for specific keys matching the pattern', }) export class FlushCacheCommand extends CommandRunner { private readonly logger = new Logger(FlushCacheCommand.name); @@ -21,9 +20,28 @@ export class FlushCacheCommand extends CommandRunner { super(); } - async run(): Promise<void> { - this.logger.log('Flushing cache...'); - await this.cacheStorage.flush(); + async run( + passedParams: string[], + options?: Record<string, any>, + ): Promise<void> { + const pattern = options?.pattern || '*'; + + this.logger.log(`Flushing cache for pattern: ${pattern}...`); + + if (pattern === '*') { + await this.cacheStorage.flush(); + } else { + await this.cacheStorage.flushByPattern(pattern); + } + this.logger.log('Cache flushed'); } + + @Option({ + flags: '-p, --pattern <pattern>', + description: 'Pattern to flush specific cache keys (e.g., engine:*)', + }) + parsePattern(val: string): string { + return val; + } } diff --git a/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts b/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts index e5195c18ad8a..7b132c7fbd57 100644 --- a/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts +++ b/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts @@ -1,5 +1,5 @@ -import { Inject, Injectable } from '@nestjs/common'; import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; +import { Inject, Injectable } from '@nestjs/common'; import { RedisCache } from 'cache-manager-redis-yet'; @@ -67,6 +67,31 @@ export class CacheStorageService { return this.cache.reset(); } + async flushByPattern(scanPattern: string): Promise<void> { + if (!this.isRedisCache()) { + throw new Error('flushByPattern is only supported with Redis cache'); + } + + const redisClient = (this.cache as RedisCache).store.client; + let cursor = 0; + + do { + const result = await redisClient.scan(cursor, { + MATCH: scanPattern, + COUNT: 100, + }); + + const nextCursor = result.cursor; + const keys = result.keys; + + if (keys.length > 0) { + await redisClient.del(keys); + } + + cursor = nextCursor; + } while (cursor !== 0); + } + private isRedisCache() { return (this.cache.store as any)?.name === 'redis'; } diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts index eefb2509be39..12cf5c3164dd 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts @@ -76,9 +76,6 @@ export class ClientConfig { @Field(() => AuthProviders, { nullable: false }) authProviders: AuthProviders; - @Field(() => Telemetry, { nullable: false }) - telemetry: Telemetry; - @Field(() => Billing, { nullable: false }) billing: Billing; diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts index 54844671bbb3..3615066a4390 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts @@ -1,4 +1,4 @@ -import { Resolver, Query } from '@nestjs/graphql'; +import { Query, Resolver } from '@nestjs/graphql'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; @@ -17,9 +17,6 @@ export class ClientConfigResolver { password: this.environmentService.get('AUTH_PASSWORD_ENABLED'), microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'), }, - telemetry: { - enabled: this.environmentService.get('TELEMETRY_ENABLED'), - }, billing: { isBillingEnabled: this.environmentService.get('IS_BILLING_ENABLED'), billingUrl: this.environmentService.get('BILLING_PLAN_REQUIRED_LINK'), diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index a78b636e2a9a..2e2df06c4d8e 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -7,43 +7,44 @@ import { AISQLQueryModule } from 'src/engine/core-modules/ai-sql-query/ai-sql-qu import { AppTokenModule } from 'src/engine/core-modules/app-token/app-token.module'; import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; +import { CacheStorageModule } from 'src/engine/core-modules/cache-storage/cache-storage.module'; import { TimelineCalendarEventModule } from 'src/engine/core-modules/calendar/timeline-calendar-event.module'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { HealthModule } from 'src/engine/core-modules/health/health.module'; -import { TimelineMessagingModule } from 'src/engine/core-modules/messaging/timeline-messaging.module'; -import { OpenApiModule } from 'src/engine/core-modules/open-api/open-api.module'; -import { PostgresCredentialsModule } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.module'; -import { UserModule } from 'src/engine/core-modules/user/user.module'; -import { WorkflowTriggerApiModule } from 'src/engine/core-modules/workflow/workflow-trigger-api.module'; -import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; -import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; +import { CaptchaModule } from 'src/engine/core-modules/captcha/captcha.module'; +import { captchaModuleFactory } from 'src/engine/core-modules/captcha/captcha.module-factory'; +import { EmailModule } from 'src/engine/core-modules/email/email.module'; +import { emailModuleFactory } from 'src/engine/core-modules/email/email.module-factory'; import { EnvironmentModule } from 'src/engine/core-modules/environment/environment.module'; -import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module'; -import { fileStorageModuleFactory } from 'src/engine/core-modules/file-storage/file-storage.module-factory'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { LoggerModule } from 'src/engine/core-modules/logger/logger.module'; -import { loggerModuleFactory } from 'src/engine/core-modules/logger/logger.module-factory'; -import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module'; -import { messageQueueModuleFactory } from 'src/engine/core-modules/message-queue/message-queue.module-factory'; import { ExceptionHandlerModule } from 'src/engine/core-modules/exception-handler/exception-handler.module'; import { exceptionHandlerModuleFactory } from 'src/engine/core-modules/exception-handler/exception-handler.module-factory'; -import { EmailModule } from 'src/engine/core-modules/email/email.module'; -import { emailModuleFactory } from 'src/engine/core-modules/email/email.module-factory'; -import { CaptchaModule } from 'src/engine/core-modules/captcha/captcha.module'; -import { captchaModuleFactory } from 'src/engine/core-modules/captcha/captcha.module-factory'; -import { CacheStorageModule } from 'src/engine/core-modules/cache-storage/cache-storage.module'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module'; +import { fileStorageModuleFactory } from 'src/engine/core-modules/file-storage/file-storage.module-factory'; +import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; +import { HealthModule } from 'src/engine/core-modules/health/health.module'; import { LLMChatModelModule } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.module'; import { llmChatModelModuleFactory } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.module-factory'; import { LLMTracingModule } from 'src/engine/core-modules/llm-tracing/llm-tracing.module'; import { llmTracingModuleFactory } from 'src/engine/core-modules/llm-tracing/llm-tracing.module-factory'; -import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module'; +import { LoggerModule } from 'src/engine/core-modules/logger/logger.module'; +import { loggerModuleFactory } from 'src/engine/core-modules/logger/logger.module-factory'; +import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module'; +import { messageQueueModuleFactory } from 'src/engine/core-modules/message-queue/message-queue.module-factory'; +import { TimelineMessagingModule } from 'src/engine/core-modules/messaging/timeline-messaging.module'; +import { OpenApiModule } from 'src/engine/core-modules/open-api/open-api.module'; +import { PostgresCredentialsModule } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.module'; import { serverlessModuleFactory } from 'src/engine/core-modules/serverless/serverless-module.factory'; -import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; +import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module'; +import { TelemetryModule } from 'src/engine/core-modules/telemetry/telemetry.module'; +import { UserModule } from 'src/engine/core-modules/user/user.module'; +import { WorkflowTriggerApiModule } from 'src/engine/core-modules/workflow/workflow-trigger-api.module'; import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.module'; +import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; +import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; -import { FileModule } from './file/file.module'; -import { ClientConfigModule } from './client-config/client-config.module'; import { AnalyticsModule } from './analytics/analytics.module'; +import { ClientConfigModule } from './client-config/client-config.module'; +import { FileModule } from './file/file.module'; @Module({ imports: [ @@ -66,6 +67,7 @@ import { AnalyticsModule } from './analytics/analytics.module'; WorkflowTriggerApiModule, WorkspaceEventEmitterModule, ActorModule, + TelemetryModule, EnvironmentModule.forRoot({}), FileStorageModule.forRootAsync({ useFactory: fileStorageModuleFactory, 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<Record>, - 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<Record>, - ) { - 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/environment/environment-variables.ts b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts index c3ecf518a092..cb5b2fbe2822 100644 --- a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts @@ -16,20 +16,20 @@ import { } from 'class-validator'; import { EmailDriver } from 'src/engine/core-modules/email/interfaces/email.interface'; +import { AwsRegion } from 'src/engine/core-modules/environment/interfaces/aws-region.interface'; import { NodeEnvironment } from 'src/engine/core-modules/environment/interfaces/node-environment.interface'; +import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface'; import { LLMChatModelDriver } from 'src/engine/core-modules/llm-chat-model/interfaces/llm-chat-model.interface'; import { LLMTracingDriver } from 'src/engine/core-modules/llm-tracing/interfaces/llm-tracing.interface'; -import { AwsRegion } from 'src/engine/core-modules/environment/interfaces/aws-region.interface'; -import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface'; -import { IsDuration } from 'src/engine/core-modules/environment/decorators/is-duration.decorator'; -import { IsAWSRegion } from 'src/engine/core-modules/environment/decorators/is-aws-region.decorator'; -import { CastToPositiveNumber } from 'src/engine/core-modules/environment/decorators/cast-to-positive-number.decorator'; -import { CastToLogLevelArray } from 'src/engine/core-modules/environment/decorators/cast-to-log-level-array.decorator'; -import { CastToBoolean } from 'src/engine/core-modules/environment/decorators/cast-to-boolean.decorator'; import { CacheStorageType } from 'src/engine/core-modules/cache-storage/types/cache-storage-type.enum'; import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces'; +import { CastToBoolean } from 'src/engine/core-modules/environment/decorators/cast-to-boolean.decorator'; +import { CastToLogLevelArray } from 'src/engine/core-modules/environment/decorators/cast-to-log-level-array.decorator'; +import { CastToPositiveNumber } from 'src/engine/core-modules/environment/decorators/cast-to-positive-number.decorator'; import { CastToStringArray } from 'src/engine/core-modules/environment/decorators/cast-to-string-array.decorator'; +import { IsAWSRegion } from 'src/engine/core-modules/environment/decorators/is-aws-region.decorator'; +import { IsDuration } from 'src/engine/core-modules/environment/decorators/is-duration.decorator'; import { IsStrictlyLowerThan } from 'src/engine/core-modules/environment/decorators/is-strictly-lower-than.decorator'; import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces'; @@ -88,6 +88,15 @@ export class EnvironmentVariables { @IsBoolean() TELEMETRY_ENABLED = true; + @CastToBoolean() + @IsOptional() + @IsBoolean() + ANALYTICS_ENABLED = false; + + @IsString() + @ValidateIf((env) => env.ANALYTICS_ENABLED) + TINYBIRD_TOKEN: string; + @CastToPositiveNumber() @IsNumber() @IsOptional() @@ -382,7 +391,7 @@ export class EnvironmentVariables { @CastToBoolean() MESSAGING_PROVIDER_GMAIL_ENABLED = false; - MESSAGE_QUEUE_TYPE: string = MessageQueueDriverType.Sync; + MESSAGE_QUEUE_TYPE: string = MessageQueueDriverType.BullMQ; EMAIL_FROM_ADDRESS = 'noreply@yourdomain.com'; @@ -417,7 +426,7 @@ export class EnvironmentVariables { @CastToPositiveNumber() API_RATE_LIMITING_LIMIT = 500; - CACHE_STORAGE_TYPE: CacheStorageType = CacheStorageType.Memory; + CACHE_STORAGE_TYPE: CacheStorageType = CacheStorageType.Redis; @CastToPositiveNumber() CACHE_STORAGE_TTL: number = 3600 * 24 * 7; diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record-destroy.event.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record-destroy.event.ts new file mode 100644 index 000000000000..f12b1e17547f --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record-destroy.event.ts @@ -0,0 +1,7 @@ +import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event'; + +export class ObjectRecordDestroyEvent<T> extends ObjectRecordBaseEvent { + properties: { + before: T; + }; +} diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts index c9c1806698f6..b18400d080bf 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts @@ -13,6 +13,7 @@ const mockObjectMetadata: ObjectMetadataInterface = { fromRelations: [], toRelations: [], fields: [], + indexMetadatas: [], isSystem: false, isCustom: false, isActive: true, diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 7bd085cc4136..8483f3089f6b 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -10,4 +10,9 @@ export enum FeatureFlagKey { IsMessageThreadSubscriberEnabled = 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED', IsQueryRunnerTwentyORMEnabled = 'IS_QUERY_RUNNER_TWENTY_ORM_ENABLED', IsWorkspaceFavoriteEnabled = 'IS_WORKSPACE_FAVORITE_ENABLED', + IsSearchEnabled = 'IS_SEARCH_ENABLED', + IsWorkspaceMigratedForSearch = 'IS_WORKSPACE_MIGRATED_FOR_SEARCH', + IsGmailSendEmailScopeEnabled = 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED', + IsAnalyticsV2Enabled = 'IS_ANALYTICS_V2_ENABLED', + IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED', } diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts index fc9c5038a2bd..cd2e4ca4d255 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts @@ -46,4 +46,17 @@ export class FeatureFlagService { return workspaceFeatureFlagsMap; } + + public async enableFeatureFlags( + keys: FeatureFlagKey[], + workspaceId: string, + ): Promise<void> { + await this.featureFlagRepository.upsert( + keys.map((key) => ({ workspaceId, key, value: true })), + { + conflictPaths: ['workspaceId', 'key'], + skipUpdateIfNoValuesChanged: true, + }, + ); + } } diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/drivers/interfaces/storage-driver.interface.ts b/packages/twenty-server/src/engine/core-modules/file-storage/drivers/interfaces/storage-driver.interface.ts index 84cf20a21894..65a0076249c0 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/drivers/interfaces/storage-driver.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/drivers/interfaces/storage-driver.interface.ts @@ -17,4 +17,8 @@ export interface StorageDriver { from: { folderPath: string; filename?: string }; to: { folderPath: string; filename?: string }; }): Promise<void>; + download(params: { + from: { folderPath: string; filename?: string }; + to: { folderPath: string; filename?: string }; + }): Promise<void>; } diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/drivers/local.driver.ts b/packages/twenty-server/src/engine/core-modules/file-storage/drivers/local.driver.ts index 3bbcc5493646..104808b4e63f 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/drivers/local.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/drivers/local.driver.ts @@ -21,10 +21,6 @@ export class LocalDriver implements StorageDriver { } async createFolder(path: string) { - if (existsSync(path)) { - return; - } - return fs.mkdir(path, { recursive: true }); } @@ -122,21 +118,24 @@ export class LocalDriver implements StorageDriver { } } - async copy(params: { - from: { folderPath: string; filename?: string }; - to: { folderPath: string; filename?: string }; - }): Promise<void> { + async copy( + params: { + from: { folderPath: string; filename?: string }; + to: { folderPath: string; filename?: string }; + }, + toInMemory = false, + ): Promise<void> { if (!params.from.filename && params.to.filename) { throw new Error('Cannot copy folder to file'); } const fromPath = join( - `${this.options.storagePath}/`, + this.options.storagePath, params.from.folderPath, params.from.filename || '', ); const toPath = join( - `${this.options.storagePath}/`, + toInMemory ? '' : this.options.storagePath, params.to.folderPath, params.to.filename || '', ); @@ -156,4 +155,11 @@ export class LocalDriver implements StorageDriver { throw error; } } + + async download(params: { + from: { folderPath: string; filename?: string }; + to: { folderPath: string; filename?: string }; + }): Promise<void> { + await this.copy(params, true); + } } diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/drivers/s3.driver.ts b/packages/twenty-server/src/engine/core-modules/file-storage/drivers/s3.driver.ts index 765ded5b0068..766be0b13b80 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/drivers/s3.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/drivers/s3.driver.ts @@ -1,4 +1,8 @@ import { Readable } from 'stream'; +import fs from 'fs'; +import { mkdir } from 'fs/promises'; +import { join } from 'path'; +import { pipeline } from 'stream/promises'; import { CopyObjectCommand, @@ -188,6 +192,23 @@ export class S3Driver implements StorageDriver { } } + extractFolderAndFilePaths(objectKey: string | undefined) { + if (!isDefined(objectKey)) { + return; + } + + const result = /(?<folder>.*)\/(?<file>.*)/.exec(objectKey); + + if (!isDefined(result) || !isDefined(result.groups)) { + return; + } + + const fromFolderPath = result.groups.folder; + const filename = result.groups.file; + + return { fromFolderPath, filename }; + } + async copy(params: { from: { folderPath: string; filename?: string }; to: { folderPath: string; filename?: string }; @@ -239,17 +260,18 @@ export class S3Driver implements StorageDriver { ); if (!listedObjects.Contents || listedObjects.Contents.length === 0) { - throw new Error('No objects found in the source folder.'); + throw new Error(`No objects found in the source folder ${fromKey}.`); } for (const object of listedObjects.Contents) { - const match = object.Key?.match(/(.*)\/(.*)/); + const folderAndFilePaths = this.extractFolderAndFilePaths(object.Key); - if (!isDefined(match)) { + if (!isDefined(folderAndFilePaths)) { continue; } - const fromFolderPath = match[1]; - const filename = match[2]; + + const { fromFolderPath, filename } = folderAndFilePaths; + const toFolderPath = fromFolderPath.replace( params.from.folderPath, params.to.folderPath, @@ -269,6 +291,85 @@ export class S3Driver implements StorageDriver { } } + async download(params: { + from: { folderPath: string; filename?: string }; + to: { folderPath: string; filename?: string }; + }): Promise<void> { + if (!params.from.filename && params.to.filename) { + throw new Error('Cannot copy folder to file'); + } + + if (isDefined(params.from.filename)) { + try { + const dir = params.to.folderPath; + + await mkdir(dir, { recursive: true }); + + const fileStream = await this.read({ + folderPath: params.from.folderPath, + filename: params.from.filename, + }); + + const toPath = join( + params.to.folderPath, + params.to.filename || params.from.filename, + ); + + await pipeline(fileStream, fs.createWriteStream(toPath)); + + return; + } catch (error) { + if (error.name === 'NotFound') { + throw new FileStorageException( + 'File not found', + FileStorageExceptionCode.FILE_NOT_FOUND, + ); + } + // For other errors, throw the original error + throw error; + } + } + + const listedObjects = await this.s3Client.send( + new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: params.from.folderPath, + }), + ); + + if (!listedObjects.Contents || listedObjects.Contents.length === 0) { + throw new Error( + `No objects found in the source folder ${params.from.folderPath}.`, + ); + } + + for (const object of listedObjects.Contents) { + const folderAndFilePaths = this.extractFolderAndFilePaths(object.Key); + + if (!isDefined(folderAndFilePaths)) { + continue; + } + + const { fromFolderPath, filename } = folderAndFilePaths; + const toFolderPath = fromFolderPath.replace( + params.from.folderPath, + params.to.folderPath, + ); + + if (!isDefined(toFolderPath)) { + continue; + } + + await this.download({ + from: { + folderPath: fromFolderPath, + filename, + }, + to: { folderPath: toFolderPath, filename }, + }); + } + } + async checkBucketExists(args: HeadBucketCommandInput) { try { await this.s3Client.headBucket(args); diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts b/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts index f23822b8a374..1b608e48ae4b 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts @@ -40,4 +40,11 @@ export class FileStorageService implements StorageDriver { }): Promise<void> { return this.driver.copy(params); } + + download(params: { + from: { folderPath: string; filename?: string }; + to: { folderPath: string; filename?: string }; + }): Promise<void> { + return this.driver.download(params); + } } 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..bde4e50992fa 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 @@ -1,19 +1,8 @@ -import { - fields, - objectMetadataItemMock, -} from 'src/engine/api/__mocks__/object-metadata-item.mock'; +import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { computeSchemaComponents } from 'src/engine/core-modules/open-api/utils/components.utils'; -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; 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, - ), - ); - }); it('should compute schema components', () => { expect( computeSchemaComponents([ @@ -21,6 +10,7 @@ describe('computeSchemaComponents', () => { ] as ObjectMetadataEntity[]), ).toEqual({ ObjectName: { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -30,9 +20,6 @@ describe('computeSchemaComponents', () => { fieldText: { type: 'string', }, - fieldPhone: { - type: 'string', - }, fieldPhones: { properties: { additionalPhones: { @@ -47,10 +34,6 @@ describe('computeSchemaComponents', () => { }, type: 'object', }, - fieldEmail: { - type: 'string', - format: 'email', - }, fieldEmails: { type: 'object', properties: { @@ -195,6 +178,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, }, @@ -203,6 +187,7 @@ describe('computeSchemaComponents', () => { required: ['fieldNumber'], }, 'ObjectName for Update': { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -212,9 +197,6 @@ describe('computeSchemaComponents', () => { fieldText: { type: 'string', }, - fieldPhone: { - type: 'string', - }, fieldPhones: { properties: { additionalPhones: { @@ -229,10 +211,6 @@ describe('computeSchemaComponents', () => { }, type: 'object', }, - fieldEmail: { - type: 'string', - format: 'email', - }, fieldEmails: { type: 'object', properties: { @@ -377,6 +355,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, }, @@ -384,6 +363,7 @@ describe('computeSchemaComponents', () => { }, }, 'ObjectName for Response': { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -393,9 +373,6 @@ describe('computeSchemaComponents', () => { fieldText: { type: 'string', }, - fieldPhone: { - type: 'string', - }, fieldPhones: { properties: { additionalPhones: { @@ -410,10 +387,6 @@ describe('computeSchemaComponents', () => { }, type: 'object', }, - fieldEmail: { - type: 'string', - format: 'email', - }, fieldEmails: { type: 'object', properties: { @@ -558,6 +531,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, workspaceMemberId: { diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts index 0a15ff0fd1b3..bab186af5231 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts @@ -1,5 +1,9 @@ import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/add-default-conjunction.utils'; +import { FilterComparators } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils'; +import { Conjunctions } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils'; +import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/input-factories/order-by-input.factory'; import { computeDepthParameters, computeEndingBeforeParameters, @@ -9,10 +13,6 @@ import { computeOrderByParameters, computeStartingAfterParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; -import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/input-factories/order-by-input.factory'; -import { FilterComparators } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils'; -import { Conjunctions } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils'; -import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/add-default-conjunction.utils'; describe('computeParameters', () => { describe('computeLimit', () => { @@ -83,7 +83,9 @@ describe('computeParameters', () => { name: 'filter', in: 'query', description: `Filters objects returned. - Should have the following shape: **field_1[COMPARATOR]:value_1,field_2[COMPARATOR]:value_2,...** + Should have the following shape: **field_1[COMPARATOR]:value_1,field_2[COMPARATOR]:value_2... + To filter on nested objects use **field.nestedField[COMPARATOR]:value_1 + ** Available comparators are **${Object.values(FilterComparators).join( '**, **', )}**. @@ -102,6 +104,10 @@ describe('computeParameters', () => { value: 'createdAt[gte]:"2023-01-01"', description: 'A simple filter param', }, + simpleNested: { + value: 'emails.primaryEmail[eq]:foo99@example.com', + description: 'A simple nested filter param', + }, complex: { value: 'or(createdAt[gte]:"2024-01-01",createdAt[lte]:"2023-01-01",not(id[is]:NULL))', diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 7e34e35a31fe..07ad4770c782 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -56,11 +56,8 @@ const getFieldProperties = ( case FieldMetadataType.UUID: return { type: 'string', format: 'uuid' }; case FieldMetadataType.TEXT: - case FieldMetadataType.PHONE: case FieldMetadataType.RICH_TEXT: return { type: 'string' }; - case FieldMetadataType.EMAIL: - return { type: 'string', format: 'email' }; case FieldMetadataType.DATE_TIME: return { type: 'string', format: 'date-time' }; case FieldMetadataType.DATE: @@ -109,7 +106,8 @@ const getSchemaComponentsProperties = ({ return item.fields.reduce((node, field) => { if ( !isFieldAvailable(field, forResponse) || - field.type === FieldMetadataType.RELATION + field.type === FieldMetadataType.RELATION || + field.type === FieldMetadataType.TS_VECTOR ) { return node; } @@ -138,7 +136,6 @@ const getSchemaComponentsProperties = ({ enum: field.options.map((option: { value: string }) => option.value), }; break; - case FieldMetadataType.LINK: case FieldMetadataType.LINKS: case FieldMetadataType.CURRENCY: case FieldMetadataType.FULL_NAME: diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts index ca009395f3b4..f16c6fe436b9 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts @@ -2,10 +2,10 @@ import { OpenAPIV3_1 } from 'openapi-types'; import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/add-default-conjunction.utils'; import { FilterComparators } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils'; import { Conjunctions } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils'; import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/input-factories/order-by-input.factory'; -import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/add-default-conjunction.utils'; export const computeLimitParameters = ( fromMetadata = false, @@ -73,7 +73,9 @@ export const computeFilterParameters = (): OpenAPIV3_1.ParameterObject => { name: 'filter', in: 'query', description: `Filters objects returned. - Should have the following shape: **field_1[COMPARATOR]:value_1,field_2[COMPARATOR]:value_2,...** + Should have the following shape: **field_1[COMPARATOR]:value_1,field_2[COMPARATOR]:value_2... + To filter on nested objects use **field.nestedField[COMPARATOR]:value_1 + ** Available comparators are **${Object.values(FilterComparators).join( '**, **', )}**. @@ -83,6 +85,7 @@ export const computeFilterParameters = (): OpenAPIV3_1.ParameterObject => { Default root conjunction is **${DEFAULT_CONJUNCTION}**. To filter **null** values use **field[is]:NULL** or **field[is]:NOT_NULL** To filter using **boolean** values use **field[eq]:true** or **field[eq]:false**`, + required: false, schema: { type: 'string', @@ -92,6 +95,10 @@ export const computeFilterParameters = (): OpenAPIV3_1.ParameterObject => { value: 'createdAt[gte]:"2023-01-01"', description: 'A simple filter param', }, + simpleNested: { + value: 'emails.primaryEmail[eq]:foo99@example.com', + description: 'A simple nested filter param', + }, complex: { value: 'or(createdAt[gte]:"2024-01-01",createdAt[lte]:"2023-01-01",not(id[is]:NULL))', diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/base-serverless.driver.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/base-serverless.driver.ts deleted file mode 100644 index e7abc6743302..000000000000 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/base-serverless.driver.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; -import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content'; -import { SOURCE_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/source-file-name'; -import { compileTypescript } from 'src/engine/core-modules/serverless/drivers/utils/compile-typescript'; -import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils'; - -export class BaseServerlessDriver { - async getCompiledCode( - serverlessFunction: ServerlessFunctionEntity, - fileStorageService: FileStorageService, - ) { - const folderPath = getServerlessFolder({ - serverlessFunction, - version: 'draft', - }); - const fileStream = await fileStorageService.read({ - folderPath, - filename: SOURCE_FILE_NAME, - }); - const typescriptCode = await readFileContent(fileStream); - - return compileTypescript(typescriptCode); - } -} diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/.gitignore b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/.gitignore new file mode 100644 index 000000000000..c5115e3f50b4 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/.gitignore @@ -0,0 +1 @@ +!base-typescript-project/**/.env diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/base-typescript-project/.env b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/base-typescript-project/.env new file mode 100644 index 000000000000..f7ba4ce53275 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/base-typescript-project/.env @@ -0,0 +1,2 @@ +# Add your environment variables here. +# Access them in your serverless function code using process.env.VARIABLE diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/base-typescript-project/src/index.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/base-typescript-project/src/index.ts new file mode 100644 index 000000000000..da19597d5cfb --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/base-typescript-project/src/index.ts @@ -0,0 +1,7 @@ +export const handler = async ( + event: object, + context: object, +): Promise<object> => { + // Your code here + return {}; +}; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/env-file-name.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/env-file-name.ts new file mode 100644 index 000000000000..4b409ce327af --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/env-file-name.ts @@ -0,0 +1 @@ +export const ENV_FILE_NAME = '.env'; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/index-file-name.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/index-file-name.ts new file mode 100644 index 000000000000..d32b30a4927a --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/index-file-name.ts @@ -0,0 +1 @@ +export const INDEX_FILE_NAME = 'index.ts'; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/outdir-folder.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/outdir-folder.ts new file mode 100644 index 000000000000..fed2d5ea331f --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/outdir-folder.ts @@ -0,0 +1 @@ +export const OUTDIR_FOLDER = 'dist'; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/source-file-name.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/source-file-name.ts deleted file mode 100644 index 9126720f9e5d..000000000000 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/constants/source-file-name.ts +++ /dev/null @@ -1 +0,0 @@ -export const SOURCE_FILE_NAME = 'source.ts'; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/lambda.driver.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/lambda.driver.ts index 9903309660d5..cf6315ca9a84 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/lambda.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/lambda.driver.ts @@ -1,6 +1,7 @@ import * as fs from 'fs/promises'; import { join } from 'path'; +import dotenv from 'dotenv'; import { CreateFunctionCommand, DeleteFunctionCommand, @@ -18,6 +19,8 @@ import { waitUntilFunctionUpdatedV2, ListLayerVersionsCommandInput, ListLayerVersionsCommand, + UpdateFunctionConfigurationCommand, + UpdateFunctionConfigurationCommandInput, } from '@aws-sdk/client-lambda'; import { CreateFunctionCommandInput } from '@aws-sdk/client-lambda/dist-types/commands/CreateFunctionCommand'; import { UpdateFunctionCodeCommandInput } from '@aws-sdk/client-lambda/dist-types/commands/UpdateFunctionCodeCommand'; @@ -36,7 +39,6 @@ import { NODE_LAYER_SUBFOLDER, } from 'src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; -import { BaseServerlessDriver } from 'src/engine/core-modules/serverless/drivers/base-serverless.driver'; import { createZipFile } from 'src/engine/core-modules/serverless/drivers/utils/create-zip-file'; import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto'; import { @@ -46,6 +48,11 @@ import { import { isDefined } from 'src/utils/is-defined'; import { COMMON_LAYER_NAME } from 'src/engine/core-modules/serverless/drivers/constants/common-layer-name'; import { copyAndBuildDependencies } from 'src/engine/core-modules/serverless/drivers/utils/copy-and-build-dependencies'; +import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils'; +import { SERVERLESS_TMPDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/serverless-tmpdir-folder'; +import { compileTypescript } from 'src/engine/core-modules/serverless/drivers/utils/compile-typescript'; +import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name'; +import { OUTDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/outdir-folder'; export interface LambdaDriverOptions extends LambdaClientConfig { fileStorageService: FileStorageService; @@ -53,16 +60,12 @@ export interface LambdaDriverOptions extends LambdaClientConfig { role: string; } -export class LambdaDriver - extends BaseServerlessDriver - implements ServerlessDriver -{ +export class LambdaDriver implements ServerlessDriver { private readonly lambdaClient: Lambda; private readonly lambdaRole: string; private readonly fileStorageService: FileStorageService; constructor(options: LambdaDriverOptions) { - super(); const { region, role, ...lambdaOptions } = options; this.lambdaClient = new Lambda({ ...lambdaOptions, region }); @@ -165,24 +168,50 @@ export class LambdaDriver } } - async build(serverlessFunction: ServerlessFunctionEntity) { - const javascriptCode = await this.getCompiledCode( + private getInMemoryServerlessFunctionFolderPath = ( + serverlessFunction: ServerlessFunctionEntity, + version: string, + ) => { + return join(SERVERLESS_TMPDIR_FOLDER, serverlessFunction.id, version); + }; + + async build(serverlessFunction: ServerlessFunctionEntity, version: string) { + const computedVersion = + version === 'latest' ? serverlessFunction.latestVersion : version; + + const inMemoryServerlessFunctionFolderPath = + this.getInMemoryServerlessFunctionFolderPath( + serverlessFunction, + computedVersion, + ); + + const folderPath = getServerlessFolder({ serverlessFunction, - this.fileStorageService, - ); + version, + }); - const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager(); + await this.fileStorageService.download({ + from: { folderPath }, + to: { folderPath: inMemoryServerlessFunctionFolderPath }, + }); - const { - sourceTemporaryDir, + compileTypescript(inMemoryServerlessFunctionFolderPath); + + const lambdaZipPath = join( + inMemoryServerlessFunctionFolderPath, + 'lambda.zip', + ); + + await createZipFile( + join(inMemoryServerlessFunctionFolderPath, OUTDIR_FOLDER), lambdaZipPath, - javascriptFilePath, - lambdaHandler, - } = await lambdaBuildDirectoryManager.init(); + ); - await fs.writeFile(javascriptFilePath, javascriptCode); + const envFileContent = await fs.readFile( + join(inMemoryServerlessFunctionFolderPath, ENV_FILE_NAME), + ); - await createZipFile(sourceTemporaryDir, lambdaZipPath); + const envVariables = dotenv.parse(envFileContent); const functionExists = await this.checkFunctionExists( serverlessFunction.id, @@ -198,8 +227,11 @@ export class LambdaDriver ZipFile: await fs.readFile(lambdaZipPath), }, FunctionName: serverlessFunction.id, - Handler: lambdaHandler, + Handler: 'src/index.handler', Layers: [layerArn], + Environment: { + Variables: envVariables, + }, Role: this.lambdaRole, Runtime: serverlessFunction.runtime, Description: 'Lambda function to run user script', @@ -210,23 +242,37 @@ export class LambdaDriver await this.lambdaClient.send(command); } else { - const params: UpdateFunctionCodeCommandInput = { + const updateCodeParams: UpdateFunctionCodeCommandInput = { ZipFile: await fs.readFile(lambdaZipPath), FunctionName: serverlessFunction.id, }; - const command = new UpdateFunctionCodeCommand(params); + const updateCodeCommand = new UpdateFunctionCodeCommand(updateCodeParams); - await this.lambdaClient.send(command); + await this.lambdaClient.send(updateCodeCommand); + + const updateConfigurationParams: UpdateFunctionConfigurationCommandInput = + { + Environment: { + Variables: envVariables, + }, + FunctionName: serverlessFunction.id, + }; + + const updateConfigurationCommand = new UpdateFunctionConfigurationCommand( + updateConfigurationParams, + ); + + await this.waitFunctionUpdates(serverlessFunction.id, 10); + + await this.lambdaClient.send(updateConfigurationCommand); } await this.waitFunctionUpdates(serverlessFunction.id, 10); - - await lambdaBuildDirectoryManager.clean(); } async publish(serverlessFunction: ServerlessFunctionEntity) { - await this.build(serverlessFunction); + await this.build(serverlessFunction, 'draft'); const params: PublishVersionCommandInput = { FunctionName: serverlessFunction.id, }; @@ -240,6 +286,20 @@ export class LambdaDriver throw new Error('New published version is undefined'); } + const draftFolderPath = getServerlessFolder({ + serverlessFunction: serverlessFunction, + version: 'draft', + }); + const newFolderPath = getServerlessFolder({ + serverlessFunction: serverlessFunction, + version: newVersion, + }); + + await this.fileStorageService.copy({ + from: { folderPath: draftFolderPath }, + to: { folderPath: newFolderPath }, + }); + return newVersion; } diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/local.driver.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/local.driver.ts index 00772cfd24a9..769c5c61524f 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/local.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/local.driver.ts @@ -1,10 +1,9 @@ import { fork } from 'child_process'; -import { promises as fs, existsSync } from 'fs'; +import { promises as fs } from 'fs'; import { join } from 'path'; -import { v4 } from 'uuid'; +import dotenv from 'dotenv'; -import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; import { ServerlessDriver, ServerlessExecuteError, @@ -12,35 +11,36 @@ import { } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; -import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content'; -import { BaseServerlessDriver } from 'src/engine/core-modules/serverless/drivers/base-serverless.driver'; -import { BUILD_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/build-file-name'; import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils'; import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto'; import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { - ServerlessFunctionException, - ServerlessFunctionExceptionCode, -} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception'; import { COMMON_LAYER_NAME } from 'src/engine/core-modules/serverless/drivers/constants/common-layer-name'; import { copyAndBuildDependencies } from 'src/engine/core-modules/serverless/drivers/utils/copy-and-build-dependencies'; import { SERVERLESS_TMPDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/serverless-tmpdir-folder'; +import { compileTypescript } from 'src/engine/core-modules/serverless/drivers/utils/compile-typescript'; +import { OUTDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/outdir-folder'; +import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name'; + +const LISTENER_FILE_NAME = 'listener.js'; export interface LocalDriverOptions { fileStorageService: FileStorageService; } -export class LocalDriver - extends BaseServerlessDriver - implements ServerlessDriver -{ +export class LocalDriver implements ServerlessDriver { private readonly fileStorageService: FileStorageService; constructor(options: LocalDriverOptions) { - super(); this.fileStorageService = options.fileStorageService; } + private getInMemoryServerlessFunctionFolderPath = ( + serverlessFunction: ServerlessFunctionEntity, + version: string, + ) => { + return join(SERVERLESS_TMPDIR_FOLDER, serverlessFunction.id, version); + }; + private getInMemoryLayerFolderPath = (version: number) => { return join(SERVERLESS_TMPDIR_FOLDER, COMMON_LAYER_NAME, `${version}`); }; @@ -49,88 +49,54 @@ export class LocalDriver const inMemoryLastVersionLayerFolderPath = this.getInMemoryLayerFolderPath(version); - if (existsSync(inMemoryLastVersionLayerFolderPath)) { - return; + try { + await fs.access(inMemoryLastVersionLayerFolderPath); + } catch (e) { + await copyAndBuildDependencies(inMemoryLastVersionLayerFolderPath); } - - await copyAndBuildDependencies(inMemoryLastVersionLayerFolderPath); } async delete() {} - async build(serverlessFunction: ServerlessFunctionEntity) { + async build(serverlessFunction: ServerlessFunctionEntity, version: string) { + const computedVersion = + version === 'latest' ? serverlessFunction.latestVersion : version; + await this.createLayerIfNotExists(serverlessFunction.layerVersion); - const javascriptCode = await this.getCompiledCode( - serverlessFunction, - this.fileStorageService, - ); - const draftFolderPath = getServerlessFolder({ + const inMemoryServerlessFunctionFolderPath = + this.getInMemoryServerlessFunctionFolderPath( + serverlessFunction, + computedVersion, + ); + + const folderPath = getServerlessFolder({ serverlessFunction, - version: 'draft', + version, }); - await this.fileStorageService.write({ - file: javascriptCode, - name: BUILD_FILE_NAME, - mimeType: undefined, - folder: draftFolderPath, + await this.fileStorageService.download({ + from: { folderPath }, + to: { folderPath: inMemoryServerlessFunctionFolderPath }, }); - } - - async publish(serverlessFunction: ServerlessFunctionEntity) { - await this.build(serverlessFunction); - - return serverlessFunction.latestVersion - ? `${parseInt(serverlessFunction.latestVersion, 10) + 1}` - : '1'; - } - async execute( - serverlessFunction: ServerlessFunctionEntity, - payload: object, - version: string, - ): Promise<ServerlessExecuteResult> { - await this.createLayerIfNotExists(serverlessFunction.layerVersion); - - const startTime = Date.now(); - let fileContent = ''; - - try { - const fileStream = await this.fileStorageService.read({ - folderPath: getServerlessFolder({ - serverlessFunction, - version, - }), - filename: BUILD_FILE_NAME, - }); + compileTypescript(inMemoryServerlessFunctionFolderPath); - fileContent = await readFileContent(fileStream); - } catch (error) { - if (error.code === FileStorageExceptionCode.FILE_NOT_FOUND) { - throw new ServerlessFunctionException( - `Function Version '${version}' does not exist`, - ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND, - ); - } - throw error; - } - - const tmpFolderPath = join(SERVERLESS_TMPDIR_FOLDER, v4()); - - const tmpFilePath = join(tmpFolderPath, 'index.js'); - - await fs.symlink( - this.getInMemoryLayerFolderPath(serverlessFunction.layerVersion), - tmpFolderPath, - 'dir', + const envFileContent = await fs.readFile( + join(inMemoryServerlessFunctionFolderPath, ENV_FILE_NAME), ); - const modifiedContent = ` + const envVariables = dotenv.parse(envFileContent); + + const listener = ` + const index_1 = require("./src/index"); + + process.env = ${JSON.stringify(envVariables)} + process.on('message', async (message) => { const { event, context } = message; try { - const result = await handler(event, context); + const result = await index_1.handler(event, context); process.send(result); } catch (error) { process.send({ @@ -140,82 +106,158 @@ export class LocalDriver }); } }); - - ${fileContent} `; - await fs.writeFile(tmpFilePath, modifiedContent); + await fs.writeFile( + join( + inMemoryServerlessFunctionFolderPath, + OUTDIR_FOLDER, + LISTENER_FILE_NAME, + ), + listener, + ); + + try { + await fs.symlink( + join( + this.getInMemoryLayerFolderPath(serverlessFunction.layerVersion), + 'node_modules', + ), + join( + inMemoryServerlessFunctionFolderPath, + OUTDIR_FOLDER, + 'node_modules', + ), + 'dir', + ); + } catch (err) { + if (err.code !== 'EEXIST') { + throw err; + } + } + } + + async publish(serverlessFunction: ServerlessFunctionEntity) { + const newVersion = serverlessFunction.latestVersion + ? `${parseInt(serverlessFunction.latestVersion, 10) + 1}` + : '1'; + + const draftFolderPath = getServerlessFolder({ + serverlessFunction: serverlessFunction, + version: 'draft', + }); + const newFolderPath = getServerlessFolder({ + serverlessFunction: serverlessFunction, + version: newVersion, + }); + + await this.fileStorageService.copy({ + from: { folderPath: draftFolderPath }, + to: { folderPath: newFolderPath }, + }); + + await this.build(serverlessFunction, newVersion); + + return newVersion; + } + + async execute( + serverlessFunction: ServerlessFunctionEntity, + payload: object, + version: string, + ): Promise<ServerlessExecuteResult> { + const startTime = Date.now(); + const computedVersion = + version === 'latest' ? serverlessFunction.latestVersion : version; + + const listenerFile = join( + this.getInMemoryServerlessFunctionFolderPath( + serverlessFunction, + computedVersion, + ), + OUTDIR_FOLDER, + LISTENER_FILE_NAME, + ); - return await new Promise((resolve, reject) => { - const child = fork(tmpFilePath, { silent: true }); + try { + return await new Promise((resolve, reject) => { + const child = fork(listenerFile, { silent: true }); + + child.on('message', (message: object | ServerlessExecuteError) => { + const duration = Date.now() - startTime; + + if ('errorType' in message) { + resolve({ + data: null, + duration, + error: message, + status: ServerlessFunctionExecutionStatus.ERROR, + }); + } else { + resolve({ + data: message, + duration, + status: ServerlessFunctionExecutionStatus.SUCCESS, + }); + } + child.kill(); + }); + + child.stderr?.on('data', (data) => { + const stackTrace = data + .toString() + .split('\n') + .filter((line: string) => line.trim() !== ''); + const errorTrace = stackTrace.filter((line: string) => + line.includes('Error: '), + )?.[0]; + + let errorType = 'Unknown'; + let errorMessage = ''; - child.on('message', (message: object | ServerlessExecuteError) => { - const duration = Date.now() - startTime; + if (errorTrace) { + errorType = errorTrace.split(':')[0]; + errorMessage = errorTrace.split(': ')[1]; + } + const duration = Date.now() - startTime; - if ('errorType' in message) { resolve({ data: null, duration, - error: message, status: ServerlessFunctionExecutionStatus.ERROR, + error: { + errorType, + errorMessage, + stackTrace: stackTrace, + }, }); - } else { - resolve({ - data: message, - duration, - status: ServerlessFunctionExecutionStatus.SUCCESS, - }); - } - child.kill(); - fs.unlink(tmpFilePath).catch(console.error); - }); + child.kill(); + }); - child.stderr?.on('data', (data) => { - const stackTrace = data - .toString() - .split('\n') - .filter((line: string) => line.trim() !== ''); - const errorTrace = stackTrace.filter((line: string) => - line.includes('Error: '), - )?.[0]; - - let errorType = 'Unknown'; - let errorMessage = ''; - - if (errorTrace) { - errorType = errorTrace.split(':')[0]; - errorMessage = errorTrace.split(': ')[1]; - } - const duration = Date.now() - startTime; - - resolve({ - data: null, - duration, - status: ServerlessFunctionExecutionStatus.ERROR, - error: { - errorType, - errorMessage, - stackTrace: stackTrace, - }, + child.on('error', (error) => { + reject(error); + child.kill(); }); - child.kill(); - fs.unlink(tmpFilePath).catch(console.error); - }); - child.on('error', (error) => { - reject(error); - child.kill(); - fs.unlink(tmpFilePath).catch(console.error); - }); + child.on('exit', (code) => { + if (code && code !== 0) { + reject(new Error(`Child process exited with code ${code}`)); + } + }); - child.on('exit', (code) => { - if (code && code !== 0) { - reject(new Error(`Child process exited with code ${code}`)); - fs.unlink(tmpFilePath).catch(console.error); - } + child.send({ event: payload }); }); - - child.send({ event: payload }); - }); + } catch (error) { + return { + data: null, + duration: Date.now() - startTime, + error: { + errorType: 'UnhandledError', + errorMessage: error.message || 'Unknown error', + stackTrace: error.stack ? error.stack.split('\n') : [], + }, + status: ServerlessFunctionExecutionStatus.ERROR, + }; + } } } diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/compile-typescript.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/compile-typescript.ts index 7db82434a7f0..5e23b6a67a95 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/compile-typescript.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/compile-typescript.ts @@ -1,6 +1,11 @@ -import ts from 'typescript'; +import { join } from 'path'; -export const compileTypescript = (typescriptCode: string): string => { +import ts, { createProgram } from 'typescript'; + +import { OUTDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/outdir-folder'; +import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name'; + +export const compileTypescript = (folderPath: string) => { const options: ts.CompilerOptions = { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017, @@ -8,12 +13,9 @@ export const compileTypescript = (typescriptCode: string): string => { esModuleInterop: true, resolveJsonModule: true, allowSyntheticDefaultImports: true, + outDir: join(folderPath, OUTDIR_FOLDER, 'src'), types: ['node'], }; - const result = ts.transpileModule(typescriptCode, { - compilerOptions: options, - }); - - return result.outputText; + createProgram([join(folderPath, 'src', INDEX_FILE_NAME)], options).emit(); }; 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 new file mode 100644 index 000000000000..3d3a8c2a8952 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts @@ -0,0 +1,39 @@ +import fs from 'fs/promises'; +import path, { join } from 'path'; + +import { ASSET_PATH } from 'src/constants/assets-path'; + +type File = { name: string; path: string; content: Buffer }; + +const getAllFiles = async ( + rootDir: string, + dir: string = rootDir, + files: File[] = [], +): Promise<File[]> => { + const dirEntries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of dirEntries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + return getAllFiles(rootDir, fullPath, files); + } else { + files.push({ + path: path.relative(rootDir, dir), + name: entry.name, + content: await fs.readFile(fullPath), + }); + } + } + + return files; +}; + +export const getBaseTypescriptProjectFiles = (async () => { + const baseTypescriptProjectPath = join( + ASSET_PATH, + `engine/core-modules/serverless/drivers/constants/base-typescript-project`, + ); + + return await getAllFiles(baseTypescriptProjectPath); +})(); diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-layer-dependencies-dir-name.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-layer-dependencies-dir-name.ts index 3c6ee4e8b26e..a576ea89e649 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-layer-dependencies-dir-name.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-layer-dependencies-dir-name.ts @@ -1,12 +1,17 @@ -import path from 'path'; +import path, { join } from 'path'; import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version'; +import { ASSET_PATH } from 'src/constants/assets-path'; -// Can only be used in src/engine/integrations/serverless/drivers/utils folder export const getLayerDependenciesDirName = ( version: 'latest' | 'engine' | number, ): string => { const formattedVersion = version === 'latest' ? LAST_LAYER_VERSION : version; - return path.resolve(__dirname, `../layers/${formattedVersion}`); + const baseTypescriptProjectPath = join( + ASSET_PATH, + `engine/core-modules/serverless/drivers/layers/${formattedVersion}`, + ); + + return path.resolve(baseTypescriptProjectPath); }; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager.ts index b67811f9742f..4ec30f7f4f45 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager.ts @@ -10,14 +10,12 @@ export const NODE_LAYER_SUBFOLDER = 'nodejs'; const TEMPORARY_LAMBDA_FOLDER = 'lambda-build'; const TEMPORARY_LAMBDA_SOURCE_FOLDER = 'src'; const LAMBDA_ZIP_FILE_NAME = 'lambda.zip'; -const LAMBDA_ENTRY_FILE_NAME = 'index.js'; export class LambdaBuildDirectoryManager { private temporaryDir = join( SERVERLESS_TMPDIR_FOLDER, `${TEMPORARY_LAMBDA_FOLDER}-${v4()}`, ); - private lambdaHandler = `${LAMBDA_ENTRY_FILE_NAME.split('.')[0]}.handler`; async init() { const sourceTemporaryDir = join( @@ -25,15 +23,12 @@ export class LambdaBuildDirectoryManager { TEMPORARY_LAMBDA_SOURCE_FOLDER, ); const lambdaZipPath = join(this.temporaryDir, LAMBDA_ZIP_FILE_NAME); - const javascriptFilePath = join(sourceTemporaryDir, LAMBDA_ENTRY_FILE_NAME); await fs.mkdir(sourceTemporaryDir, { recursive: true }); return { sourceTemporaryDir, lambdaZipPath, - javascriptFilePath, - lambdaHandler: this.lambdaHandler, }; } diff --git a/packages/twenty-server/src/engine/core-modules/telemetry/telemetry.module.ts b/packages/twenty-server/src/engine/core-modules/telemetry/telemetry.module.ts new file mode 100644 index 000000000000..b9be5ec208d0 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/telemetry/telemetry.module.ts @@ -0,0 +1,15 @@ +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; + +import { TelemetryService } from './telemetry.service'; + +@Module({ + providers: [TelemetryService], + imports: [ + HttpModule.register({ + baseURL: 'https://t.twenty.com/api/v2', + }), + ], + exports: [TelemetryService], +}) +export class TelemetryModule {} diff --git a/packages/twenty-server/src/engine/core-modules/telemetry/telemetry.service.ts b/packages/twenty-server/src/engine/core-modules/telemetry/telemetry.service.ts new file mode 100644 index 000000000000..6f59f9847811 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/telemetry/telemetry.service.ts @@ -0,0 +1,55 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable, Logger } from '@nestjs/common'; + +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; + +type CreateEventInput = { + action: string; + payload: object; +}; + +@Injectable() +export class TelemetryService { + private readonly logger = new Logger(TelemetryService.name); + + constructor( + private readonly environmentService: EnvironmentService, + private readonly httpService: HttpService, + ) {} + + async create( + createEventInput: CreateEventInput, + userId: string | null | undefined, + workspaceId: string | null | undefined, + ) { + if (!this.environmentService.get('TELEMETRY_ENABLED')) { + return { success: true }; + } + + const data = { + action: createEventInput.action, + timestamp: new Date().toISOString(), + version: '1', + payload: { + userId: userId, + workspaceId: workspaceId, + ...createEventInput.payload, + }, + }; + + try { + await this.httpService.axiosRef.post(`/selfHostingEvent`, data); + } catch (error) { + this.logger.error('Error occurred:', error); + if (error.response) { + this.logger.error( + `Error response body: ${JSON.stringify(error.response.data)}`, + ); + } + + return { success: false }; + } + + return { success: true }; + } +} diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index 31fdd6379aed..4f26a8b0e0df 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -5,19 +5,19 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { Repository } from 'typeorm'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; +import { + AppToken, + AppTokenType, +} from 'src/engine/core-modules/app-token/app-token.entity'; +import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { User } from 'src/engine/core-modules/user/user.entity'; +import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { assert } from 'src/utils/assert'; -import { - AppToken, - AppTokenType, -} from 'src/engine/core-modules/app-token/app-token.entity'; -import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> { constructor( diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.spec.ts index 6a3d8b4400a6..0c53265e6302 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.spec.ts @@ -2,16 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; +import { EmailService } from 'src/engine/core-modules/email/email.service'; +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { UserService } from 'src/engine/core-modules/user/services/user.service'; import { User } from 'src/engine/core-modules/user/user.entity'; +import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { EmailService } from 'src/engine/core-modules/email/email.service'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; -import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { WorkspaceService } from './workspace.service'; @@ -66,6 +67,10 @@ describe('WorkspaceService', () => { provide: WorkspaceInvitationService, useValue: {}, }, + { + provide: FeatureFlagService, + useValue: {}, + }, ], }).compile(); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index 0be6fd02f97d..38befbf6e12d 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { ModuleRef } from '@nestjs/core'; +import { InjectRepository } from '@nestjs/typeorm'; import assert from 'assert'; @@ -8,6 +8,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { Repository } from 'typeorm'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { User } from 'src/engine/core-modules/user/user.entity'; @@ -17,6 +18,7 @@ import { WorkspaceActivationStatus, } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; +import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags'; // eslint-disable-next-line @nx/workspace-inject-workspace-repository export class WorkspaceService extends TypeOrmQueryService<Workspace> { @@ -29,6 +31,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> { @InjectRepository(UserWorkspace, 'core') private readonly userWorkspaceRepository: Repository<UserWorkspace>, private readonly workspaceManagerService: WorkspaceManagerService, + private readonly featureFlagService: FeatureFlagService, private readonly billingSubscriptionService: BillingSubscriptionService, private moduleRef: ModuleRef, ) { @@ -69,6 +72,11 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> { activationStatus: WorkspaceActivationStatus.ONGOING_CREATION, }); + await this.featureFlagService.enableFeatureFlags( + DEFAULT_FEATURE_FLAGS, + user.defaultWorkspaceId, + ); + await this.workspaceManagerService.init(user.defaultWorkspaceId); await this.userWorkspaceService.createWorkspaceMember( user.defaultWorkspaceId, diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts index 040b945329d4..bd03d0e12c88 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts @@ -5,7 +5,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; import { FileModule } from 'src/engine/core-modules/file/file.module'; import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module'; @@ -13,12 +13,12 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { UserWorkspaceResolver } from 'src/engine/core-modules/user-workspace/user-workspace.resolver'; import { User } from 'src/engine/core-modules/user/user.entity'; +import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.module'; import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener'; import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.resolver'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; -import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.module'; import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts'; import { Workspace } from './workspace.entity'; @@ -35,11 +35,12 @@ import { WorkspaceService } from './services/workspace.service'; FileUploadModule, WorkspaceMetadataCacheModule, NestjsQueryTypeOrmModule.forFeature( - [User, Workspace, UserWorkspace, FeatureFlagEntity], + [User, Workspace, UserWorkspace], 'core', ), UserWorkspaceModule, WorkspaceManagerModule, + FeatureFlagModule, DataSourceModule, OnboardingModule, TypeORMModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/constants/search-vector-field.constants.ts b/packages/twenty-server/src/engine/metadata-modules/constants/search-vector-field.constants.ts new file mode 100644 index 000000000000..498dca583cc2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/constants/search-vector-field.constants.ts @@ -0,0 +1,5 @@ +export const SEARCH_VECTOR_FIELD = { + name: 'searchVector', + label: 'Search vector', + description: 'Field used for full-text search', +} as const; 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/composite-types/emails.composite-type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type.ts index 9ca5ceea55a6..5cda75160530 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type.ts @@ -10,6 +10,7 @@ export const emailsCompositeType: CompositeType = { type: FieldMetadataType.TEXT, hidden: false, isRequired: false, + isIncludedInUniqueConstraint: true, }, { name: 'additionalEmails', diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type.ts index 835adb703501..937a9f6aae3a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type.ts @@ -10,12 +10,14 @@ export const fullNameCompositeType: CompositeType = { type: FieldMetadataType.TEXT, hidden: false, isRequired: false, + isIncludedInUniqueConstraint: true, }, { name: 'lastName', type: FieldMetadataType.TEXT, hidden: false, isRequired: false, + isIncludedInUniqueConstraint: true, }, ], }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts index de361576a7bb..fbc999d49e70 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts @@ -5,7 +5,6 @@ import { addressCompositeType } from 'src/engine/metadata-modules/field-metadata import { currencyCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type'; import { emailsCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type'; import { fullNameCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type'; -import { linkCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/link.composite-type'; import { linksCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type'; import { phonesCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -14,7 +13,6 @@ export const compositeTypeDefinitions = new Map< FieldMetadataType, CompositeType >([ - [FieldMetadataType.LINK, linkCompositeType], [FieldMetadataType.LINKS, linksCompositeType], [FieldMetadataType.CURRENCY, currencyCompositeType], [FieldMetadataType.FULL_NAME, fullNameCompositeType], diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/link.composite-type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/link.composite-type.ts deleted file mode 100644 index 4037a5d5c9f8..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/link.composite-type.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface'; - -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; - -export const linkCompositeType: CompositeType = { - type: FieldMetadataType.LINK, - properties: [ - { - name: 'label', - type: FieldMetadataType.TEXT, - hidden: false, - isRequired: false, - }, - { - name: 'url', - type: FieldMetadataType.TEXT, - hidden: false, - isRequired: false, - }, - ], -}; - -export type LinkMetadata = { - label: string; - url: string; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/links.composite-type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/links.composite-type.ts index 2238e2175847..eedfeb4da0ab 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/links.composite-type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/links.composite-type.ts @@ -16,6 +16,7 @@ export const linksCompositeType: CompositeType = { type: FieldMetadataType.TEXT, hidden: false, isRequired: false, + isIncludedInUniqueConstraint: true, }, { name: 'secondaryLinks', diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type.ts index a53661779b80..366e957545cb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type.ts @@ -10,6 +10,7 @@ export const phonesCompositeType: CompositeType = { type: FieldMetadataType.TEXT, hidden: false, isRequired: false, + isIncludedInUniqueConstraint: true, }, { name: 'primaryPhoneCountryCode', diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts index a617f8ad971e..42d98b4d6e55 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/default-value.input.ts @@ -71,16 +71,6 @@ export class FieldMetadataDefaultValueDate { value: Date | null; } -export class FieldMetadataDefaultValueLink { - @ValidateIf((object, value) => value !== null) - @IsQuotedString() - label: string | null; - - @ValidateIf((object, value) => value !== null) - @IsQuotedString() - url: string | null; -} - export class FieldMetadataDefaultValueCurrency { @ValidateIf((object, value) => value !== null) @IsNumberString() diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts index b81cf7a76b8b..862fcd4e0156 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts @@ -118,6 +118,11 @@ export class FieldMetadataDTO< @Field({ nullable: true }) isNullable?: boolean; + @IsBoolean() + @IsOptional() + @Field({ nullable: true }) + isUnique?: boolean; + @Validate(IsFieldMetadataDefaultValue) @IsOptional() @Field(() => GraphQLJSON, { nullable: true }) 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<T>; + }) { + switch (fieldType) { + case FieldMetadataType.NUMBER: + this.validateNumberSettings(settings); + break; + default: + break; + } + } + + private validateNumberSettings(settings: FieldMetadataSettings<T>) { + 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.entity.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts index 3f5413aeb2b8..31d2dab39185 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts @@ -24,16 +24,13 @@ import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-met export enum FieldMetadataType { UUID = 'UUID', TEXT = 'TEXT', - PHONE = 'PHONE', PHONES = 'PHONES', - EMAIL = 'EMAIL', EMAILS = 'EMAILS', DATE_TIME = 'DATE_TIME', DATE = 'DATE', BOOLEAN = 'BOOLEAN', NUMBER = 'NUMBER', NUMERIC = 'NUMERIC', - LINK = 'LINK', LINKS = 'LINKS', CURRENCY = 'CURRENCY', FULL_NAME = 'FULL_NAME', @@ -47,6 +44,7 @@ export enum FieldMetadataType { RICH_TEXT = 'RICH_TEXT', ACTOR = 'ACTOR', ARRAY = 'ARRAY', + TS_VECTOR = 'TS_VECTOR', } @Entity('fieldMetadata') @@ -110,6 +108,9 @@ export class FieldMetadataEntity< @Column({ nullable: true, default: true }) isNullable: boolean; + @Column({ nullable: true, default: false }) + isUnique: boolean; + @Column({ nullable: false, type: 'uuid' }) workspaceId: string; @@ -128,7 +129,7 @@ export class FieldMetadataEntity< @OneToMany( () => IndexFieldMetadataEntity, (indexFieldMetadata: IndexFieldMetadataEntity) => - indexFieldMetadata.fieldMetadata, + indexFieldMetadata.indexMetadata, { cascade: true, }, 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..f402f5b0b1b0 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 @@ -55,7 +55,9 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global. import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; 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 { isDefined } from 'src/utils/is-defined'; +import { FieldMetadataValidationService } from './field-metadata-validation.service'; import { FieldMetadataEntity, FieldMetadataType, @@ -82,6 +84,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit private readonly typeORMService: TypeORMService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly fieldMetadataValidationService: FieldMetadataValidationService, ) { super(fieldMetadataRepository); } @@ -142,27 +145,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit fieldMetadataInput.options = generateRatingOptions(); } - if (fieldMetadataInput.type === FieldMetadataType.LINK) { - throw new FieldMetadataException( - '"Link" field types are being deprecated, please use Links type instead', - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } - - if (fieldMetadataInput.type === FieldMetadataType.EMAIL) { - throw new FieldMetadataException( - '"Email" field types are being deprecated, please use Emails type instead', - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } - - this.validateFieldMetadataInput<CreateFieldInput>( - fieldMetadataInput, - objectMetadata, - ); - - console.time('createOne save'); - const createdFieldMetadata = await fieldMetadataRepository.save({ + const fieldMetadataForCreate = { id: v4(), createdAt: new Date(), updatedAt: new Date(), @@ -183,7 +166,18 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit : undefined, isActive: true, isCustom: true, - }); + }; + + this.validateFieldMetadata<CreateFieldInput>( + fieldMetadataForCreate.type, + fieldMetadataForCreate, + objectMetadata, + ); + + console.time('createOne save'); + const createdFieldMetadata = await fieldMetadataRepository.save( + fieldMetadataForCreate, + ); console.timeEnd('createOne save'); @@ -390,11 +384,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit } } - this.validateFieldMetadataInput<UpdateFieldInput>( - fieldMetadataInput, - objectMetadata, - ); - const updatableFieldInput = existingFieldMetadata.isCustom === false ? this.buildUpdatableStandardFieldInput( @@ -403,21 +392,22 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit ) : fieldMetadataInput; - // We're running field update under a transaction, so we can rollback if migration fails - await fieldMetadataRepository.update(id, { + const fieldMetadataForUpdate = { ...updatableFieldInput, defaultValue: - // Todo: we handle default value for all field types. - ![ - FieldMetadataType.SELECT, - FieldMetadataType.MULTI_SELECT, - FieldMetadataType.BOOLEAN, - ].includes(existingFieldMetadata.type) - ? existingFieldMetadata.defaultValue - : updatableFieldInput.defaultValue !== null - ? updatableFieldInput.defaultValue - : null, - }); + updatableFieldInput.defaultValue !== undefined + ? updatableFieldInput.defaultValue + : existingFieldMetadata.defaultValue, + }; + + this.validateFieldMetadata<UpdateFieldInput>( + existingFieldMetadata.type, + fieldMetadataForUpdate, + objectMetadata, + ); + + // We're running field update under a transaction, so we can rollback if migration fails + await fieldMetadataRepository.update(id, fieldMetadataForUpdate); const updatedFieldMetadata = await fieldMetadataRepository.findOne({ where: { id }, @@ -705,9 +695,11 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit } } - private validateFieldMetadataInput< - T extends UpdateFieldInput | CreateFieldInput, - >(fieldMetadataInput: T, objectMetadata: ObjectMetadataEntity): T { + private validateFieldMetadata<T extends UpdateFieldInput | CreateFieldInput>( + fieldMetadataType: FieldMetadataType, + fieldMetadataInput: T, + objectMetadata: ObjectMetadataEntity, + ): T { if (fieldMetadataInput.name) { try { validateFieldNameValidityOrThrow(fieldMetadataInput.name); @@ -737,6 +729,15 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit } } + if (fieldMetadataInput.isNullable === false) { + if (!isDefined(fieldMetadataInput.defaultValue)) { + throw new FieldMetadataException( + 'Default value is required for non nullable fields', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + } + if (fieldMetadataInput.options) { for (const option of fieldMetadataInput.options) { if (exceedsDatabaseIdentifierMaximumLength(option.value)) { @@ -748,6 +749,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit } } + if (fieldMetadataInput.settings) { + this.fieldMetadataValidationService.validateSettingsOrThrow({ + fieldType: fieldMetadataType, + settings: fieldMetadataInput.settings, + }); + } + return fieldMetadataInput; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface.ts index c6f34b31806b..321cd3c7f917 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface.ts @@ -10,6 +10,7 @@ export interface CompositeProperty< type: Type; hidden: 'input' | 'output' | true | false; isRequired: boolean; + isIncludedInUniqueConstraint?: boolean; isArray?: boolean; options?: FieldMetadataOptions<Type>; } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts index 07f18bd4a2c6..262cd9f22760 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts @@ -6,7 +6,6 @@ import { FieldMetadataDefaultValueDateTime, FieldMetadataDefaultValueEmails, FieldMetadataDefaultValueFullName, - FieldMetadataDefaultValueLink, FieldMetadataDefaultValueLinks, FieldMetadataDefaultValueNowFunction, FieldMetadataDefaultValueNumber, @@ -27,9 +26,7 @@ type FieldMetadataDefaultValueMapping = { | FieldMetadataDefaultValueString | FieldMetadataDefaultValueUuidFunction; [FieldMetadataType.TEXT]: FieldMetadataDefaultValueString; - [FieldMetadataType.PHONE]: FieldMetadataDefaultValueString; [FieldMetadataType.PHONES]: FieldMetadataDefaultValuePhones; - [FieldMetadataType.EMAIL]: FieldMetadataDefaultValueString; [FieldMetadataType.EMAILS]: FieldMetadataDefaultValueEmails; [FieldMetadataType.DATE_TIME]: | FieldMetadataDefaultValueDateTime @@ -41,7 +38,6 @@ type FieldMetadataDefaultValueMapping = { [FieldMetadataType.NUMBER]: FieldMetadataDefaultValueNumber; [FieldMetadataType.POSITION]: FieldMetadataDefaultValueNumber; [FieldMetadataType.NUMERIC]: FieldMetadataDefaultValueString; - [FieldMetadataType.LINK]: FieldMetadataDefaultValueLink; [FieldMetadataType.LINKS]: FieldMetadataDefaultValueLinks; [FieldMetadataType.CURRENCY]: FieldMetadataDefaultValueCurrency; [FieldMetadataType.FULL_NAME]: FieldMetadataDefaultValueFullName; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts index c79f33047386..e0d40c0f7ccf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts @@ -12,10 +12,21 @@ type FieldMetadataDefaultSettings = { type FieldMetadataNumberSettings = { dataType: NumberDataType; + decimals?: number; +}; + +type FieldMetadataDateSettings = { + displayAsRelativeDate?: boolean; +}; + +type FieldMetadataDateTimeSettings = { + displayAsRelativeDate?: boolean; }; type FieldMetadataSettingsMapping = { [FieldMetadataType.NUMBER]: FieldMetadataNumberSettings; + [FieldMetadataType.DATE]: FieldMetadataDateSettings; + [FieldMetadataType.DATE_TIME]: FieldMetadataDateTimeSettings; }; type SettingsByFieldMetadata<T extends FieldMetadataType | 'default'> = diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts index 7367a1dac737..a33fe7392cdd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts @@ -19,7 +19,10 @@ export interface FieldMetadataInterface< workspaceId?: string; description?: string; isNullable?: boolean; + isUnique?: boolean; fromRelationMetadata?: RelationMetadataEntity; toRelationMetadata?: RelationMetadataEntity; isCustom?: boolean; + generatedType?: 'STORED' | 'VIRTUAL'; + asExpression?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts index 8d805dfe4a03..cc903fba3c1d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface.ts @@ -1,3 +1,5 @@ +import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface'; + import { FieldMetadataInterface } from './field-metadata.interface'; import { RelationMetadataInterface } from './relation-metadata.interface'; @@ -13,6 +15,7 @@ export interface ObjectMetadataInterface { fromRelations: RelationMetadataInterface[]; toRelations: RelationMetadataInterface[]; fields: FieldMetadataInterface[]; + indexMetadatas: IndexMetadataInterface[]; isSystem: boolean; isCustom: boolean; isActive: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/generate-nullable.spec.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/generate-nullable.spec.ts index 6d4ab103196d..7d98519bd66d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/generate-nullable.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/generate-nullable.spec.ts @@ -4,16 +4,10 @@ import { generateNullable } from 'src/engine/metadata-modules/field-metadata/uti describe('generateNullable', () => { it('should generate a nullable value false for TEXT, EMAIL, PHONE no matter what the input is', () => { expect(generateNullable(FieldMetadataType.TEXT, false)).toEqual(false); - expect(generateNullable(FieldMetadataType.PHONE, false)).toEqual(false); - expect(generateNullable(FieldMetadataType.EMAIL, false)).toEqual(false); expect(generateNullable(FieldMetadataType.TEXT, true)).toEqual(false); - expect(generateNullable(FieldMetadataType.PHONE, true)).toEqual(false); - expect(generateNullable(FieldMetadataType.EMAIL, true)).toEqual(false); expect(generateNullable(FieldMetadataType.TEXT)).toEqual(false); - expect(generateNullable(FieldMetadataType.PHONE)).toEqual(false); - expect(generateNullable(FieldMetadataType.EMAIL)).toEqual(false); }); it('should should return true if no input is given', () => { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/validate-default-value-based-on-type.spec.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/validate-default-value-based-on-type.spec.ts index 3695a072ec9c..60e4b04261c3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/validate-default-value-based-on-type.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/__tests__/validate-default-value-based-on-type.spec.ts @@ -40,32 +40,6 @@ describe('validateDefaultValueForType', () => { ).toBe(false); }); - it('should validate string default value for PHONE type', () => { - expect( - validateDefaultValueForType(FieldMetadataType.PHONE, "'+123456789'") - .isValid, - ).toBe(true); - }); - - it('should return false for invalid string default value for PHONE type', () => { - expect( - validateDefaultValueForType(FieldMetadataType.PHONE, 123).isValid, - ).toBe(false); - }); - - it('should validate string default value for EMAIL type', () => { - expect( - validateDefaultValueForType(FieldMetadataType.EMAIL, "'test@example.com'") - .isValid, - ).toBe(true); - }); - - it('should return false for invalid string default value for EMAIL type', () => { - expect( - validateDefaultValueForType(FieldMetadataType.EMAIL, 123).isValid, - ).toBe(false); - }); - it('should validate number default value for NUMBER type', () => { expect( validateDefaultValueForType(FieldMetadataType.NUMBER, 100).isValid, @@ -90,27 +64,6 @@ describe('validateDefaultValueForType', () => { ).toBe(false); }); - // LINK type - it('should validate LINK default value', () => { - expect( - validateDefaultValueForType(FieldMetadataType.LINK, { - label: "'http://example.com'", - url: "'Example'", - }).isValid, - ).toBe(true); - }); - - it('should return false for invalid LINK default value', () => { - expect( - validateDefaultValueForType( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error Just for testing purposes - { label: 123, url: {} }, - FieldMetadataType.LINK, - ).isValid, - ).toBe(false); - }); - // CURRENCY type it('should validate CURRENCY default value', () => { expect( diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts index 263448dab8e2..bc24729a7277 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts @@ -11,6 +11,11 @@ import { pascalCase } from 'src/utils/pascal-case'; type ComputeColumnNameOptions = { isForeignKey?: boolean }; +export type FieldTypeAndNameMetadata = { + name: string; + type: FieldMetadataType; +}; + export function computeColumnName( fieldName: string, options?: ComputeColumnNameOptions, @@ -48,13 +53,16 @@ export function computeCompositeColumnName( export function computeCompositeColumnName< T extends FieldMetadataType | 'default', >( - fieldMetadata: FieldMetadataInterface<T>, + fieldMetadata: FieldTypeAndNameMetadata | FieldMetadataInterface<T>, compositeProperty: CompositeProperty, ): string; export function computeCompositeColumnName< T extends FieldMetadataType | 'default', >( - fieldMetadataOrFieldName: FieldMetadataInterface<T> | string, + fieldMetadataOrFieldName: + | FieldTypeAndNameMetadata + | FieldMetadataInterface<T> + | string, compositeProperty: CompositeProperty, ): string { const generateName = (name: string) => { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-default-value.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-default-value.ts index 958ff0e3212b..3d9f8bcf368a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-default-value.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-default-value.ts @@ -7,8 +7,6 @@ export function generateDefaultValue( ): FieldMetadataDefaultValue { switch (type) { case FieldMetadataType.TEXT: - case FieldMetadataType.PHONE: - case FieldMetadataType.EMAIL: return "''"; case FieldMetadataType.EMAILS: return { @@ -31,11 +29,6 @@ export function generateDefaultValue( addressLat: null, addressLng: null, }; - case FieldMetadataType.LINK: - return { - url: "''", - label: "''", - }; case FieldMetadataType.CURRENCY: return { amountMicros: null, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-nullable.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-nullable.ts index 5724314847b0..759012070ffc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-nullable.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/generate-nullable.ts @@ -11,8 +11,6 @@ export function generateNullable( switch (type) { case FieldMetadataType.TEXT: - case FieldMetadataType.PHONE: - case FieldMetadataType.EMAIL: return false; default: return inputNullableValue ?? true; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util.ts index 110673bd1cf6..911162c9ae39 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util.ts @@ -3,7 +3,6 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi export const isCompositeFieldMetadataType = ( type: FieldMetadataType, ): type is - | FieldMetadataType.LINK | FieldMetadataType.CURRENCY | FieldMetadataType.FULL_NAME | FieldMetadataType.ADDRESS @@ -12,7 +11,6 @@ export const isCompositeFieldMetadataType = ( | FieldMetadataType.EMAILS | FieldMetadataType.PHONES => { return [ - FieldMetadataType.LINK, FieldMetadataType.CURRENCY, FieldMetadataType.FULL_NAME, FieldMetadataType.ADDRESS, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts index 06303046259d..a5cea6d00482 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts @@ -15,7 +15,6 @@ import { FieldMetadataDefaultValueDateTime, FieldMetadataDefaultValueEmails, FieldMetadataDefaultValueFullName, - FieldMetadataDefaultValueLink, FieldMetadataDefaultValueLinks, FieldMetadataDefaultValueNowFunction, FieldMetadataDefaultValueNumber, @@ -34,8 +33,6 @@ export const defaultValueValidatorsMap = { FieldMetadataDefaultValueUuidFunction, ], [FieldMetadataType.TEXT]: [FieldMetadataDefaultValueString], - [FieldMetadataType.PHONE]: [FieldMetadataDefaultValueString], - [FieldMetadataType.EMAIL]: [FieldMetadataDefaultValueString], [FieldMetadataType.DATE_TIME]: [ FieldMetadataDefaultValueDateTime, FieldMetadataDefaultValueNowFunction, @@ -44,7 +41,6 @@ export const defaultValueValidatorsMap = { [FieldMetadataType.BOOLEAN]: [FieldMetadataDefaultValueBoolean], [FieldMetadataType.NUMBER]: [FieldMetadataDefaultValueNumber], [FieldMetadataType.NUMERIC]: [FieldMetadataDefaultValueString], - [FieldMetadataType.LINK]: [FieldMetadataDefaultValueLink], [FieldMetadataType.CURRENCY]: [FieldMetadataDefaultValueCurrency], [FieldMetadataType.FULL_NAME]: [FieldMetadataDefaultValueFullName], [FieldMetadataType.RATING]: [FieldMetadataDefaultValueString], diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto.ts new file mode 100644 index 000000000000..8235ec9b335d --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto.ts @@ -0,0 +1,62 @@ +import { Field, HideField, ObjectType } from '@nestjs/graphql'; + +import { + Authorize, + FilterableField, + IDField, + QueryOptions, + Relation, +} from '@ptc-org/nestjs-query-graphql'; +import { IsDateString, IsNotEmpty, IsNumber, IsUUID } from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; + +import { IndexMetadataDTO } from './index-metadata.dto'; + +@ObjectType('indexField') +@Authorize({ + authorize: (context: any) => ({ + workspaceId: { eq: context?.req?.workspace?.id }, + }), +}) +@QueryOptions({ + defaultResultSize: 10, + disableSort: true, + maxResultsSize: 1000, +}) +@Relation('indexMetadata', () => IndexMetadataDTO, { + nullable: true, +}) +@Relation('fieldMetadata', () => FieldMetadataDTO, { + nullable: true, +}) +export class IndexFieldMetadataDTO { + @IsUUID() + @IsNotEmpty() + @IDField(() => UUIDScalarType) + id: string; + + indexMetadataId: string; + + @IsUUID() + @IsNotEmpty() + @FilterableField(() => UUIDScalarType) + fieldMetadataId: string; + + @IsNumber() + @IsNotEmpty() + @Field() + order: number; + + @IsDateString() + @Field() + createdAt: Date; + + @IsDateString() + @Field() + updatedAt: Date; + + @HideField() + workspaceId: string; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto.ts new file mode 100644 index 000000000000..209c1cafa7ec --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto.ts @@ -0,0 +1,93 @@ +import { + Field, + HideField, + ObjectType, + registerEnumType, +} from '@nestjs/graphql'; + +import { + Authorize, + CursorConnection, + FilterableField, + IDField, + QueryOptions, +} from '@ptc-org/nestjs-query-graphql'; +import { + IsBoolean, + IsDateString, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator'; +import { IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; + +registerEnumType(IndexType, { + name: 'IndexType', + description: 'Type of the index', +}); + +@ObjectType('index') +@Authorize({ + authorize: (context: any) => ({ + workspaceId: { eq: context?.req?.workspace?.id }, + }), +}) +@QueryOptions({ + defaultResultSize: 10, + disableSort: true, + maxResultsSize: 1000, +}) +@CursorConnection('objectMetadata', () => ObjectMetadataDTO) +@CursorConnection('indexFieldMetadatas', () => IndexFieldMetadataDTO) +export class IndexMetadataDTO { + @IsUUID() + @IsNotEmpty() + @IDField(() => UUIDScalarType) + id: string; + + @IsString() + @IsNotEmpty() + @Field() + @IsValidMetadataName() + name: string; + + @IsBoolean() + @IsOptional() + @FilterableField({ nullable: true }) + isCustom?: boolean; + + @IsBoolean() + @IsNotEmpty() + @Field() + isUnique: boolean; + + @IsString() + @IsOptional() + @Field({ nullable: true }) + indexWhereClause?: string; + + @IsEnum(IndexType) + @IsNotEmpty() + @Field(() => IndexType) + indexType: IndexType; + + objectMetadataId: string; + + @IsDateString() + @Field() + createdAt: Date; + + @IsDateString() + @Field() + updatedAt: Date; + + @HideField() + workspaceId: string; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts index 23f4e7bb8d7d..fc2f991ce225 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts @@ -13,11 +13,22 @@ import { import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +export enum IndexType { + BTREE = 'BTREE', + GIN = 'GIN', +} + @Entity('indexMetadata') export class IndexMetadataEntity { @PrimaryGeneratedColumn('uuid') id: string; + @CreateDateColumn({ type: 'timestamptz' }) + createdAt: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + updatedAt: Date; + @Column({ nullable: false }) name: string; @@ -27,7 +38,7 @@ export class IndexMetadataEntity { @Column({ nullable: false, type: 'uuid' }) objectMetadataId: string; - @ManyToOne(() => ObjectMetadataEntity, (object) => object.indexes, { + @ManyToOne(() => ObjectMetadataEntity, (object) => object.indexMetadatas, { onDelete: 'CASCADE', }) @JoinColumn() @@ -43,9 +54,20 @@ export class IndexMetadataEntity { ) indexFieldMetadatas: Relation<IndexFieldMetadataEntity[]>; - @CreateDateColumn({ type: 'timestamptz' }) - createdAt: Date; + @Column({ default: false }) + isCustom: boolean; - @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt: Date; + @Column({ nullable: false, default: false }) + isUnique: boolean; + + @Column({ type: 'text', nullable: true }) + indexWhereClause: string | null; + + @Column({ + type: 'enum', + enum: IndexType, + default: IndexType.BTREE, + nullable: false, + }) + indexType?: IndexType; } diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts index c22a56dfb222..826fb9b796e4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts @@ -1,14 +1,50 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { SortDirection } from '@ptc-org/nestjs-query-core'; +import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql'; +import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; + +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto'; +import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; +import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; @Module({ imports: [ TypeOrmModule.forFeature([IndexMetadataEntity], 'metadata'), - WorkspaceMigrationModule, + NestjsQueryGraphQLModule.forFeature({ + imports: [ + NestjsQueryTypeOrmModule.forFeature( + [IndexMetadataEntity, IndexFieldMetadataEntity], + 'metadata', + ), + WorkspaceMigrationModule, + ], + services: [IndexMetadataService], + resolvers: [ + { + EntityClass: IndexMetadataEntity, + DTOClass: IndexMetadataDTO, + read: { + defaultSort: [{ field: 'id', direction: SortDirection.DESC }], + many: { + name: 'indexMetadatas', //TODO: check + singular + }, + }, + create: { + disabled: true, + }, + update: { disabled: true }, + delete: { disabled: true }, + guards: [WorkspaceAuthGuard], + interceptors: [ObjectMetadataGraphqlApiExceptionInterceptor], + }, + ], + }), ], providers: [IndexMetadataService], exports: [IndexMetadataService], diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts index 97d9fdb9b3a7..362519ef78a4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts @@ -1,10 +1,14 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { isDefined } from 'class-validator'; import { Repository } from 'typeorm'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { + IndexMetadataEntity, + IndexType, +} from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; @@ -28,6 +32,10 @@ export class IndexMetadataService { workspaceId: string, objectMetadata: ObjectMetadataEntity, fieldMetadataToIndex: Partial<FieldMetadataEntity>[], + isUnique: boolean, + isCustom: boolean, + indexType?: IndexType, + indexWhereClause?: string, ) { const tableName = computeObjectTargetTable(objectMetadata); @@ -53,6 +61,8 @@ export class IndexMetadataService { ), workspaceId, objectMetadataId: objectMetadata.id, + ...(isDefined(indexType) ? { indexType: indexType } : {}), + isCustom: isCustom, }); } catch (error) { throw new Error( @@ -74,6 +84,9 @@ export class IndexMetadataService { action: WorkspaceMigrationIndexActionType.CREATE, columns: columnNames, name: indexName, + isUnique, + where: indexWhereClause, + type: indexType, }, ], } satisfies WorkspaceMigrationTableAction; diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface.ts new file mode 100644 index 000000000000..f9fd9c6f3402 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface.ts @@ -0,0 +1,11 @@ +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; +import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface'; + +export interface IndexFieldMetadataInterface { + id: string; + indexMetadataId: string; + fieldMetadataId: string; + fieldMetadata: FieldMetadataInterface; + indexMetadata: IndexMetadataInterface; + order: number; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface.ts new file mode 100644 index 000000000000..2b928a967250 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface.ts @@ -0,0 +1,7 @@ +import { IndexFieldMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface'; + +export interface IndexMetadataInterface { + name: string; + isUnique: boolean; + indexFieldMetadatas: IndexFieldMetadataInterface[]; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts index 6a494370d962..e390aca9f236 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts @@ -11,6 +11,7 @@ import { import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto'; import { BeforeDeleteOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-delete-one-object.hook'; @ObjectType('object') @@ -26,6 +27,7 @@ import { BeforeDeleteOneObject } from 'src/engine/metadata-modules/object-metada }) @BeforeDeleteOne(BeforeDeleteOneObject) @CursorConnection('fields', () => FieldMetadataDTO) +@CursorConnection('indexMetadatas', () => IndexMetadataDTO) export class ObjectMetadataDTO { @IDField(() => UUIDScalarType) id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts index 3d550b02fe5d..9cfc8bedc12e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts @@ -86,7 +86,7 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface { @OneToMany(() => IndexMetadataEntity, (index) => index.objectMetadata, { cascade: true, }) - indexes: Relation<IndexMetadataEntity[]>; + indexMetadatas: Relation<IndexMetadataEntity[]>; @OneToMany( () => RelationMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index 94a721dec53d..14d9d58c2200 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -10,9 +10,11 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module'; import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor'; import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver'; @@ -44,6 +46,8 @@ import { UpdateObjectPayload } from './dtos/update-object.input'; WorkspaceMigrationRunnerModule, WorkspaceMetadataVersionModule, RemoteTableRelationsModule, + IndexMetadataModule, + FeatureFlagModule, ], services: [ObjectMetadataService], resolvers: [ diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index c0240c6c2f1b..83db047cd859 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -5,19 +5,30 @@ import console from 'console'; import { Query, QueryOptions } from '@ptc-org/nestjs-query-core'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; +import { isDefined } from 'class-validator'; import { FindManyOptions, FindOneOptions, In, Repository } from 'typeorm'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { TypeORMService } from 'src/database/typeorm/typeorm.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'; +import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { FieldMetadataEntity, FieldMetadataType, } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { + computeColumnName, + FieldTypeAndNameMetadata, +} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; +import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants'; import { ObjectMetadataException, ObjectMetadataExceptionCode, @@ -33,12 +44,15 @@ import { RelationToDelete } from 'src/engine/metadata-modules/relation-metadata/ import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; +import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnDrop, + WorkspaceMigrationTableAction, WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -58,6 +72,7 @@ import { createForeignKeyDeterministicUuid, createRelationDeterministicUuid, } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; @@ -79,12 +94,18 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt private readonly remoteTableRelationsService: RemoteTableRelationsService, + private readonly tsVectorColumnActionFactory: TsVectorColumnActionFactory, + private readonly dataSourceService: DataSourceService, private readonly typeORMService: TypeORMService, private readonly workspaceMigrationService: WorkspaceMigrationService, + + private readonly indexMetadataService: IndexMetadataService, + private readonly featureFlagService: FeatureFlagService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, ) { super(objectMetadataRepository); } @@ -350,6 +371,18 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt objectMetadataInput, createdObjectMetadata, ); + + const isSearchEnabled = await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsSearchEnabled, + objectMetadataInput.workspaceId, + ); + + if (isSearchEnabled) { + await this.createSearchVectorField( + objectMetadataInput, + createdObjectMetadata, + ); + } } else { await this.remoteTableRelationsService.createForeignKeysMetadataAndMigrations( objectMetadataInput.workspaceId, @@ -533,9 +566,39 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt objectMetadataInput.primaryKeyFieldMetadataSettings, ); - return this.workspaceMigrationService.createCustomMigration( + await this.workspaceMigrationService.createCustomMigration( generateMigrationName(`create-${createdObjectMetadata.nameSingular}`), createdObjectMetadata.workspaceId, + [ + { + name: computeObjectTargetTable(createdObjectMetadata), + action: WorkspaceMigrationTableActionType.CREATE, + } satisfies WorkspaceMigrationTableAction, + ], + ); + + for (const fieldMetadata of createdObjectMetadata.fields) { + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName(`create-${fieldMetadata.name}`), + createdObjectMetadata.workspaceId, + [ + { + name: computeObjectTargetTable(createdObjectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + fieldMetadata, + ), + }, + ] satisfies WorkspaceMigrationTableAction[], + ); + } + + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName( + `create-${createdObjectMetadata.nameSingular}-relations`, + ), + createdObjectMetadata.workspaceId, buildMigrationsForCustomObjectRelations( createdObjectMetadata, activityTargetObjectMetadata, @@ -548,6 +611,73 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt ); } + private async createSearchVectorField( + objectMetadataInput: CreateObjectInput, + createdObjectMetadata: ObjectMetadataEntity, + ) { + const searchVectorFieldMetadata = await this.fieldMetadataRepository.save({ + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector, + objectMetadataId: createdObjectMetadata.id, + workspaceId: objectMetadataInput.workspaceId, + isCustom: false, + isActive: false, + isSystem: true, + type: FieldMetadataType.TS_VECTOR, + name: SEARCH_VECTOR_FIELD.name, + label: SEARCH_VECTOR_FIELD.label, + description: SEARCH_VECTOR_FIELD.description, + isNullable: true, + }); + + const searchableFieldForCustomObject = + createdObjectMetadata.labelIdentifierFieldMetadataId + ? createdObjectMetadata.fields.find( + (field) => + field.id === createdObjectMetadata.labelIdentifierFieldMetadataId, + ) + : createdObjectMetadata.fields.find( + (field) => field.name === DEFAULT_LABEL_IDENTIFIER_FIELD_NAME, + ); + + if (!isDefined(searchableFieldForCustomObject)) { + throw new Error('No searchable field found for custom object'); + } + + this.workspaceMigrationService.createCustomMigration( + generateMigrationName( + `update-${createdObjectMetadata.nameSingular}-add-searchVector`, + ), + createdObjectMetadata.workspaceId, + [ + { + name: computeTableName( + createdObjectMetadata.nameSingular, + createdObjectMetadata.isCustom, + ), + action: WorkspaceMigrationTableActionType.ALTER, + columns: this.tsVectorColumnActionFactory.handleCreateAction({ + ...searchVectorFieldMetadata, + defaultValue: undefined, + generatedType: 'STORED', + asExpression: getTsVectorColumnExpressionFromFields([ + searchableFieldForCustomObject as FieldTypeAndNameMetadata, + ]), + options: undefined, + } as FieldMetadataInterface<FieldMetadataType.TS_VECTOR>), + }, + ], + ); + + await this.indexMetadataService.createIndex( + objectMetadataInput.workspaceId, + createdObjectMetadata, + [searchVectorFieldMetadata], + false, + false, + IndexType.GIN, + ); + } + private async createActivityTargetRelation( workspaceId: string, createdObjectMetadata: ObjectMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts index 35ccf55011d2..869c8d1bea32 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts @@ -18,10 +18,6 @@ export const buildMigrationsForCustomObjectRelations = ( noteTargetObjectMetadata: ObjectMetadataEntity, taskTargetObjectMetadata: ObjectMetadataEntity, ): WorkspaceMigrationTableAction[] => [ - { - name: computeObjectTargetTable(createdObjectMetadata), - action: WorkspaceMigrationTableActionType.CREATE, - } satisfies WorkspaceMigrationTableAction, // Add activity target relation { name: computeObjectTargetTable(activityTargetObjectMetadata), diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index affe7e1b7772..90291ad6522a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -153,6 +153,8 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat relationMetadataInput.workspaceId, toObjectMetadata, [foreignKeyFieldMetadata, deletedFieldMetadata], + false, + false, ); await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input.ts deleted file mode 100644 index 0e9f2885d676..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Field, InputType } from '@nestjs/graphql'; - -import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; - -@InputType() -export class CreateServerlessFunctionFromFileInput { - @IsString() - @IsNotEmpty() - @Field() - name: string; - - @IsString() - @IsOptional() - @Field({ nullable: true }) - description?: string; -} diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input.ts index 63431ad881f7..327044b7bd2f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input.ts @@ -1,13 +1,16 @@ import { Field, InputType } from '@nestjs/graphql'; -import { IsNotEmpty, IsString } from 'class-validator'; - -import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; @InputType() -export class CreateServerlessFunctionInput extends CreateServerlessFunctionFromFileInput { +export class CreateServerlessFunctionInput { @IsString() @IsNotEmpty() @Field() - code: string; + name: string; + + @IsString() + @IsOptional() + @Field({ nullable: true }) + description?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto.ts index 092d2fce762c..e24687cec848 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto.ts @@ -50,11 +50,6 @@ export class ServerlessFunctionDTO { @Field({ nullable: true }) description: string; - @IsString() - @IsNotEmpty() - @Field() - sourceCodeHash: string; - @IsString() @IsNotEmpty() @Field() diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input.ts index 8c75bf25534c..60dec1d7d581 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input.ts @@ -1,6 +1,7 @@ import { Field, InputType } from '@nestjs/graphql'; -import { IsNotEmpty, IsString, IsUUID } from 'class-validator'; +import { IsNotEmpty, IsObject, IsString, IsUUID } from 'class-validator'; +import graphqlTypeJson from 'graphql-type-json'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; @@ -21,7 +22,7 @@ export class UpdateServerlessFunctionInput { @Field({ nullable: true }) description?: string; - @IsString() - @Field() - code: string; + @Field(() => graphqlTypeJson) + @IsObject() + code: JSON; } diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts index 782362bfba94..58406efbda10 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts @@ -29,9 +29,6 @@ export class ServerlessFunctionEntity { @Column({ nullable: true }) latestVersion: string; - @Column({ nullable: false }) - sourceCodeHash: string; - @Column({ nullable: false, default: ServerlessFunctionRuntime.NODE18 }) runtime: ServerlessFunctionRuntime; diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts index 14f4ed48478d..05440c6c7aef 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts @@ -3,7 +3,6 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; import graphqlTypeJson from 'graphql-type-json'; -import { FileUpload, GraphQLUpload } from 'graphql-upload'; import { Repository } from 'typeorm'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -11,7 +10,6 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature- import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input'; import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input'; import { DeleteServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/delete-serverless-function.input'; import { ExecuteServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/execute-serverless-function.input'; @@ -63,7 +61,7 @@ export class ServerlessFunctionResolver { } } - @Query(() => String, { nullable: true }) + @Query(() => graphqlTypeJson, { nullable: true }) async getServerlessFunctionSourceCode( @Args('input') input: GetServerlessFunctionSourceCodeInput, @AuthWorkspace() { id: workspaceId }: Workspace, @@ -130,28 +128,6 @@ export class ServerlessFunctionResolver { name: input.name, description: input.description, }, - input.code, - workspaceId, - ); - } catch (error) { - serverlessFunctionGraphQLApiExceptionHandler(error); - } - } - - @Mutation(() => ServerlessFunctionDTO) - async createOneServerlessFunctionFromFile( - @Args({ name: 'file', type: () => GraphQLUpload }) - file: FileUpload, - @Args('input') - input: CreateServerlessFunctionFromFileInput, - @AuthWorkspace() { id: workspaceId }: Workspace, - ) { - try { - await this.checkFeatureFlag(workspaceId); - - return await this.serverlessFunctionService.createOneServerlessFunction( - input, - file, workspaceId, ); } catch (error) { diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts index d7c591eb0c8c..191dc9edf414 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts @@ -1,9 +1,11 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { basename, dirname, join } from 'path'; + import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; -import { FileUpload } from 'graphql-upload'; import { Repository } from 'typeorm'; +import deepEqual from 'deep-equal'; import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface'; @@ -12,10 +14,9 @@ import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.se import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content'; -import { SOURCE_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/source-file-name'; +import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name'; import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service'; import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils'; -import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input'; import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input'; import { ServerlessFunctionEntity, @@ -25,10 +26,12 @@ import { ServerlessFunctionException, ServerlessFunctionExceptionCode, } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception'; -import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils'; import { isDefined } from 'src/utils/is-defined'; import { getLastLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies'; import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version'; +import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input'; +import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files'; +import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name'; @Injectable() export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> { @@ -47,7 +50,7 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun workspaceId: string, id: string, version: string, - ) { + ): Promise<{ [filePath: string]: string } | undefined> { const serverlessFunction = await this.serverlessFunctionRepository.findOne({ where: { id, @@ -68,12 +71,20 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun version, }); - const fileStream = await this.fileStorageService.read({ - folderPath, - filename: SOURCE_FILE_NAME, + const indexFileStream = await this.fileStorageService.read({ + folderPath: join(folderPath, 'src'), + filename: INDEX_FILE_NAME, + }); + + const envFileStream = await this.fileStorageService.read({ + folderPath: folderPath, + filename: ENV_FILE_NAME, }); - return await readFileContent(fileStream); + return { + '.env': await readFileContent(envFileStream), + 'src/index.ts': await readFileContent(indexFileStream), + }; } catch (error) { if (error.code === FileStorageExceptionCode.FILE_NOT_FOUND) { return; @@ -132,10 +143,7 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun 'draft', ); - if ( - serverlessFunctionCreateHash(latestCode || '') === - serverlessFunctionCreateHash(draftCode || '') - ) { + if (deepEqual(latestCode, draftCode)) { throw new Error( 'Cannot publish a new version when code has not changed', ); @@ -146,20 +154,6 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun existingServerlessFunction, ); - const draftFolderPath = getServerlessFolder({ - serverlessFunction: existingServerlessFunction, - version: 'draft', - }); - const newFolderPath = getServerlessFolder({ - serverlessFunction: existingServerlessFunction, - version: newVersion, - }); - - await this.fileStorageService.copy({ - from: { folderPath: draftFolderPath }, - to: { folderPath: newFolderPath }, - }); - await super.updateOne(existingServerlessFunction.id, { latestVersion: newVersion, }); @@ -213,9 +207,6 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun name: serverlessFunctionInput.name, description: serverlessFunctionInput.description, syncStatus: ServerlessFunctionSyncStatus.NOT_READY, - sourceCodeHash: serverlessFunctionCreateHash( - serverlessFunctionInput.code, - ), }); const fileFolder = getServerlessFolder({ @@ -223,12 +214,14 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun version: 'draft', }); - await this.fileStorageService.write({ - file: serverlessFunctionInput.code, - name: SOURCE_FILE_NAME, - mimeType: undefined, - folder: fileFolder, - }); + for (const key of Object.keys(serverlessFunctionInput.code)) { + await this.fileStorageService.write({ + file: serverlessFunctionInput.code[key], + name: basename(key), + mimeType: undefined, + folder: join(fileFolder, dirname(key)), + }); + } await this.serverlessService.build(existingServerlessFunction, 'draft'); await super.updateOne(existingServerlessFunction.id, { @@ -259,22 +252,12 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun } async createOneServerlessFunction( - serverlessFunctionInput: CreateServerlessFunctionFromFileInput, - code: FileUpload | string, + serverlessFunctionInput: CreateServerlessFunctionInput, workspaceId: string, ) { - let typescriptCode: string; - - if (typeof code === 'string') { - typescriptCode = code; - } else { - typescriptCode = await readFileContent(code.createReadStream()); - } - const createdServerlessFunction = await super.createOne({ ...serverlessFunctionInput, workspaceId, - sourceCodeHash: serverlessFunctionCreateHash(typescriptCode), layerVersion: LAST_LAYER_VERSION, }); @@ -283,12 +266,14 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun version: 'draft', }); - await this.fileStorageService.write({ - file: typescriptCode, - name: SOURCE_FILE_NAME, - mimeType: undefined, - folder: draftFileFolder, - }); + for (const file of await getBaseTypescriptProjectFiles) { + await this.fileStorageService.write({ + file: file.content, + name: file.name, + mimeType: undefined, + folder: join(draftFileFolder, file.path), + }); + } await this.serverlessService.build(createdServerlessFunction, 'draft'); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/metadata.constants.ts b/packages/twenty-server/src/engine/metadata-modules/utils/constants/identifier-max-char-length.constants.ts similarity index 100% rename from packages/twenty-server/src/engine/metadata-modules/utils/metadata.constants.ts rename to packages/twenty-server/src/engine/metadata-modules/utils/constants/identifier-max-char-length.constants.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-database-identifier-length.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-database-identifier-length.utils.ts index dff01b4f33b3..b798049e5117 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-database-identifier-length.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-database-identifier-length.utils.ts @@ -1,4 +1,4 @@ -import { IDENTIFIER_MAX_CHAR_LENGTH } from 'src/engine/metadata-modules/utils/metadata.constants'; +import { IDENTIFIER_MAX_CHAR_LENGTH } from 'src/engine/metadata-modules/utils/constants/identifier-max-char-length.constants'; export const exceedsDatabaseIdentifierMaximumLength = (string: string) => { return string.length > IDENTIFIER_MAX_CHAR_LENGTH; diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts index e1d5a0ae9667..c3d454c9450b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts @@ -77,6 +77,8 @@ export class WorkspaceMetadataCacheService { 'fields', 'fields.fromRelationMetadata', 'fields.toRelationMetadata', + 'indexMetadatas', + 'indexMetadatas.indexFieldMetadatas', ], }); diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts index 643819ddcb7d..26925227d2b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts @@ -21,8 +21,6 @@ import { export type BasicFieldMetadataType = | FieldMetadataType.UUID | FieldMetadataType.TEXT - | FieldMetadataType.PHONE - | FieldMetadataType.EMAIL | FieldMetadataType.NUMERIC | FieldMetadataType.NUMBER | FieldMetadataType.BOOLEAN @@ -51,6 +49,7 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF columnType: fieldMetadataTypeToColumnType(fieldMetadata.type), isArray: fieldMetadata.type === FieldMetadataType.ARRAY, isNullable: fieldMetadata.isNullable ?? true, + isUnique: fieldMetadata.isUnique ?? false, defaultValue: serializedDefaultValue, }, ]; @@ -85,6 +84,7 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF columnType: fieldMetadataTypeToColumnType(currentFieldMetadata.type), isArray: currentFieldMetadata.type === FieldMetadataType.ARRAY, isNullable: currentFieldMetadata.isNullable ?? true, + isUnique: currentFieldMetadata.isUnique ?? false, defaultValue: serializeDefaultValue( currentFieldMetadata.defaultValue, ), @@ -94,6 +94,7 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF columnType: fieldMetadataTypeToColumnType(alteredFieldMetadata.type), isArray: alteredFieldMetadata.type === FieldMetadataType.ARRAY, isNullable: alteredFieldMetadata.isNullable ?? true, + isUnique: alteredFieldMetadata.isUnique ?? false, defaultValue: serializedDefaultValue, }, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts index 9af3e89d4b04..7438e7a8559b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts @@ -18,11 +18,11 @@ import { WorkspaceMigrationExceptionCode, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; +// TODO: could we export this to GraphQL ? export type CompositeFieldMetadataType = | FieldMetadataType.ADDRESS | FieldMetadataType.CURRENCY | FieldMetadataType.FULL_NAME - | FieldMetadataType.LINK | FieldMetadataType.LINKS | FieldMetadataType.EMAILS | FieldMetadataType.PHONES; @@ -69,6 +69,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co columnType: fieldMetadataTypeToColumnType(property.type), enum: enumOptions, isNullable: fieldMetadata.isNullable || !property.isRequired, + isUnique: fieldMetadata.isUnique, defaultValue: serializedDefaultValue, isArray: property.type === FieldMetadataType.MULTI_SELECT || property.isArray, @@ -168,6 +169,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co : undefined, isNullable: currentFieldMetadata.isNullable || !currentProperty.isRequired, + isUnique: currentFieldMetadata.isUnique ?? false, defaultValue: serializeDefaultValue( currentFieldMetadata.defaultValue?.[currentProperty.name], ), @@ -181,6 +183,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co enum: enumOptions, isNullable: alteredFieldMetadata.isNullable || !alteredProperty.isRequired, + isUnique: alteredFieldMetadata.isUnique ?? false, defaultValue: serializedDefaultValue, isArray: alteredProperty.type === FieldMetadataType.MULTI_SELECT || diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory.ts index cfae64f8a86e..182bc0a7c9f4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory.ts @@ -46,6 +46,7 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie enum: enumOptions, isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT, isNullable: fieldMetadata.isNullable ?? true, + isUnique: fieldMetadata.isUnique ?? false, defaultValue: serializedDefaultValue, }, ]; @@ -103,6 +104,7 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie : undefined, isArray: currentFieldMetadata.type === FieldMetadataType.MULTI_SELECT, isNullable: currentFieldMetadata.isNullable ?? true, + isUnique: currentFieldMetadata.isUnique ?? false, defaultValue: serializeDefaultValue( currentFieldMetadata.defaultValue, ), @@ -113,6 +115,7 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie enum: enumOptions, isArray: alteredFieldMetadata.type === FieldMetadataType.MULTI_SELECT, isNullable: alteredFieldMetadata.isNullable ?? true, + isUnique: alteredFieldMetadata.isUnique ?? false, defaultValue: serializedDefaultValue, }, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/factories.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/factories.ts index 5bda4bac116a..3dcbe1fac424 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/factories.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/factories.ts @@ -1,8 +1,10 @@ import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory'; import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory'; +import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory'; export const workspaceColumnActionFactories = [ + TsVectorColumnActionFactory, BasicColumnActionFactory, EnumColumnActionFactory, CompositeColumnActionFactory, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory.ts new file mode 100644 index 000000000000..fa6aea20100d --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory.ts @@ -0,0 +1,53 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; +import { WorkspaceColumnActionOptions } from 'src/engine/metadata-modules/workspace-migration/interfaces/workspace-column-action-options.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory'; +import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationColumnAlter, + WorkspaceMigrationColumnCreate, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { + WorkspaceMigrationException, + WorkspaceMigrationExceptionCode, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; + +export type TsVectorFieldMetadataType = FieldMetadataType.TS_VECTOR; + +@Injectable() +export class TsVectorColumnActionFactory extends ColumnActionAbstractFactory<TsVectorFieldMetadataType> { + protected readonly logger = new Logger(TsVectorColumnActionFactory.name); + + handleCreateAction( + fieldMetadata: FieldMetadataInterface<TsVectorFieldMetadataType>, + ): WorkspaceMigrationColumnCreate[] { + return [ + { + action: WorkspaceMigrationColumnActionType.CREATE, + columnName: computeColumnName(fieldMetadata), + columnType: fieldMetadataTypeToColumnType(fieldMetadata.type), + isNullable: fieldMetadata.isNullable ?? true, + isUnique: fieldMetadata.isUnique ?? false, + defaultValue: undefined, + generatedType: fieldMetadata.generatedType, + asExpression: fieldMetadata.asExpression, + }, + ]; + } + + protected handleAlterAction( + _currentFieldMetadata: FieldMetadataInterface<TsVectorFieldMetadataType>, + _alteredFieldMetadata: FieldMetadataInterface<TsVectorFieldMetadataType>, + _options?: WorkspaceColumnActionOptions, + ): WorkspaceMigrationColumnAlter[] { + throw new WorkspaceMigrationException( + `TsVectorColumnActionFactory.handleAlterAction has not been implemented yet.`, + WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA, + ); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts index 9d3ff6276cf9..46ec14ad3fc1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util.ts @@ -18,9 +18,6 @@ export const fieldMetadataTypeToColumnType = <Type extends FieldMetadataType>( case FieldMetadataType.RICH_TEXT: case FieldMetadataType.ARRAY: return 'text'; - case FieldMetadataType.PHONE: - case FieldMetadataType.EMAIL: - return 'varchar'; case FieldMetadataType.NUMERIC: return 'numeric'; case FieldMetadataType.NUMBER: @@ -38,6 +35,8 @@ export const fieldMetadataTypeToColumnType = <Type extends FieldMetadataType>( return 'enum'; case FieldMetadataType.RAW_JSON: return 'jsonb'; + case FieldMetadataType.TS_VECTOR: + return 'tsvector'; default: throw new WorkspaceMigrationException( `Cannot convert ${fieldMetadataType} to column type.`, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts index 0c1177fb7921..ed3a1e5cfcc7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts @@ -5,6 +5,7 @@ import { PrimaryGeneratedColumn, } from 'typeorm'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; export enum WorkspaceMigrationColumnActionType { @@ -29,13 +30,19 @@ export interface WorkspaceMigrationColumnDefinition { enum?: WorkspaceMigrationEnum[]; isArray?: boolean; isNullable: boolean; + isUnique?: boolean; defaultValue: any; + generatedType?: 'STORED' | 'VIRTUAL'; + asExpression?: string; } export interface WorkspaceMigrationIndexAction { action: WorkspaceMigrationIndexActionType; name: string; columns: string[]; + isUnique: boolean; + where?: string | null; + type?: IndexType; } export interface WorkspaceMigrationColumnCreate diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts index 6f33cf0137e3..5df5da052f2b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts @@ -8,6 +8,7 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory'; import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory'; +import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory'; import { WorkspaceMigrationColumnAction, WorkspaceMigrationColumnActionType, @@ -30,6 +31,7 @@ export class WorkspaceMigrationFactory { constructor( private readonly basicColumnActionFactory: BasicColumnActionFactory, + private readonly tsVectorColumnActionFactory: TsVectorColumnActionFactory, private readonly enumColumnActionFactory: EnumColumnActionFactory, private readonly compositeColumnActionFactory: CompositeColumnActionFactory, ) { @@ -50,24 +52,6 @@ export class WorkspaceMigrationFactory { }, }, ], - [ - FieldMetadataType.PHONE, - { - factory: this.basicColumnActionFactory, - options: { - defaultValue: '', - }, - }, - ], - [ - FieldMetadataType.EMAIL, - { - factory: this.basicColumnActionFactory, - options: { - defaultValue: '', - }, - }, - ], [FieldMetadataType.NUMERIC, { factory: this.basicColumnActionFactory }], [FieldMetadataType.NUMBER, { factory: this.basicColumnActionFactory }], [FieldMetadataType.POSITION, { factory: this.basicColumnActionFactory }], @@ -82,7 +66,6 @@ export class WorkspaceMigrationFactory { FieldMetadataType.MULTI_SELECT, { factory: this.enumColumnActionFactory }, ], - [FieldMetadataType.LINK, { factory: this.compositeColumnActionFactory }], [ FieldMetadataType.CURRENCY, { factory: this.compositeColumnActionFactory }, @@ -106,6 +89,10 @@ export class WorkspaceMigrationFactory { FieldMetadataType.PHONES, { factory: this.compositeColumnActionFactory }, ], + [ + FieldMetadataType.TS_VECTOR, + { factory: this.tsVectorColumnActionFactory }, + ], ]); } diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.module.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.module.ts index 9e0d4072c476..e7ecd97b0601 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.module.ts @@ -4,8 +4,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { workspaceColumnActionFactories } from 'src/engine/metadata-modules/workspace-migration/factories/factories'; import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; -import { WorkspaceMigrationService } from './workspace-migration.service'; import { WorkspaceMigrationEntity } from './workspace-migration.entity'; +import { WorkspaceMigrationService } from './workspace-migration.service'; @Module({ imports: [TypeOrmModule.forFeature([WorkspaceMigrationEntity], 'metadata')], @@ -14,6 +14,10 @@ import { WorkspaceMigrationEntity } from './workspace-migration.entity'; WorkspaceMigrationFactory, WorkspaceMigrationService, ], - exports: [WorkspaceMigrationFactory, WorkspaceMigrationService], + exports: [ + ...workspaceColumnActionFactories, + WorkspaceMigrationFactory, + WorkspaceMigrationService, + ], }) export class WorkspaceMigrationModule {} diff --git a/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts b/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts index b2053e44c814..6f0bc8dffe5e 100644 --- a/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts +++ b/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts @@ -1,5 +1,4 @@ import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository'; import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository'; import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; @@ -7,7 +6,6 @@ import { WorkspaceMemberRepository } from 'src/modules/workspace-member/reposito export const metadataToRepositoryMapping = { AuditLogWorkspaceEntity: AuditLogRepository, BlocklistWorkspaceEntity: BlocklistRepository, - ConnectedAccountWorkspaceEntity: ConnectedAccountRepository, TimelineActivityWorkspaceEntity: TimelineActivityRepository, WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository, }; 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 f122f29c47a5..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 @@ -25,6 +25,9 @@ export abstract class BaseWorkspaceEntity { description: 'Creation date', icon: 'IconCalendar', defaultValue: 'now', + settings: { + displayAsRelativeDate: true, + }, }) createdAt: string; @@ -35,6 +38,9 @@ export abstract class BaseWorkspaceEntity { description: 'Last time the record was changed', icon: 'IconCalendarClock', defaultValue: 'now', + settings: { + displayAsRelativeDate: true, + }, }) updatedAt: string; @@ -44,7 +50,10 @@ export abstract class BaseWorkspaceEntity { label: 'Deleted at', description: 'Date when the record was deleted', icon: 'IconCalendarMinus', + settings: { + displayAsRelativeDate: true, + }, }) @WorkspaceIsNullable() - deletedAt?: string | null; + deletedAt: string | null; } diff --git a/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts b/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts index 0b9b5201d2b7..c9dc9264274d 100644 --- a/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts +++ b/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts @@ -1,19 +1,24 @@ +import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { ActorMetadata, FieldActorSource, } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants'; import { RelationMetadataType, RelationOnDeleteAction, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator'; +import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; @@ -136,4 +141,22 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsNullable() @WorkspaceIsSystem() timelineActivities: TimelineActivityWorkspaceEntity[]; + + @WorkspaceField({ + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector, + type: FieldMetadataType.TS_VECTOR, + label: SEARCH_VECTOR_FIELD.label, + description: SEARCH_VECTOR_FIELD.description, + generatedType: 'STORED', + asExpression: getTsVectorColumnExpressionFromFields([ + { + name: DEFAULT_LABEL_IDENTIFIER_FIELD_NAME, + type: FieldMetadataType.TEXT, + }, + ]), + }) + @WorkspaceIsNullable() + @WorkspaceIsSystem() + @WorkspaceFieldIndex({ indexType: IndexType.GIN }) + [SEARCH_VECTOR_FIELD.name]: any; } diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field-index.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field-index.decorator.ts new file mode 100644 index 000000000000..d4a748f91dbc --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field-index.decorator.ts @@ -0,0 +1,44 @@ +import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name'; +import { WorkspaceIndexOptions } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; +import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage'; +import { getColumnsForIndex } from 'src/engine/twenty-orm/utils/get-default-columns-for-index.util'; +import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; +import { TypedReflect } from 'src/utils/typed-reflect'; + +export function WorkspaceFieldIndex( + options?: WorkspaceIndexOptions, +): PropertyDecorator { + return (target: any, propertyKey: string | symbol) => { + if (propertyKey === undefined) { + throw new Error('This decorator should be used with a field not a class'); + } + + const gate = TypedReflect.getMetadata( + 'workspace:gate-metadata-args', + target, + propertyKey.toString(), + ); + + const additionalDefaultColumnsForIndex = getColumnsForIndex( + options?.indexType, + ); + + const columns = [ + propertyKey.toString(), + ...additionalDefaultColumnsForIndex, + ]; + + metadataArgsStorage.addIndexes({ + name: `IDX_${generateDeterministicIndexName([ + convertClassNameToObjectMetadataName(target.constructor.name), + ...columns, + ])}`, + columns, + target: target.constructor, + gate, + isUnique: options?.isUnique ?? false, + whereClause: options?.indexWhereClause ?? null, + type: options?.indexType, + }); + }; +} diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts index fd2b280fde60..35068e823005 100644 --- a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts +++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-field.decorator.ts @@ -20,6 +20,8 @@ export interface WorkspaceFieldOptions< options?: FieldMetadataOptions<T>; settings?: FieldMetadataSettings<T>; isActive?: boolean; + generatedType?: 'STORED' | 'VIRTUAL'; + asExpression?: string; } export function WorkspaceField<T extends FieldMetadataType>( @@ -55,6 +57,12 @@ export function WorkspaceField<T extends FieldMetadataType>( object, propertyKey.toString(), ) ?? false; + const isUnique = + TypedReflect.getMetadata( + 'workspace:is-unique-metadata-args', + object, + propertyKey.toString(), + ) ?? false; const defaultValue = (options.defaultValue ?? generateDefaultValue(options.type)) as FieldMetadataDefaultValue | null; @@ -69,12 +77,16 @@ export function WorkspaceField<T extends FieldMetadataType>( icon: options.icon, defaultValue, options: options.options, + settings: options.settings, isPrimary, isNullable, isSystem, gate, isDeprecated, + isUnique, isActive: options.isActive, + asExpression: options.asExpression, + generatedType: options.generatedType, }); }; } diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-index.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-index.decorator.ts index bf2e43201c30..f35dcca18f01 100644 --- a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-index.decorator.ts +++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-index.decorator.ts @@ -1,48 +1,40 @@ +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name'; import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage'; import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; import { TypedReflect } from 'src/utils/typed-reflect'; -export function WorkspaceIndex(): PropertyDecorator; -export function WorkspaceIndex(columns: string[]): ClassDecorator; +export type WorkspaceIndexOptions = { + isUnique?: boolean; + indexWhereClause?: string; + indexType?: IndexType; +}; + export function WorkspaceIndex( - columns?: string[], -): PropertyDecorator | ClassDecorator { - return (target: any, propertyKey: string | symbol) => { - if (propertyKey === undefined && columns === undefined) { - throw new Error('Class level WorkspaceIndex should be used with columns'); - } + columns: string[], + options: WorkspaceIndexOptions, +): ClassDecorator { + if (!Array.isArray(columns) || columns.length === 0) { + throw new Error('Class level WorkspaceIndex should be used with columns'); + } + return (target: any) => { const gate = TypedReflect.getMetadata( 'workspace:gate-metadata-args', target, - propertyKey.toString(), ); - // TODO: handle composite field metadata types - - if (Array.isArray(columns) && columns.length > 0) { - metadataArgsStorage.addIndexes({ - name: `IDX_${generateDeterministicIndexName([ - convertClassNameToObjectMetadataName(target.name), - ...columns, - ])}`, - columns, - target: target, - gate, - }); - - return; - } - metadataArgsStorage.addIndexes({ name: `IDX_${generateDeterministicIndexName([ - convertClassNameToObjectMetadataName(target.constructor.name), - ...[propertyKey.toString(), 'deletedAt'], + convertClassNameToObjectMetadataName(target.name), + ...columns, ])}`, - columns: [propertyKey.toString(), 'deletedAt'], - target: target.constructor, + columns, + target: target, gate, + isUnique: options?.isUnique ?? false, + whereClause: options?.indexWhereClause ?? null, + type: options?.indexType, }); }; } diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-is-unique.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-is-unique.decorator.ts new file mode 100644 index 000000000000..179aa1390249 --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-is-unique.decorator.ts @@ -0,0 +1,39 @@ +import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name'; +import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage'; +import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; +import { TypedReflect } from 'src/utils/typed-reflect'; + +export function WorkspaceIsUnique(): PropertyDecorator { + return (target: any, propertyKey: string | symbol) => { + if (propertyKey === undefined) { + throw new Error('This decorator should be used with a field not a class'); + } + + const gate = TypedReflect.getMetadata( + 'workspace:gate-metadata-args', + target, + propertyKey.toString(), + ); + + const columns = [propertyKey.toString()]; + + metadataArgsStorage.addIndexes({ + name: `IDX_UNIQUE_${generateDeterministicIndexName([ + convertClassNameToObjectMetadataName(target.constructor.name), + ...columns, + ])}`, + columns, + target: target.constructor, + gate, + isUnique: true, + whereClause: null, + }); + + return TypedReflect.defineMetadata( + 'workspace:is-unique-metadata-args', + true, + target, + propertyKey.toString(), + ); + }; +} diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-join-column.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-join-column.decorator.ts index 98dc6eedb10c..8b1954afb6bf 100644 --- a/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-join-column.decorator.ts +++ b/packages/twenty-server/src/engine/twenty-orm/decorators/workspace-join-column.decorator.ts @@ -1,4 +1,4 @@ -import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; +import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator'; import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage'; export function WorkspaceJoinColumn( @@ -12,6 +12,6 @@ export function WorkspaceJoinColumn( }); // Register index for join column - WorkspaceIndex()(object, propertyKey); + WorkspaceFieldIndex()(object, propertyKey); }; } diff --git a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface.ts b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface.ts index 862d72a25117..03862b3adf61 100644 --- a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface.ts +++ b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface.ts @@ -1,5 +1,6 @@ import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface'; +import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -54,6 +55,11 @@ export interface WorkspaceFieldMetadataArgs { */ readonly options?: FieldMetadataOptions; + /** + * Field settings. + */ + readonly settings?: FieldMetadataSettings; + /** * Is primary field. */ @@ -69,6 +75,11 @@ export interface WorkspaceFieldMetadataArgs { */ readonly isNullable: boolean; + /** + * Is unique field. + */ + readonly isUnique: boolean; + /** * Field gate. */ @@ -83,4 +94,14 @@ export interface WorkspaceFieldMetadataArgs { * Is active field. */ readonly isActive?: boolean; + + /** + * Is active field. + */ + readonly generatedType?: 'STORED' | 'VIRTUAL'; + + /** + * Is active field. + */ + readonly asExpression?: string; } diff --git a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-index-metadata-args.interface.ts b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-index-metadata-args.interface.ts index 9412e417c5f5..df2fc1e8366a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-index-metadata-args.interface.ts +++ b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-index-metadata-args.interface.ts @@ -1,5 +1,7 @@ import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; + export interface WorkspaceIndexMetadataArgs { /** * Class to which index is applied. @@ -17,6 +19,21 @@ export interface WorkspaceIndexMetadataArgs { */ columns: string[]; + /** + * Is index unique. + */ + isUnique: boolean; + + /* + * Index type. Defaults to Btree. + */ + type?: IndexType; + + /** + * Index where clause. + */ + whereClause: string | null; + /** * Field gate. */ diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index ea3c3f9e84bc..bb4327cc8b1b 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -664,7 +664,7 @@ export class WorkspaceRepository< return formatData(data, objectMetadata) as T; } - private async formatResult<T>( + async formatResult<T>( data: T, objectMetadata?: ObjectMetadataMapItem, ): Promise<T> { diff --git a/packages/twenty-server/src/engine/twenty-orm/storage/metadata-args.storage.ts b/packages/twenty-server/src/engine/twenty-orm/storage/metadata-args.storage.ts index 7ea7f9d76840..69807d28d2a0 100644 --- a/packages/twenty-server/src/engine/twenty-orm/storage/metadata-args.storage.ts +++ b/packages/twenty-server/src/engine/twenty-orm/storage/metadata-args.storage.ts @@ -1,12 +1,12 @@ /* eslint-disable @typescript-eslint/ban-types */ import { WorkspaceDynamicRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface'; -import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface'; import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-entity-metadata-args.interface'; -import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface'; import { WorkspaceExtendedEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-extended-entity-metadata-args.interface'; +import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface'; import { WorkspaceIndexMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-index-metadata-args.interface'; import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface'; +import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface'; export class MetadataArgsStorage { private readonly entities: WorkspaceEntityMetadataArgs[] = []; diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-default-columns-for-index.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-default-columns-for-index.util.spec.ts new file mode 100644 index 000000000000..fad8a9cd18be --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/get-default-columns-for-index.util.spec.ts @@ -0,0 +1,22 @@ +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { getColumnsForIndex } from 'src/engine/twenty-orm/utils/get-default-columns-for-index.util'; + +describe('getColumnsForIndex', () => { + it('should return ["deletedAt"] when indexType is undefined', () => { + const result = getColumnsForIndex(); + + expect(result).toEqual(['deletedAt']); + }); + + it('should return an empty array when indexType is IndexType.GIN', () => { + const result = getColumnsForIndex(IndexType.GIN); + + expect(result).toEqual([]); + }); + + it('should return ["deletedAt"] when indexType is IndexType.BTREE', () => { + const result = getColumnsForIndex(IndexType.BTREE); + + expect(result).toEqual(['deletedAt']); + }); +}); 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<T>( data: T, @@ -17,49 +19,70 @@ export function formatData<T>( 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<string, any> = {}; 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<string, any> { + 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<string, any> = {}; + + 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<T>( 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<T>( 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/twenty-orm/utils/get-default-columns-for-index.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/get-default-columns-for-index.util.ts new file mode 100644 index 000000000000..b97d086a6299 --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/utils/get-default-columns-for-index.util.ts @@ -0,0 +1,10 @@ +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; + +export const getColumnsForIndex = (indexType?: IndexType) => { + switch (indexType) { + case IndexType.GIN: + return []; + default: + return ['deletedAt']; + } +}; diff --git a/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts b/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts index 77a30372ae2f..f274475e7008 100644 --- a/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts +++ b/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts @@ -33,6 +33,8 @@ export const getResolverName = ( return `delete${pascalCase(objectMetadata.namePlural)}`; case 'destroyMany': return `destroy${pascalCase(objectMetadata.namePlural)}`; + case 'search': + return `search${pascalCase(objectMetadata.namePlural)}`; default: throw new Error(`Unknown resolver type: ${type}`); } diff --git a/packages/twenty-server/src/engine/utils/should-seed-workspace-favorite.ts b/packages/twenty-server/src/engine/utils/should-seed-workspace-favorite.ts new file mode 100644 index 000000000000..9668a7a4eda4 --- /dev/null +++ b/packages/twenty-server/src/engine/utils/should-seed-workspace-favorite.ts @@ -0,0 +1,9 @@ +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const shouldSeedWorkspaceFavorite = ( + objectMetadataId, + objectMetadataMap, +): boolean => + objectMetadataId !== + objectMetadataMap[STANDARD_OBJECT_IDS.workflowVersion]?.id && + objectMetadataId !== objectMetadataMap[STANDARD_OBJECT_IDS.workflowRun]?.id; diff --git a/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts b/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts index 226e5557c82b..73b36137db03 100644 --- a/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts +++ b/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data.ts @@ -2,6 +2,7 @@ import { DataSource, EntityManager } from 'typeorm'; import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { companyPrefillDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/company'; import { opportunityPrefillDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/opportunity'; import { personPrefillDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/person'; @@ -42,7 +43,7 @@ export const demoObjectsPrefillData = async ( await seedWorkspaceFavorites( viewDefinitionsWithId - .filter((view) => view.key === 'INDEX') + .filter((view) => view.key === 'INDEX' && shouldSeedWorkspaceFavorite(view.objectMetadataId, objectMetadataMap)) .map((view) => view.id), entityManager, schemaName, 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 8d5a24a1eef8..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,13 @@ 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'; +export const FIGMA_ID = '9d5bcf43-7d38-4e88-82cb-d6d4ce638bf0'; +export const NOTION_ID = '06290608-8bf0-4806-99ae-a715a6a93fad'; + export const companyPrefillData = async ( entityManager: EntityManager, schemaName: string, @@ -8,6 +16,7 @@ export const companyPrefillData = async ( .createQueryBuilder() .insert() .into(`${schemaName}.company`, [ + 'id', 'name', 'domainNamePrimaryLinkUrl', 'addressAddressStreet1', @@ -25,6 +34,7 @@ export const companyPrefillData = async ( .orIgnore() .values([ { + id: AIRBNB_ID, name: 'Airbnb', domainNamePrimaryLinkUrl: 'https://airbnb.com', addressAddressStreet1: '888 Brannan St', @@ -35,11 +45,12 @@ export const companyPrefillData = async ( addressAddressCountry: 'United States', employees: 5000, position: 1, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, { + id: QONTO_ID, name: 'Qonto', domainNamePrimaryLinkUrl: 'https://qonto.com', addressAddressStreet1: '18 rue de navarrin', @@ -50,11 +61,12 @@ export const companyPrefillData = async ( addressAddressCountry: 'France', employees: 800, position: 2, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, { + id: STRIPE_ID, name: 'Stripe', domainNamePrimaryLinkUrl: 'https://stripe.com', addressAddressStreet1: 'Eutaw Street', @@ -65,11 +77,12 @@ export const companyPrefillData = async ( addressAddressCountry: 'Ireland', employees: 8000, position: 3, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, { + id: FIGMA_ID, name: 'Figma', domainNamePrimaryLinkUrl: 'https://figma.com', addressAddressStreet1: '760 Market St', @@ -80,11 +93,12 @@ export const companyPrefillData = async ( addressAddressCountry: 'United States', employees: 800, position: 4, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, { + id: NOTION_ID, name: 'Notion', domainNamePrimaryLinkUrl: 'https://notion.com', addressAddressStreet1: '2300 Harrison St', @@ -95,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 fb227b15ca56..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,14 @@ import { EntityManager } from 'typeorm'; +import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; +import { + AIRBNB_ID, + FIGMA_ID, + NOTION_ID, + QONTO_ID, + STRIPE_ID, +} from 'src/engine/workspace-manager/standard-objects-prefill-data/company'; + // FixMe: Is this file a duplicate of src/database/typeorm-seeds/workspace/people.ts export const personPrefillData = async ( entityManager: EntityManager, @@ -18,6 +27,9 @@ export const personPrefillData = async ( 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'phonesPrimaryPhoneNumber', + 'phonesPrimaryPhoneCountryCode', + 'companyId', ]) .orIgnore() .values([ @@ -29,9 +41,12 @@ 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', + phonesPrimaryPhoneCountryCode: '+1', + companyId: AIRBNB_ID, }, { nameFirstName: 'Alexandre', @@ -41,9 +56,12 @@ 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', + phonesPrimaryPhoneCountryCode: '+33', + companyId: QONTO_ID, }, { nameFirstName: 'Patrick', @@ -53,9 +71,12 @@ 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', + phonesPrimaryPhoneCountryCode: '+1', + companyId: STRIPE_ID, }, { nameFirstName: 'Dylan', @@ -65,9 +86,12 @@ 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', + phonesPrimaryPhoneCountryCode: '+1', + companyId: FIGMA_ID, }, { nameFirstName: 'Ivan', @@ -77,9 +101,12 @@ 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', + phonesPrimaryPhoneCountryCode: '+1', + companyId: NOTION_ID, }, ]) .returning('*') diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts index 7198fe4ef1e6..9584474f4377 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts @@ -2,6 +2,7 @@ import { DataSource, EntityManager } from 'typeorm'; import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company'; import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person'; import { viewPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/view'; @@ -45,7 +46,14 @@ export const standardObjectsPrefillData = async ( await seedWorkspaceFavorites( viewDefinitionsWithId - .filter((view) => view.key === 'INDEX') + .filter( + (view) => + view.key === 'INDEX' && + shouldSeedWorkspaceFavorite( + view.objectMetadataId, + objectMetadataMap, + ), + ) .map((view) => view.id), entityManager, schemaName, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts index c781e9e4f1e8..1d059f886061 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts @@ -9,6 +9,8 @@ import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard- import { peopleAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view'; import { tasksAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view'; import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view'; +import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view'; +import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view'; import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view'; export const viewPrefillData = async ( @@ -18,14 +20,20 @@ export const viewPrefillData = async ( isWorkflowEnabled: boolean, ) => { const viewDefinitions = [ - await companiesAllView(objectMetadataMap), - await peopleAllView(objectMetadataMap), - await opportunitiesAllView(objectMetadataMap), - await opportunitiesByStageView(objectMetadataMap), - await notesAllView(objectMetadataMap), - await tasksAllView(objectMetadataMap), - await tasksByStatusView(objectMetadataMap), - ...(isWorkflowEnabled ? [await workflowsAllView(objectMetadataMap)] : []), + companiesAllView(objectMetadataMap), + peopleAllView(objectMetadataMap), + opportunitiesAllView(objectMetadataMap), + opportunitiesByStageView(objectMetadataMap), + notesAllView(objectMetadataMap), + tasksAllView(objectMetadataMap), + tasksByStatusView(objectMetadataMap), + ...(isWorkflowEnabled + ? [ + workflowsAllView(objectMetadataMap), + workflowVersionsAllView(objectMetadataMap), + workflowRunsAllView(objectMetadataMap), + ] + : []), ]; const viewDefinitionsWithId = viewDefinitions.map((viewDefinition) => ({ diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view.ts index ff26eab35631..73aad80dbba6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view.ts @@ -5,7 +5,7 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const companiesAllView = async ( +export const companiesAllView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts index 97354aa1f2ff..fec9f63b0ed0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts @@ -5,7 +5,7 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const notesAllView = async ( +export const notesAllView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view.ts index 4dfad89ad4a7..b3a57bf4de61 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view.ts @@ -2,7 +2,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const opportunitiesAllView = async ( +export const opportunitiesAllView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view.ts index e0abae54d139..2ac1b1279d05 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view.ts @@ -2,7 +2,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const opportunitiesByStageView = async ( +export const opportunitiesByStageView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view.ts index 6beb5cda8c4d..53492e7b9dda 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view.ts @@ -5,7 +5,7 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const peopleAllView = async ( +export const peopleAllView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { @@ -57,7 +57,7 @@ export const peopleAllView = async ( { fieldMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[ - PERSON_STANDARD_FIELD_IDS.phone + PERSON_STANDARD_FIELD_IDS.phones ], position: 4, isVisible: true, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts index 3d9a0ffadf71..29ed1ce476f8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts @@ -5,7 +5,7 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const tasksAllView = async ( +export const tasksAllView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts index a2c390179d95..e5e66656550c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts @@ -5,7 +5,7 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const tasksByStatusView = async ( +export const tasksByStatusView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts new file mode 100644 index 000000000000..d7bc3775d9c3 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts @@ -0,0 +1,56 @@ +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WORKFLOW_RUN_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const workflowRunsAllView = ( + objectMetadataMap: Record<string, ObjectMetadataEntity>, +) => { + return { + name: 'All Workflow Runs', + objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.workflowRun].id, + type: 'table', + key: 'INDEX', + position: 0, + icon: 'IconHistory', + kanbanFieldMetadataId: '', + filters: [], + fields: [ + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowRun].fields[ + WORKFLOW_RUN_STANDARD_FIELD_IDS.name + ], + position: 0, + isVisible: true, + size: 210, + }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowRun].fields[ + WORKFLOW_RUN_STANDARD_FIELD_IDS.status + ], + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowRun].fields[ + WORKFLOW_RUN_STANDARD_FIELD_IDS.startedAt + ], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowRun].fields[ + WORKFLOW_RUN_STANDARD_FIELD_IDS.endedAt + ], + position: 3, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts new file mode 100644 index 000000000000..fff64745d579 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts @@ -0,0 +1,56 @@ +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WORKFLOW_VERSION_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +export const workflowVersionsAllView = ( + objectMetadataMap: Record<string, ObjectMetadataEntity>, +) => { + return { + name: 'All Workflow Versions', + objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.workflowVersion].id, + type: 'table', + key: 'INDEX', + position: 0, + icon: 'IconVersions', + kanbanFieldMetadataId: '', + filters: [], + fields: [ + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowVersion].fields[ + WORKFLOW_VERSION_STANDARD_FIELD_IDS.name + ], + position: 0, + isVisible: true, + size: 210, + }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowVersion].fields[ + WORKFLOW_VERSION_STANDARD_FIELD_IDS.status + ], + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowVersion].fields[ + WORKFLOW_VERSION_STANDARD_FIELD_IDS.trigger + ], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: + objectMetadataMap[STANDARD_OBJECT_IDS.workflowVersion].fields[ + WORKFLOW_VERSION_STANDARD_FIELD_IDS.steps + ], + position: 3, + isVisible: true, + size: 150, + }, + ], + }; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts index 8eba4bdc4442..330d68523529 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts @@ -2,7 +2,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { WORKFLOW_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -export const workflowsAllView = async ( +export const workflowsAllView = ( objectMetadataMap: Record<string, ObjectMetadataEntity>, ) => { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface.ts index 6a762c1b7269..543db747ff4c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface.ts @@ -5,9 +5,9 @@ export interface WorkspaceTableStructure { dataType: string; columnDefault: string; isNullable: boolean; + isUnique: boolean; isPrimaryKey: boolean; isForeignKey: boolean; - isUnique: boolean; isArray: boolean; onUpdateAction: string; onDeleteAction: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory.ts index 0392041edc93..480d5f87c652 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory.ts @@ -1,16 +1,20 @@ import { Injectable } from '@nestjs/common'; +import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface'; import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.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 { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; +import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { WorkspaceMigrationEntity, WorkspaceMigrationIndexActionType, WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; -import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; @Injectable() export class WorkspaceMigrationIndexFactory { @@ -77,10 +81,8 @@ export class WorkspaceMigrationIndexFactory { objectMetadata.fields.map((field) => [field.id, field]), ); - const indexes = indexMetadataCollection.map((indexMetadata) => ({ - name: indexMetadata.name, - action: WorkspaceMigrationIndexActionType.CREATE, - columns: indexMetadata.indexFieldMetadatas + const indexes = indexMetadataCollection.map((indexMetadata) => { + const columns = indexMetadata.indexFieldMetadatas .sort((a, b) => a.order - b.order) .map((indexFieldMetadata) => { const fieldMetadata = @@ -92,9 +94,35 @@ export class WorkspaceMigrationIndexFactory { ); } - return fieldMetadata.name; - }), - })); + if (!isCompositeFieldMetadataType(fieldMetadata.type)) { + return fieldMetadata.name; + } + + const compositeType = compositeTypeDefinitions.get( + fieldMetadata.type, + ) as CompositeType; + + return compositeType.properties + .filter((property) => property.isIncludedInUniqueConstraint) + .map((property) => + computeCompositeColumnName(fieldMetadata, property), + ); + }) + .flat(); + + const defaultWhereClause = indexMetadata.isUnique + ? `${columns.map((column) => `"${column}"`).join(" != '' AND ")} != '' AND "deletedAt" IS NULL` + : null; + + return { + name: indexMetadata.name, + action: WorkspaceMigrationIndexActionType.CREATE, + isUnique: indexMetadata.isUnique, + columns, + type: indexMetadata.indexType, + where: indexMetadata.indexWhereClause ?? defaultWhereClause, + }; + }); workspaceMigrations.push({ workspaceId: objectMetadata.workspaceId, @@ -133,6 +161,7 @@ export class WorkspaceMigrationIndexFactory { name: indexMetadata.name, action: WorkspaceMigrationIndexActionType.DROP, columns: [], + isUnique: indexMetadata.isUnique, })); workspaceMigrations.push({ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts index afd381cb16c4..843042832bfd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts @@ -77,6 +77,7 @@ export class WorkspaceMigrationEnumService { enumName: newEnumTypeName, isArray: columnDefinition.isArray, isNullable: columnDefinition.isNullable, + isUnique: columnDefinition.isUnique, }), ); 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 3fec17109c3f..df85336609db 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 @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { isDefined } from 'class-validator'; import { QueryRunner, Table, @@ -9,6 +10,7 @@ import { TableUnique, } from 'typeorm'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { WorkspaceMigrationColumnAction, WorkspaceMigrationColumnActionType, @@ -26,9 +28,9 @@ 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 { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service'; -import { customTableDefaultColumns } from './utils/custom-table-default-column.util'; @Injectable() export class WorkspaceMigrationRunnerService { @@ -120,7 +122,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) { @@ -194,13 +201,23 @@ export class WorkspaceMigrationRunnerService { for (const index of indexes) { switch (index.action) { case WorkspaceMigrationIndexActionType.CREATE: - await queryRunner.createIndex( - `${schemaName}.${tableName}`, - new TableIndex({ - name: index.name, - columnNames: index.columns, - }), - ); + if (isDefined(index.type) && index.type !== IndexType.BTREE) { + const quotedColumns = index.columns.map((column) => `"${column}"`); + + await queryRunner.query(` + CREATE INDEX "${index.name}" ON "${schemaName}"."${tableName}" USING ${index.type} (${quotedColumns.join(', ')}) + `); + } else { + await queryRunner.createIndex( + `${schemaName}.${tableName}`, + new TableIndex({ + name: index.name, + columnNames: index.columns, + isUnique: index.isUnique, + where: index.where ?? undefined, + }), + ); + } break; case WorkspaceMigrationIndexActionType.DROP: try { @@ -235,16 +252,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}})'; @@ -380,6 +407,9 @@ export class WorkspaceMigrationRunnerService { enumName: enumName, isArray: migrationColumn.isArray, isNullable: migrationColumn.isNullable, + isUnique: migrationColumn.isUnique, + asExpression: migrationColumn.asExpression, + generatedType: migrationColumn.generatedType, }), ); } @@ -433,6 +463,7 @@ export class WorkspaceMigrationRunnerService { ), isArray: migrationColumn.currentColumnDefinition.isArray, isNullable: migrationColumn.currentColumnDefinition.isNullable, + isUnique: migrationColumn.currentColumnDefinition.isUnique, }), new TableColumn({ name: migrationColumn.alteredColumnDefinition.columnName, @@ -443,6 +474,7 @@ export class WorkspaceMigrationRunnerService { ), isArray: migrationColumn.alteredColumnDefinition.isArray, isNullable: migrationColumn.alteredColumnDefinition.isNullable, + isUnique: migrationColumn.alteredColumnDefinition.isUnique, }), ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator.ts index 7e51a398f9be..d26c3aa8bd63 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator.ts @@ -24,6 +24,8 @@ const commonFieldPropertiesToIgnore = [ 'settings', 'joinColumn', 'gate', + 'asExpression', + 'generatedType', ]; const fieldPropertiesToStringify = ['defaultValue'] as const; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator.ts index 5fbf2ad9756d..d8693db4e768 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import diff from 'microdiff'; import { - IndexComparatorResult, ComparatorAction, + IndexComparatorResult, } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator.ts index eaa37da7cf84..350fdaf5348b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import diff from 'microdiff'; import omit from 'lodash.omit'; +import diff from 'microdiff'; import { ComparatorAction, @@ -9,8 +9,8 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface'; import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface'; -import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; const objectPropertiesToIgnore = [ 'id', @@ -28,7 +28,10 @@ export class WorkspaceObjectComparator { public compare( originalObjectMetadata: Omit<ObjectMetadataEntity, 'fields'> | undefined, - standardObjectMetadata: Omit<ComputedPartialWorkspaceEntity, 'fields'>, + standardObjectMetadata: Omit< + ComputedPartialWorkspaceEntity, + 'fields' | 'indexMetadatas' + >, ): ObjectComparatorResult { // If the object doesn't exist in the original metadata, we need to create it if (!originalObjectMetadata) { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags.ts new file mode 100644 index 000000000000..88ec505dd432 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags.ts @@ -0,0 +1,6 @@ +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; + +export const DEFAULT_FEATURE_FLAGS = [ + FeatureFlagKey.IsSearchEnabled, + FeatureFlagKey.IsWorkspaceMigratedForSearch, +]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index bf645e747fd6..92572f563156 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -63,6 +63,7 @@ export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS = { calendarChannel: '20202020-93ee-4da4-8d58-0282c4a9cb7d', calendarEvent: '20202020-5aa5-437e-bb86-f42d457783e3', eventExternalId: '20202020-9ec8-48bb-b279-21d0734a75a1', + recurringEventExternalId: '20202020-c58f-4c69-9bf8-9518fa31aa50', }; export const CALENDAR_CHANNEL_STANDARD_FIELD_IDS = { @@ -78,6 +79,7 @@ export const CALENDAR_CHANNEL_STANDARD_FIELD_IDS = { syncStatus: '20202020-7116-41da-8b4b-035975c4eb6a', syncStage: '20202020-6246-42e6-b5cd-003bd921782c', syncStageStartedAt: '20202020-a934-46f1-a8e7-9568b1e3a53e', + syncedAt: '20202020-2ff5-4f70-953a-3d0d36357576', }; export const CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS = { @@ -103,7 +105,6 @@ export const CALENDAR_EVENT_STANDARD_FIELD_IDS = { iCalUID: '20202020-f24b-45f4-b6a3-d2f9fcb98714', conferenceSolution: '20202020-1c3f-4b5a-b526-5411a82179eb', conferenceLink: '20202020-35da-43ef-9ca0-e936e9dc237b', - recurringEventExternalId: '20202020-4b96-43d0-8156-4c7a9717635c', calendarChannelEventAssociations: '20202020-bdf8-4572-a2cc-ecbb6bcc3a02', calendarEventParticipants: '20202020-e07e-4ccb-88f5-6f3d00458eec', }; @@ -135,6 +136,7 @@ export const COMPANY_STANDARD_FIELD_IDS = { favorites: '20202020-4d1d-41ac-b13b-621631298d55', attachments: '20202020-c1b5-4120-b0f0-987ca401ed53', timelineActivities: '20202020-0414-4daf-9c0d-64fe7b27f89f', + searchVector: '85c71601-72f9-4b7b-b343-d46100b2c74d', }; export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = { @@ -148,6 +150,7 @@ export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = { messageChannels: '20202020-24f7-4362-8468-042204d1e445', calendarChannels: '20202020-af4a-47bb-99ec-51911c1d3977', handleAliases: '20202020-8a3d-46be-814f-6228af16c47b', + scopes: '20202020-8a3d-46be-814f-6228af16c47c', }; export const EVENT_STANDARD_FIELD_IDS = { @@ -188,6 +191,9 @@ export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = { opportunity: '20202020-7664-4a35-a3df-580d389fd527', task: '20202020-b2f5-415c-9135-a31dfe49501b', note: '20202020-ec55-4135-8da5-3a20badc0156', + workflow: '20202020-616c-4ad3-a2e9-c477c341e295', + workflowVersion: '20202020-74f1-4711-a129-e14ca0ecd744', + workflowRun: '20202020-96f0-401b-9186-a3a0759225ac', custom: '20202020-4a71-41b0-9f83-9cdcca3f8b14', linkedRecordCachedName: '20202020-cfdb-4bef-bbce-a29f41230934', linkedRecordId: '20202020-2e0e-48c0-b445-ee6c1e61687d', @@ -201,6 +207,8 @@ export const FAVORITE_STANDARD_FIELD_IDS = { company: '20202020-cff5-4682-8bf9-069169e08279', opportunity: '20202020-dabc-48e1-8318-2781a2b32aa2', workflow: '20202020-b11b-4dc8-999a-6bd0a947b463', + workflowVersion: '20202020-e1b8-4caf-b55a-3ab4d4cbcd21', + workflowRun: '20202020-db5a-4fe4-9a13-9afa22b1e762', task: '20202020-1b1b-4b3b-8b1b-7f8d6a1d7d5c', note: '20202020-1f25-43fe-8b00-af212fdde824', view: '20202020-5a93-4fa9-acce-e73481a0bbdf', @@ -300,6 +308,7 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = { noteTargets: '20202020-dd3f-42d5-a382-db58aabf43d3', attachments: '20202020-87c7-4118-83d6-2f4031005209', timelineActivities: '20202020-30e2-421f-96c7-19c69d1cf631', + searchVector: '428a0da5-4b2e-4ce3-b695-89a8b384e6e3', }; export const PERSON_STANDARD_FIELD_IDS = { @@ -310,6 +319,7 @@ export const PERSON_STANDARD_FIELD_IDS = { xLink: '20202020-8fc2-487c-b84a-55a99b145cfd', jobTitle: '20202020-b0d0-415a-bef9-640a26dacd9b', phone: '20202020-4564-4b8b-a09f-05445f2e0bce', + phones: '20202020-0638-448e-8825-439134618022', city: '20202020-5243-4ffb-afc5-2c675da41346', avatarUrl: '20202020-b8a6-40df-961c-373dc5d2ec21', position: '20202020-fcd5-4231-aff5-fff583eaa0b1', @@ -324,6 +334,7 @@ export const PERSON_STANDARD_FIELD_IDS = { messageParticipants: '20202020-498e-4c61-8158-fa04f0638334', calendarEventParticipants: '20202020-52ee-45e9-a702-b64b3753e3a9', timelineActivities: '20202020-a43e-4873-9c23-e522de906ce5', + searchVector: '57d1d7ad-fa10-44fc-82f3-ad0959ec2534', }; export const TASK_STANDARD_FIELD_IDS = { @@ -414,15 +425,21 @@ export const WORKFLOW_STANDARD_FIELD_IDS = { runs: '20202020-759b-4340-b58b-e73595c4df4f', eventListeners: '20202020-0229-4c66-832e-035c67579a38', favorites: '20202020-c554-4c41-be7a-cf9cd4b0d512', + timelineActivities: '20202020-906e-486a-a798-131a5f081faf', }; export const WORKFLOW_RUN_STANDARD_FIELD_IDS = { + name: '20202020-b840-4253-aef9-4e5013694587', workflowVersion: '20202020-2f52-4ba8-8dc4-d0d6adb9578d', workflow: '20202020-8c57-4e7f-84f5-f373f68e1b82', startedAt: '20202020-a234-4e2d-bd15-85bcea6bb183', endedAt: '20202020-e1c1-4b6b-bbbd-b2beaf2e159e', status: '20202020-6b3e-4f9c-8c2b-2e5b8e6d6f3b', + position: '20202020-7802-4c40-ae89-1f506fe3365c', createdBy: '20202020-6007-401a-8aa5-e6f38581a6f3', + output: '20202020-7be4-4db2-8ac6-3ff0d740843d', + favorites: '20202020-4baf-4604-b899-2f7fcfbbf90d', + timelineActivities: '20202020-af4d-4eb0-babc-eb960a45b356', }; export const WORKFLOW_VERSION_STANDARD_FIELD_IDS = { @@ -430,8 +447,11 @@ export const WORKFLOW_VERSION_STANDARD_FIELD_IDS = { workflow: '20202020-afa3-46c3-91b0-0631ca6aa1c8', trigger: '20202020-4eae-43e7-86e0-212b41a30b48', status: '20202020-5a34-440e-8a25-39d8c3d1d4cf', + position: '20202020-791d-4950-ab28-0e704767ae1c', runs: '20202020-1d08-46df-901a-85045f18099a', steps: '20202020-5988-4a64-b94a-1f9b7b989039', + favorites: '20202020-b8e0-4e57-928d-b51671cc71f2', + timelineActivities: '20202020-fcb0-4695-b17e-3b43a421c633', }; export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = { @@ -470,4 +490,5 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = { favorites: '20202020-a4a7-4686-b296-1c6c3482ee21', attachments: '20202020-8d59-46ca-b7b2-73d167712134', timelineActivities: '20202020-f1ef-4ba4-8f33-1a4577afa477', + searchVector: '70e56537-18ef-4811-b1c7-0a444006b815', }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts index 3c06dc9b584d..1aae07797ea7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory.ts @@ -12,6 +12,7 @@ import { import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage'; import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util'; @@ -160,11 +161,15 @@ export class StandardFieldFactory { description: workspaceFieldMetadataArgs.description, defaultValue: workspaceFieldMetadataArgs.defaultValue, options: workspaceFieldMetadataArgs.options, + settings: workspaceFieldMetadataArgs.settings, workspaceId: context.workspaceId, isNullable: workspaceFieldMetadataArgs.isNullable, + isUnique: workspaceFieldMetadataArgs.isUnique, isCustom: workspaceFieldMetadataArgs.isDeprecated ? true : false, isSystem: workspaceFieldMetadataArgs.isSystem ?? false, isActive: workspaceFieldMetadataArgs.isActive ?? true, + asExpression: workspaceFieldMetadataArgs.asExpression, + generatedType: workspaceFieldMetadataArgs.generatedType, }, ]; } @@ -215,6 +220,9 @@ export class StandardFieldFactory { isCustom: false, isSystem: true, isNullable: workspaceRelationMetadataArgs.isNullable, + isUnique: + workspaceRelationMetadataArgs.type === + RelationMetadataType.ONE_TO_ONE, isActive: workspaceRelationMetadataArgs.isActive ?? true, }); } @@ -233,6 +241,8 @@ export class StandardFieldFactory { workspaceEntityMetadataArgs?.isSystem || workspaceRelationMetadataArgs.isSystem, isNullable: true, + isUnique: + workspaceRelationMetadataArgs.type === RelationMetadataType.ONE_TO_ONE, isActive: workspaceRelationMetadataArgs.isActive ?? true, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory.ts index 17bd00215c07..5d6412fef7b7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory.ts @@ -5,9 +5,12 @@ import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-syn import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity'; import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util'; @Injectable() @@ -15,23 +18,37 @@ export class StandardIndexFactory { create( standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[], context: WorkspaceSyncContext, - originalObjectMetadataMap: Record<string, ObjectMetadataEntity>, + originalStandardObjectMetadataMap: Record<string, ObjectMetadataEntity>, + originalCustomObjectMetadataMap: Record<string, ObjectMetadataEntity>, workspaceFeatureFlagsMap: FeatureFlagMap, ): Partial<IndexMetadataEntity>[] { - return standardObjectMetadataDefinitions.flatMap((standardObjectMetadata) => - this.createIndexMetadata( - standardObjectMetadata, + const standardIndexOnStandardObjects = + standardObjectMetadataDefinitions.flatMap((standardObjectMetadata) => + this.createStandardIndexMetadataForStandardObject( + standardObjectMetadata, + context, + originalStandardObjectMetadataMap, + workspaceFeatureFlagsMap, + ), + ); + + const standardIndexesOnCustomObjects = + this.createStandardIndexMetadataForCustomObject( context, - originalObjectMetadataMap, + originalCustomObjectMetadataMap, workspaceFeatureFlagsMap, - ), - ); + ); + + return [ + standardIndexOnStandardObjects, + standardIndexesOnCustomObjects, + ].flat(); } - private createIndexMetadata( + private createStandardIndexMetadataForStandardObject( target: typeof BaseWorkspaceEntity, context: WorkspaceSyncContext, - originalObjectMetadataMap: Record<string, ObjectMetadataEntity>, + originalStandardObjectMetadataMap: Record<string, ObjectMetadataEntity>, workspaceFeatureFlagsMap: FeatureFlagMap, ): Partial<IndexMetadataEntity>[] { const workspaceEntity = metadataArgsStorage.filterEntities(target); @@ -58,7 +75,7 @@ export class StandardIndexFactory { return workspaceIndexMetadataArgsCollection.map( (workspaceIndexMetadataArgs) => { const objectMetadata = - originalObjectMetadataMap[workspaceEntity.nameSingular]; + originalStandardObjectMetadataMap[workspaceEntity.nameSingular]; if (!objectMetadata) { throw new Error( @@ -71,10 +88,59 @@ export class StandardIndexFactory { objectMetadataId: objectMetadata.id, name: workspaceIndexMetadataArgs.name, columns: workspaceIndexMetadataArgs.columns, + isUnique: workspaceIndexMetadataArgs.isUnique, + isCustom: false, + indexWhereClause: workspaceIndexMetadataArgs.whereClause, + indexType: workspaceIndexMetadataArgs.type, }; return indexMetadata; }, ); } + + private createStandardIndexMetadataForCustomObject( + context: WorkspaceSyncContext, + originalCustomObjectMetadataMap: Record<string, ObjectMetadataEntity>, + workspaceFeatureFlagsMap: FeatureFlagMap, + ): Partial<IndexMetadataEntity>[] { + const target = CustomWorkspaceEntity; + const workspaceEntity = metadataArgsStorage.filterExtendedEntities(target); + + if (!workspaceEntity) { + throw new Error( + `Object metadata decorator not found, can't parse ${target.name}`, + ); + } + + const workspaceIndexMetadataArgsCollection = metadataArgsStorage + .filterIndexes(target) + .filter((workspaceIndexMetadataArgs) => { + return !isGatedAndNotEnabled( + workspaceIndexMetadataArgs.gate, + workspaceFeatureFlagsMap, + ); + }); + + return Object.entries(originalCustomObjectMetadataMap).flatMap( + ([customObjectName, customObjectMetadata]) => { + return workspaceIndexMetadataArgsCollection.map( + (workspaceIndexMetadataArgs) => { + const indexMetadata: PartialIndexMetadata = { + workspaceId: context.workspaceId, + objectMetadataId: customObjectMetadata.id, + name: `IDX_${generateDeterministicIndexName([computeTableName(customObjectName, true), ...workspaceIndexMetadataArgs.columns])}`, + columns: workspaceIndexMetadataArgs.columns, + isCustom: false, + isUnique: workspaceIndexMetadataArgs.isUnique, + indexType: workspaceIndexMetadataArgs.type, + indexWhereClause: workspaceIndexMetadataArgs.whereClause, + }; + + return indexMetadata; + }, + ); + }, + ); + } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts index c3156881dc64..c45e3b9ae82b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory.ts @@ -14,7 +14,7 @@ export class StandardObjectFactory { standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[], context: WorkspaceSyncContext, workspaceFeatureFlagsMap: FeatureFlagMap, - ): Omit<PartialWorkspaceEntity, 'fields'>[] { + ): Omit<PartialWorkspaceEntity, 'fields' | 'indexMetadatas'>[] { return standardObjectMetadataDefinitions .map((metadata) => this.createObjectMetadata(metadata, context, workspaceFeatureFlagsMap), @@ -26,7 +26,7 @@ export class StandardObjectFactory { target: typeof BaseWorkspaceEntity, context: WorkspaceSyncContext, workspaceFeatureFlagsMap: FeatureFlagMap, - ): Omit<PartialWorkspaceEntity, 'fields'> | undefined { + ): Omit<PartialWorkspaceEntity, 'fields' | 'indexMetadatas'> | undefined { const workspaceEntityMetadataArgs = metadataArgsStorage.filterEntities(target); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts index b4279b5d2397..23c746a5f18c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts @@ -1,6 +1,6 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { ComputedPartialFieldMetadata } from './partial-field-metadata.interface'; import { ComputedPartialWorkspaceEntity } from './partial-object-metadata.interface'; @@ -33,9 +33,15 @@ export interface ComparatorDeleteResult<T> { export type ObjectComparatorResult = | ComparatorSkipResult - | ComparatorCreateResult<Omit<ComputedPartialWorkspaceEntity, 'fields'>> + | ComparatorCreateResult< + Omit<ComputedPartialWorkspaceEntity, 'fields' | 'indexMetadatas'> + > | ComparatorUpdateResult< - Partial<Omit<ComputedPartialWorkspaceEntity, 'fields'>> & { id: string } + Partial< + Omit<ComputedPartialWorkspaceEntity, 'fields' | 'indexMetadatas'> + > & { + id: string; + } >; export type FieldComparatorResult = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface.ts index 81722ad20710..4f17d0ee3dca 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface.ts @@ -16,6 +16,8 @@ export type PartialFieldMetadata = Omit< workspaceId: string; objectMetadataId?: string; isActive?: boolean; + asExpression?: string; + generatedType?: 'STORED' | 'VIRTUAL'; }; export type PartialComputedFieldMetadata = { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts index 7fff3860d216..8f3df062afc8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts @@ -13,17 +13,22 @@ import { v4 as uuidV4 } from 'uuid'; import { PartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; import { FieldMetadataEntity, 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 { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { FieldMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory'; import { ObjectMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory'; import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage'; +import { capitalize } from 'src/utils/capitalize'; @Injectable() export class WorkspaceMetadataUpdaterService { @@ -241,10 +246,42 @@ export class WorkspaceMetadataUpdaterService { const convertIndexFieldMetadataForSaving = ( column: string, order: number, - ) => { + ): DeepPartial<IndexFieldMetadataEntity> => { + // Ensure correct type const fieldMetadata = originalObjectMetadataCollection .find((object) => object.id === indexMetadata.objectMetadataId) - ?.fields.find((field) => column === field.name); + ?.fields.find((field) => { + if (field.name === column) { + return true; + } + + if (!isCompositeFieldMetadataType(field.type)) { + return; + } + + const compositeType = compositeTypeDefinitions.get( + field.type as CompositeFieldMetadataType, + ); + + if (!compositeType) { + throw new Error( + `Composite type definition not found for type: ${field.type}`, + ); + } + + const columnNames = compositeType.properties.reduce( + (acc, column) => { + acc.push(`${field.name}${capitalize(column.name)}`); + + return acc; + }, + [] as string[], + ); + + if (columnNames.includes(column)) { + return true; + } + }); if (!fieldMetadata) { throw new Error(` diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service.ts index d82e2bf69c69..4e10f7ea2b81 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service.ts @@ -10,6 +10,7 @@ import { } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface'; import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity'; @@ -143,13 +144,27 @@ export class WorkspaceSyncFieldMetadataService { ] of standardObjectStandardFieldMetadataMap) { const originalObjectMetadata = originalObjectMetadataMap[standardObjectId]; - const computedStandardFieldMetadataCollection = computeStandardFields( + + let computedStandardFieldMetadataCollection = computeStandardFields( standardFieldMetadataCollection, originalObjectMetadata, // We need to provide this for generated relations with custom objects customObjectMetadataCollection, ); + let originalObjectMetadataFields = originalObjectMetadata.fields; + + if (!workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) { + computedStandardFieldMetadataCollection = + computedStandardFieldMetadataCollection.filter( + (field) => field.type !== FieldMetadataType.TS_VECTOR, + ); + + originalObjectMetadataFields = originalObjectMetadataFields.filter( + (field) => field.type !== FieldMetadataType.TS_VECTOR, + ); + } + const fieldComparatorResults = this.workspaceFieldComparator.compare( originalObjectMetadata.id, originalObjectMetadata.fields, @@ -177,11 +192,24 @@ export class WorkspaceSyncFieldMetadataService { // Loop over all custom objects from the DB and compare their fields with standard fields for (const customObjectMetadata of customObjectMetadataCollection) { // Also, maybe it's better to refactor a bit and move generation part into a separate module ? - const standardFieldMetadataCollection = computeStandardFields( + let standardFieldMetadataCollection = computeStandardFields( customObjectStandardFieldMetadataCollection, customObjectMetadata, ); + let customObjectMetadataFields = customObjectMetadata.fields; + + if (!workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) { + standardFieldMetadataCollection = + standardFieldMetadataCollection.filter( + (field) => field.type !== FieldMetadataType.TS_VECTOR, + ); + + customObjectMetadataFields = customObjectMetadataFields.filter( + (field) => field.type !== FieldMetadataType.TS_VECTOR, + ); + } + /** * COMPARE FIELD METADATA */ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service.ts index 4f7a30ef9d25..d9ee8908ce0e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service.ts @@ -1,22 +1,25 @@ import { Injectable, Logger } from '@nestjs/common'; -import { EntityManager } from 'typeorm'; +import { Any, EntityManager } from 'typeorm'; import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; -import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; -import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface'; import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface'; +import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface'; +import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; -import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage'; -import { StandardIndexFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory'; +import { + IndexMetadataEntity, + IndexType, +} from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util'; -import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; -import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects'; +import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationIndexFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory'; import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator'; +import { StandardIndexFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory'; import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service'; -import { WorkspaceMigrationIndexFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory'; +import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects'; +import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage'; +import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util'; @Injectable() export class WorkspaceSyncIndexMetadataService { @@ -47,35 +50,60 @@ export class WorkspaceSyncIndexMetadataService { workspaceId: context.workspaceId, // We're only interested in standard fields fields: { isCustom: false }, - isCustom: false, }, - relations: ['dataSource', 'fields', 'indexes'], + relations: ['dataSource', 'fields', 'indexMetadatas'], }); // Create map of object metadata & field metadata by unique identifier - const originalObjectMetadataMap = mapObjectMetadataByUniqueIdentifier( - originalObjectMetadataCollection, - // Relation are based on the singular name + const originalStandardObjectMetadataMap = + mapObjectMetadataByUniqueIdentifier( + originalObjectMetadataCollection.filter( + (objectMetadata) => !objectMetadata.isCustom, + ), + // Relation are based on the singular name + (objectMetadata) => objectMetadata.nameSingular, + ); + + const originalCustomObjectMetadataMap = mapObjectMetadataByUniqueIdentifier( + originalObjectMetadataCollection.filter( + (objectMetadata) => objectMetadata.isCustom, + ), (objectMetadata) => objectMetadata.nameSingular, ); const indexMetadataRepository = manager.getRepository(IndexMetadataEntity); - const originalIndexMetadataCollection = await indexMetadataRepository.find({ + let originalIndexMetadataCollection = await indexMetadataRepository.find({ where: { workspaceId: context.workspaceId, + objectMetadataId: Any( + Object.values(originalObjectMetadataCollection).map( + (object) => object.id, + ), + ), + isCustom: false, }, relations: ['indexFieldMetadatas.fieldMetadata'], }); // Generate index metadata from models - const standardIndexMetadataCollection = this.standardIndexFactory.create( + let standardIndexMetadataCollection = this.standardIndexFactory.create( standardObjectMetadataDefinitions, context, - originalObjectMetadataMap, + originalStandardObjectMetadataMap, + originalCustomObjectMetadataMap, workspaceFeatureFlagsMap, ); + if (!workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) { + originalIndexMetadataCollection = originalIndexMetadataCollection.filter( + (index) => index.indexType !== IndexType.GIN, + ); + + standardIndexMetadataCollection = standardIndexMetadataCollection.filter( + (index) => index.indexType !== IndexType.GIN, + ); + } const indexComparatorResults = this.workspaceIndexComparator.compare( originalIndexMetadataCollection, standardIndexMetadataCollection, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts index 3d473242cde8..87cd71536096 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts @@ -55,8 +55,6 @@ export const standardObjectMetadataDefinitions = [ CompanyWorkspaceEntity, ConnectedAccountWorkspaceEntity, FavoriteWorkspaceEntity, - OpportunityWorkspaceEntity, - PersonWorkspaceEntity, TimelineActivityWorkspaceEntity, ViewFieldWorkspaceEntity, ViewGroupWorkspaceEntity, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts index 3cbbf6b5ebe6..8d3aea6b385c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts @@ -1,19 +1,19 @@ -import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface'; import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; +import { ComputedPartialWorkspaceEntity } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-object-metadata.interface'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; export class WorkspaceSyncStorage { // Object metadata private readonly _objectMetadataCreateCollection: Omit< ComputedPartialWorkspaceEntity, - 'fields' + 'fields' | 'indexMetadatas' >[] = []; private readonly _objectMetadataUpdateCollection: (Partial< - Omit<ComputedPartialWorkspaceEntity, 'fields'> + Omit<ComputedPartialWorkspaceEntity, 'fields' | 'indexMetadatas'> > & { id: string; })[] = []; @@ -89,7 +89,7 @@ export class WorkspaceSyncStorage { } addCreateObjectMetadata( - object: Omit<ComputedPartialWorkspaceEntity, 'fields'>, + object: Omit<ComputedPartialWorkspaceEntity, 'fields' | 'indexMetadatas'>, ) { this._objectMetadataCreateCollection.push(object); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts new file mode 100644 index 000000000000..8703879e5e64 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts @@ -0,0 +1,90 @@ +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; + +const nameTextField = { name: 'name', type: FieldMetadataType.TEXT }; +const nameFullNameField = { + name: 'name', + type: FieldMetadataType.FULL_NAME, +}; +const jobTitleTextField = { name: 'jobTitle', type: FieldMetadataType.TEXT }; +const emailsEmailsField = { name: 'emails', type: FieldMetadataType.EMAILS }; + +jest.mock( + 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util', + () => ({ + computeColumnName: jest.fn((name) => { + if (name === 'name') { + return 'name'; + } + if (name === 'jobTitle') { + return 'jobTitle'; + } + if (name === 'emailsPrimaryEmail') { + return 'emailsPrimaryEmail'; + } + if (name === 'emailsAdditionalEmails') { + return 'emailsAdditionalEmails'; + } + if (name === 'nameFirstName') { + return 'nameFirstName'; + } + if (name === 'nameLastName') { + return 'nameLastName'; + } + }), + computeCompositeColumnName: jest.fn((field, property) => { + if ( + field.name === emailsEmailsField.name && + property.name === 'primaryEmail' + ) { + return 'emailsPrimaryEmail'; + } + if ( + field.name === emailsEmailsField.name && + property.name === 'additionalEmails' + ) { + return 'emailsAdditionalEmails'; + } + if ( + field.name === nameFullNameField.name && + property.name === 'firstName' + ) { + return 'nameFirstName'; + } + if ( + field.name === nameFullNameField.name && + property.name === 'lastName' + ) { + return 'nameLastName'; + } + }), + }), +); + +describe('getTsVectorColumnExpressionFromFields', () => { + it('should generate correct expression for simple text field', () => { + const fields = [nameTextField]; + const result = getTsVectorColumnExpressionFromFields(fields); + + expect(result).toContain("to_tsvector('simple', COALESCE(\"name\", ''))"); + }); + + it('should handle multiple fields', () => { + const fields = [nameFullNameField, jobTitleTextField, emailsEmailsField]; + const result = getTsVectorColumnExpressionFromFields(fields); + const expected = ` + to_tsvector('simple', COALESCE("nameFirstName", '') || ' ' || COALESCE("nameLastName", '') || ' ' || COALESCE("jobTitle", '') || ' ' || + COALESCE( + replace( + "emailsPrimaryEmail", + '@', + ' ' + ), + '' + ) + ) + `.trim(); + + expect(result.trim()).toBe(expected); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts new file mode 100644 index 000000000000..83cabf58b278 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts @@ -0,0 +1,80 @@ +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + computeColumnName, + computeCompositeColumnName, +} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; +import { + WorkspaceMigrationException, + WorkspaceMigrationExceptionCode, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; + +type FieldTypeAndNameMetadata = { + name: string; + type: FieldMetadataType; +}; + +export const getTsVectorColumnExpressionFromFields = ( + fieldsUsedForSearch: FieldTypeAndNameMetadata[], +): string => { + const columnExpressions = fieldsUsedForSearch.flatMap( + getColumnExpressionsFromField, + ); + const concatenatedExpression = columnExpressions.join(" || ' ' || "); + + return `to_tsvector('simple', ${concatenatedExpression})`; +}; + +const getColumnExpressionsFromField = ( + fieldMetadataTypeAndName: FieldTypeAndNameMetadata, +): string[] => { + if (isCompositeFieldMetadataType(fieldMetadataTypeAndName.type)) { + const compositeType = compositeTypeDefinitions.get( + fieldMetadataTypeAndName.type, + ); + + if (!compositeType) { + throw new WorkspaceMigrationException( + `Composite type not found for field metadata type: ${fieldMetadataTypeAndName.type}`, + WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA, + ); + } + + return compositeType.properties + .filter((property) => property.type === FieldMetadataType.TEXT) + .map((property) => { + const columnName = computeCompositeColumnName( + fieldMetadataTypeAndName, + property, + ); + + return getColumnExpression(columnName, fieldMetadataTypeAndName.type); + }); + } + const columnName = computeColumnName(fieldMetadataTypeAndName.name); + + return [getColumnExpression(columnName, fieldMetadataTypeAndName.type)]; +}; + +const getColumnExpression = ( + columnName: string, + fieldType: FieldMetadataType, +): string => { + const quotedColumnName = `"${columnName}"`; + + if (fieldType === FieldMetadataType.EMAILS) { + return ` + COALESCE( + replace( + ${quotedColumnName}, + '@', + ' ' + ), + '' + ) + `; + } else { + return `COALESCE(${quotedColumnName}, '')`; + } +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts index 6e035b99c07f..c090d413aa18 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts @@ -1,10 +1,12 @@ import { Injectable, Logger } from '@nestjs/common'; -import { InjectDataSource } from '@nestjs/typeorm'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { DataSource, QueryFailedError } from 'typeorm'; +import { DataSource, QueryFailedError, Repository } from 'typeorm'; import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; @@ -35,6 +37,8 @@ export class WorkspaceSyncMetadataService { private readonly workspaceSyncIndexMetadataService: WorkspaceSyncIndexMetadataService, private readonly workspaceSyncObjectMetadataIdentifiersService: WorkspaceSyncObjectMetadataIdentifiersService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository<FeatureFlagEntity>, ) {} /** @@ -149,6 +153,13 @@ export class WorkspaceSyncMetadataService { await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( context.workspaceId, ); + + if (workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) { + await this.featureFlagService.enableFeatureFlags( + [FeatureFlagKey.IsWorkspaceMigratedForSearch], + context.workspaceId, + ); + } } catch (error) { this.logger.error('Sync of standard objects failed with:', error); diff --git a/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts b/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts index ee70a3171911..ff48d1f3287b 100644 --- a/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts +++ b/packages/twenty-server/src/modules/api-key/standard-objects/api-key.workspace-entity.ts @@ -11,9 +11,9 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.apiKey, namePlural: 'apiKeys', - labelSingular: 'Api Key', - labelPlural: 'Api Keys', - description: 'An api key', + labelSingular: 'API Key', + labelPlural: 'API Keys', + description: 'An API key', icon: 'IconRobot', labelIdentifierStandardId: API_KEY_STANDARD_FIELD_IDS.name, }) diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts index 387dc0743c68..1406d442d02c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts @@ -19,8 +19,8 @@ export class CalendarEventCleanerConnectedAccountListener { private readonly calendarQueueService: MessageQueueService, ) {} - @OnEvent('connectedAccount.deleted') - async handleDeletedEvent( + @OnEvent('connectedAccount.destroyed') + async handleDestroyedEvent( payload: WorkspaceEventBatch< ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity> >, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts index 0e472d465e9c..0385ae884422 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts @@ -25,13 +25,11 @@ import { CalendarCommonModule } from 'src/modules/calendar/common/calendar-commo import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { RefreshAccessTokenManagerModule } from 'src/modules/connected-account/refresh-access-token-manager/refresh-access-token-manager.module'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Module({ imports: [ ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, BlocklistWorkspaceEntity, WorkspaceMemberWorkspaceEntity, ]), diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts index 406e717021ac..1e28927e25b8 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts @@ -3,15 +3,12 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { CalendarEventsImportService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service'; import { CalendarChannelSyncStage, CalendarChannelWorkspaceEntity, } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; export type CalendarEventsImportJobData = { @@ -27,8 +24,6 @@ export class CalendarEventListFetchJob { constructor( private readonly twentyORMManager: TwentyORMManager, private readonly calendarEventsImportService: CalendarEventsImportService, - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, ) {} @Process(CalendarEventListFetchJob.name) @@ -47,6 +42,7 @@ export class CalendarEventListFetchJob { id: calendarChannelId, isSyncEnabled: true, }, + relations: ['connectedAccount'], }); if (!calendarChannel) { @@ -62,12 +58,6 @@ export class CalendarEventListFetchJob { return; } - const connectedAccount = - await this.connectedAccountRepository.getConnectedAccountOrThrow( - workspaceId, - calendarChannel.connectedAccountId, - ); - switch (calendarChannel.syncStage) { case CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING: await calendarChannelRepository.update(calendarChannelId, { @@ -77,7 +67,7 @@ export class CalendarEventListFetchJob { await this.calendarEventsImportService.processCalendarEventsImport( calendarChannel, - connectedAccount, + calendarChannel.connectedAccount, workspaceId, ); break; @@ -85,7 +75,7 @@ export class CalendarEventListFetchJob { case CalendarChannelSyncStage.PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING: await this.calendarEventsImportService.processCalendarEventsImport( calendarChannel, - connectedAccount, + calendarChannel.connectedAccount, workspaceId, ); break; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts index 8c71c73f1fc8..7552722e02cc 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts @@ -7,12 +7,10 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { injectIdsInCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/utils/inject-ids-in-calendar-events.util'; import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service'; import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { CalendarEventWithParticipants } from 'src/modules/calendar/common/types/calendar-event'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @@ -28,7 +26,6 @@ export class CalendarSaveEventsService { private readonly calendarEventParticipantService: CalendarEventParticipantService, @InjectMessageQueue(MessageQueue.contactCreationQueue) private readonly messageQueueService: MessageQueueService, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, ) {} public async saveCalendarEventsAndEnqueueContactCreationJob( @@ -103,6 +100,7 @@ export class CalendarSaveEventsService { calendarEventId: calendarEvent.id, eventExternalId: calendarEvent.externalId, calendarChannelId: calendarChannel.id, + recurringEventExternalId: calendarEvent.recurringEventExternalId, })); const participantsToSave = eventsToSave.flatMap( @@ -113,16 +111,57 @@ export class CalendarSaveEventsService { (event) => event.participants, ); - const savedCalendarEventParticipantsToEmit: CalendarEventParticipantWorkspaceEntity[] = - []; - const workspaceDataSource = await this.twentyORMManager.getDatasource(); await workspaceDataSource?.transaction(async (transactionManager) => { - await calendarEventRepository.save(eventsToSave, {}, transactionManager); + await calendarEventRepository.save( + eventsToSave.map( + (calendarEvent) => + ({ + id: calendarEvent.id, + iCalUID: calendarEvent.iCalUID, + title: calendarEvent.title, + description: calendarEvent.description, + startsAt: calendarEvent.startsAt, + endsAt: calendarEvent.endsAt, + location: calendarEvent.location, + isFullDay: calendarEvent.isFullDay, + isCanceled: calendarEvent.isCanceled, + conferenceSolution: calendarEvent.conferenceSolution, + conferenceLink: { + primaryLinkLabel: calendarEvent.conferenceLinkLabel, + primaryLinkUrl: calendarEvent.conferenceLinkUrl, + }, + externalCreatedAt: calendarEvent.externalCreatedAt, + externalUpdatedAt: calendarEvent.externalUpdatedAt, + }) satisfies DeepPartial<CalendarEventWorkspaceEntity>, + ), + {}, + transactionManager, + ); await calendarEventRepository.save( - eventsToUpdate, + eventsToUpdate.map( + (calendarEvent) => + ({ + id: calendarEvent.id, + iCalUID: calendarEvent.iCalUID, + title: calendarEvent.title, + description: calendarEvent.description, + startsAt: calendarEvent.startsAt, + endsAt: calendarEvent.endsAt, + location: calendarEvent.location, + isFullDay: calendarEvent.isFullDay, + isCanceled: calendarEvent.isCanceled, + conferenceSolution: calendarEvent.conferenceSolution, + conferenceLink: { + primaryLinkLabel: calendarEvent.conferenceLinkLabel, + primaryLinkUrl: calendarEvent.conferenceLinkUrl, + }, + externalCreatedAt: calendarEvent.externalCreatedAt, + externalUpdatedAt: calendarEvent.externalUpdatedAt, + }) satisfies DeepPartial<CalendarEventWorkspaceEntity>, + ), {}, transactionManager, ); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/listeners/calendar-event-participant-person.listener.ts b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/listeners/calendar-event-participant-person.listener.ts index e2124ab2a9e6..0bbd2cf5a074 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/listeners/calendar-event-participant-person.listener.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/listeners/calendar-event-participant-person.listener.ts @@ -32,10 +32,7 @@ export class CalendarEventParticipantPersonListener { >, ) { for (const eventPayload of payload.events) { - if ( - eventPayload.properties.after.emails?.primaryEmail === null && - eventPayload.properties.after.email === null - ) { + if (eventPayload.properties.after.emails?.primaryEmail === null) { continue; } @@ -44,9 +41,7 @@ export class CalendarEventParticipantPersonListener { CalendarEventParticipantMatchParticipantJob.name, { workspaceId: payload.workspaceId, - email: - eventPayload.properties.after.emails?.primaryEmail ?? - eventPayload.properties.after.email, // TODO + email: eventPayload.properties.after.emails?.primaryEmail, personId: eventPayload.recordId, }, ); @@ -64,16 +59,14 @@ export class CalendarEventParticipantPersonListener { objectRecordUpdateEventChangedProperties( eventPayload.properties.before, eventPayload.properties.after, - ).includes('email') + ).includes('emails') ) { // TODO: modify this job to take an array of participants to match await this.messageQueueService.add<CalendarEventParticipantUnmatchParticipantJobData>( CalendarEventParticipantUnmatchParticipantJob.name, { workspaceId: payload.workspaceId, - email: - eventPayload.properties.before.emails?.primaryEmail ?? - eventPayload.properties.before.email, + email: eventPayload.properties.before.emails?.primaryEmail, personId: eventPayload.recordId, }, ); @@ -82,9 +75,7 @@ export class CalendarEventParticipantPersonListener { CalendarEventParticipantMatchParticipantJob.name, { workspaceId: payload.workspaceId, - email: - eventPayload.properties.after.emails?.primaryEmail ?? - eventPayload.properties.after.email, + email: eventPayload.properties.after.emails?.primaryEmail, personId: eventPayload.recordId, }, ); diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts index 041c3a648290..2e8810d1f7b1 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service.ts @@ -1,12 +1,12 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import groupBy from 'lodash.groupby'; +import { Any } from 'typeorm'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -15,8 +15,6 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta export class CanAccessCalendarEventService { constructor( private readonly twentyORMManager: TwentyORMManager, - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, @InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) private readonly workspaceMemberService: WorkspaceMemberRepository, ) {} @@ -46,20 +44,20 @@ export class CanAccessCalendarEventService { const currentWorkspaceMember = await this.workspaceMemberService.getByIdOrFail(userId, workspaceId); - const calendarChannelsConnectedAccounts = - await this.connectedAccountRepository.getByIds( - calendarChannels.map((channel) => channel.connectedAccountId), - workspaceId, + const connectedAccountRepository = + await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>( + 'connectedAccount', ); - const calendarChannelsWorkspaceMemberIds = - calendarChannelsConnectedAccounts.map( - (connectedAccount) => connectedAccount.accountOwnerId, - ); + const connectedAccounts = await connectedAccountRepository.find({ + select: ['id'], + where: { + calendarChannels: Any(calendarChannels.map((channel) => channel.id)), + accountOwnerId: currentWorkspaceMember.id, + }, + }); - if ( - calendarChannelsWorkspaceMemberIds.includes(currentWorkspaceMember.id) - ) { + if (connectedAccounts.length > 0) { return; } diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts index ff3d178c4a21..2c5a26b18c4f 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts @@ -4,15 +4,11 @@ import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repos import { CalendarEventFindManyPreQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook'; import { CalendarEventFindOnePreQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook'; import { CanAccessCalendarEventService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-event.service'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Module({ imports: [ - ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, - WorkspaceMemberWorkspaceEntity, - ]), + ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), ], providers: [ CanAccessCalendarEventService, diff --git a/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts b/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts index 4940c1db01eb..c0fe5278f898 100644 --- a/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts @@ -171,6 +171,7 @@ export class CalendarChannelSyncStatusService { syncStatus: CalendarChannelSyncStatus.ACTIVE, throttleFailureCount: 0, syncStageStartedAt: null, + syncedAt: new Date().toISOString(), }); await this.schedulePartialCalendarEventListFetch(calendarChannelIds); diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts index f1475da3b012..ee74a697eed8 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts @@ -1,16 +1,16 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; -import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; +import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; @@ -35,6 +35,16 @@ export class CalendarChannelEventAssociationWorkspaceEntity extends BaseWorkspac }) eventExternalId: string; + @WorkspaceField({ + standardId: + CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS.recurringEventExternalId, + type: FieldMetadataType.TEXT, + label: 'Recurring Event ID', + description: 'Recurring Event ID', + icon: 'IconHistory', + }) + recurringEventExternalId: string; + @WorkspaceRelation({ standardId: CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS.calendarChannel, diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts index b6bb2b803af7..fca6a0b01369 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts @@ -270,6 +270,16 @@ export class CalendarChannelWorkspaceEntity extends BaseWorkspaceEntity { }) syncCursor: string; + @WorkspaceField({ + standardId: CALENDAR_CHANNEL_STANDARD_FIELD_IDS.syncedAt, + type: FieldMetadataType.DATE_TIME, + label: 'Last sync date', + description: 'Last sync date', + icon: 'IconHistory', + }) + @WorkspaceIsNullable() + syncedAt: string | null; + @WorkspaceField({ standardId: CALENDAR_CHANNEL_STANDARD_FIELD_IDS.syncStageStartedAt, type: FieldMetadataType.DATE_TIME, diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts index 55ede657ae96..4e4bbbf8615b 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts @@ -145,15 +145,6 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsNullable() conferenceLink: LinksMetadata; - @WorkspaceField({ - standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.recurringEventExternalId, - type: FieldMetadataType.TEXT, - label: 'Recurring Event ID', - description: 'Recurring Event ID', - icon: 'IconHistory', - }) - recurringEventExternalId: string; - @WorkspaceRelation({ standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.calendarChannelEventAssociations, 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..50517314f4c3 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' @@ -34,6 +36,7 @@ export type CalendarEventParticipantWithCalendarEventId = export type CalendarEventWithParticipants = CalendarEvent & { externalId: string; + recurringEventExternalId?: string; participants: CalendarEventParticipant[]; status: string; }; @@ -41,6 +44,7 @@ export type CalendarEventWithParticipants = CalendarEvent & { export type CalendarEventWithParticipantsAndCalendarEventId = CalendarEvent & { id: string; externalId: string; + recurringEventExternalId?: string; participants: CalendarEventParticipantWithCalendarEventId[]; status: string; }; diff --git a/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts b/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts index 32e774d4ccd5..1abbf5dd3c28 100644 --- a/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts +++ b/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts @@ -1,5 +1,6 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; +import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { ActorMetadata, FieldActorSource, @@ -8,12 +9,14 @@ import { AddressMetadata } from 'src/engine/metadata-modules/field-metadata/comp import { CurrencyMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type'; import { LinksMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { RelationMetadataType, RelationOnDeleteAction, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; @@ -22,6 +25,7 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; @@ -32,6 +36,9 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; +const NAME_FIELD_NAME = 'name'; +const DOMAIN_NAME_FIELD_NAME = 'domainName'; + @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.company, namePlural: 'companies', @@ -49,7 +56,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity { description: 'The company name', icon: 'IconBuildingSkyscraper', }) - name: string; + [NAME_FIELD_NAME]: string; @WorkspaceField({ standardId: COMPANY_STANDARD_FIELD_IDS.domainName, @@ -59,7 +66,11 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity { 'The company website URL. We use this url to fetch the company icon', icon: 'IconLink', }) - domainName?: LinksMetadata; + /* + TODO: add soon once we've confirmed it's stabled + @WorkspaceIsUnique() + */ + [DOMAIN_NAME_FIELD_NAME]?: LinksMetadata; @WorkspaceField({ standardId: COMPANY_STANDARD_FIELD_IDS.employees, @@ -273,4 +284,21 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsDeprecated() @WorkspaceIsNullable() addressOld: string; + + @WorkspaceField({ + standardId: COMPANY_STANDARD_FIELD_IDS.searchVector, + type: FieldMetadataType.TS_VECTOR, + label: SEARCH_VECTOR_FIELD.label, + description: SEARCH_VECTOR_FIELD.description, + icon: 'IconUser', + generatedType: 'STORED', + asExpression: getTsVectorColumnExpressionFromFields([ + { name: NAME_FIELD_NAME, type: FieldMetadataType.TEXT }, + { name: DOMAIN_NAME_FIELD_NAME, type: FieldMetadataType.LINKS }, + ]), + }) + @WorkspaceIsNullable() + @WorkspaceIsSystem() + @WorkspaceFieldIndex({ indexType: IndexType.GIN }) + [SEARCH_VECTOR_FIELD.name]: any; } diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts index e1678e3d7ab6..a634ff0feb2d 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts @@ -1,18 +1,11 @@ import { Module } from '@nestjs/common'; -import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service'; import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @Module({ - imports: [ - ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, - ]), - OAuth2ClientManagerModule, - ], + imports: [OAuth2ClientManagerModule], providers: [EmailAliasManagerService, GoogleEmailAliasManagerService], exports: [EmailAliasManagerService], }) diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts index 50a855ba31a7..e4ec7e083960 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts @@ -1,21 +1,18 @@ import { Injectable } from '@nestjs/common'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; +import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @Injectable() export class EmailAliasManagerService { constructor( - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, private readonly googleEmailAliasManagerService: GoogleEmailAliasManagerService, + private readonly twentyORMManager: TwentyORMManager, ) {} public async refreshHandleAliases( connectedAccount: ConnectedAccountWorkspaceEntity, - workspaceId: string, ) { let handleAliases: string[]; @@ -32,10 +29,16 @@ export class EmailAliasManagerService { ); } - await this.connectedAccountRepository.updateHandleAliases( - handleAliases, - connectedAccount.id, - workspaceId, + const connectedAccountRepository = + await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>( + 'connectedAccount', + ); + + await connectedAccountRepository.update( + { id: connectedAccount.id }, + { + handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array + }, ); } } diff --git a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts index da8bededdb03..62ee28d1bede 100644 --- a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts +++ b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts @@ -15,8 +15,8 @@ export class ConnectedAccountListener { private readonly accountsToReconnectService: AccountsToReconnectService, ) {} - @OnEvent('connectedAccount.deleted') - async handleDeletedEvent( + @OnEvent('connectedAccount.destroyed') + async handleDestroyedEvent( payload: WorkspaceEventBatch< ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity> >, diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts index c04999bea385..f49db465d04b 100644 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts @@ -8,7 +8,7 @@ import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -@WorkspaceQueryHook(`connectedAccount.deleteOne`) +@WorkspaceQueryHook(`connectedAccount.destroyOne`) export class ConnectedAccountDeleteOnePreQueryHook implements WorkspaceQueryHookInstance { @@ -34,7 +34,7 @@ export class ConnectedAccountDeleteOnePreQueryHook }); this.workspaceEventEmitter.emit( - 'messageChannel.deleted', + 'messageChannel.destroyed', messageChannels.map( (messageChannel) => ({ diff --git a/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/drivers/google/google-api-refresh-access-token.module.ts b/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/drivers/google/google-api-refresh-access-token.module.ts index 14529ba0b745..5c8308ec7053 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/drivers/google/google-api-refresh-access-token.module.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/drivers/google/google-api-refresh-access-token.module.ts @@ -1,17 +1,10 @@ import { Module } from '@nestjs/common'; -import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-access-token-manager/drivers/google/services/google-api-refresh-access-token.service'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; @Module({ - imports: [ - ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, - ]), - MessagingCommonModule, - ], + imports: [MessagingCommonModule], providers: [GoogleAPIRefreshAccessTokenService], exports: [GoogleAPIRefreshAccessTokenService], }) diff --git a/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/services/refresh-access-token.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/services/refresh-access-token.service.ts index 3326cd4ba8a1..853401356842 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/services/refresh-access-token.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-access-token-manager/services/refresh-access-token.service.ts @@ -1,20 +1,18 @@ import { Injectable } from '@nestjs/common'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; +import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-access-token-manager/drivers/google/services/google-api-refresh-access-token.service'; import { RefreshAccessTokenException, RefreshAccessTokenExceptionCode, } from 'src/modules/connected-account/refresh-access-token-manager/exceptions/refresh-access-token.exception'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @Injectable() export class RefreshAccessTokenService { constructor( private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService, - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, + private readonly twentyORMManager: TwentyORMManager, ) {} async refreshAndSaveAccessToken( @@ -44,10 +42,16 @@ export class RefreshAccessTokenService { ); } - await this.connectedAccountRepository.updateAccessToken( - accessToken, - connectedAccount.id, - workspaceId, + const connectedAccountRepository = + await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>( + 'connectedAccount', + ); + + await connectedAccountRepository.update( + { id: connectedAccount.id }, + { + accessToken, + }, ); return accessToken; diff --git a/packages/twenty-server/src/modules/connected-account/repositories/connected-account.repository.ts b/packages/twenty-server/src/modules/connected-account/repositories/connected-account.repository.ts deleted file mode 100644 index 7be9d5d654fa..000000000000 --- a/packages/twenty-server/src/modules/connected-account/repositories/connected-account.repository.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; - -import { EntityManager } from 'typeorm'; - -import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; - -@Injectable() -export class ConnectedAccountRepository { - constructor( - private readonly workspaceDataSourceService: WorkspaceDataSourceService, - ) {} - - public async getAll( - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity[]> { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - return await this.workspaceDataSourceService.executeRawQuery( - `SELECT * FROM ${dataSourceSchema}."connectedAccount" WHERE "provider" = 'google'`, - [], - workspaceId, - transactionManager, - ); - } - - public async getByIds( - connectedAccountIds: string[], - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity[]> { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - return await this.workspaceDataSourceService.executeRawQuery( - `SELECT * FROM ${dataSourceSchema}."connectedAccount" WHERE "id" = ANY($1)`, - [connectedAccountIds], - workspaceId, - transactionManager, - ); - } - - public async getAllByWorkspaceMemberId( - workspaceMemberId: string, - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity[] | undefined> { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - const connectedAccounts = - await this.workspaceDataSourceService.executeRawQuery( - `SELECT * FROM ${dataSourceSchema}."connectedAccount" WHERE "accountOwnerId" = $1`, - [workspaceMemberId], - workspaceId, - transactionManager, - ); - - return connectedAccounts; - } - - public async getAllByHandleAndWorkspaceMemberId( - handle: string, - workspaceMemberId: string, - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity[] | undefined> { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - const connectedAccounts = - await this.workspaceDataSourceService.executeRawQuery( - `SELECT * FROM ${dataSourceSchema}."connectedAccount" WHERE "handle" = $1 AND "accountOwnerId" = $2 LIMIT 1`, - [handle, workspaceMemberId], - workspaceId, - transactionManager, - ); - - return connectedAccounts; - } - - public async create( - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - | 'id' - | 'handle' - | 'provider' - | 'accessToken' - | 'refreshToken' - | 'accountOwnerId' - >, - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity> { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - return await this.workspaceDataSourceService.executeRawQuery( - `INSERT INTO ${dataSourceSchema}."connectedAccount" ("id", "handle", "provider", "accessToken", "refreshToken", "accountOwnerId") VALUES ($1, $2, $3, $4, $5, $6)`, - [ - connectedAccount.id, - connectedAccount.handle, - connectedAccount.provider, - connectedAccount.accessToken, - connectedAccount.refreshToken, - connectedAccount.accountOwnerId, - ], - workspaceId, - transactionManager, - ); - } - - public async updateAccessTokenAndRefreshToken( - accessToken: string, - refreshToken: string, - connectedAccountId: string, - workspaceId: string, - transactionManager?: EntityManager, - ) { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - await this.workspaceDataSourceService.executeRawQuery( - `UPDATE ${dataSourceSchema}."connectedAccount" SET "accessToken" = $1, "refreshToken" = $2, "authFailedAt" = NULL WHERE "id" = $3`, - [accessToken, refreshToken, connectedAccountId], - workspaceId, - transactionManager, - ); - } - - public async getById( - connectedAccountId: string, - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity | undefined> { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - const connectedAccounts = - await this.workspaceDataSourceService.executeRawQuery( - `SELECT * FROM ${dataSourceSchema}."connectedAccount" WHERE "id" = $1 LIMIT 1`, - [connectedAccountId], - workspaceId, - transactionManager, - ); - - return connectedAccounts[0]; - } - - public async getByIdOrFail( - connectedAccountId: string, - workspaceId: string, - transactionManager?: EntityManager, - ): Promise<ConnectedAccountWorkspaceEntity> { - const connectedAccount = await this.getById( - connectedAccountId, - workspaceId, - transactionManager, - ); - - if (!connectedAccount) { - throw new NotFoundException( - `Connected account with id ${connectedAccountId} not found in workspace ${workspaceId}`, - ); - } - - return connectedAccount; - } - - public async updateAccessToken( - accessToken: string, - connectedAccountId: string, - workspaceId: string, - transactionManager?: EntityManager, - ) { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - await this.workspaceDataSourceService.executeRawQuery( - `UPDATE ${dataSourceSchema}."connectedAccount" SET "accessToken" = $1, "authFailedAt" = NULL WHERE "id" = $2`, - [accessToken, connectedAccountId], - workspaceId, - transactionManager, - ); - } - - public async updateAuthFailedAt( - connectedAccountId: string, - workspaceId: string, - transactionManager?: EntityManager, - ) { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - await this.workspaceDataSourceService.executeRawQuery( - `UPDATE ${dataSourceSchema}."connectedAccount" SET "authFailedAt" = NOW() WHERE "id" = $1`, - [connectedAccountId], - workspaceId, - transactionManager, - ); - } - - public async getConnectedAccountOrThrow( - workspaceId: string, - connectedAccountId: string, - ): Promise<ConnectedAccountWorkspaceEntity> { - const connectedAccount = await this.getById( - connectedAccountId, - workspaceId, - ); - - if (!connectedAccount) { - throw new Error( - `Connected account ${connectedAccountId} not found in workspace ${workspaceId}`, - ); - } - - return connectedAccount; - } - - public async updateHandleAliases( - handleAliases: string[], - connectedAccountId: string, - workspaceId: string, - transactionManager?: EntityManager, - ) { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - await this.workspaceDataSourceService.executeRawQuery( - `UPDATE ${dataSourceSchema}."connectedAccount" SET "handleAliases" = $1 WHERE "id" = $2`, - // TODO: modify handleAliases to be of fieldmetadatatype array - [handleAliases.join(','), connectedAccountId], - workspaceId, - transactionManager, - ); - } -} diff --git a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts index f09bb74ea697..12363ed088b8 100644 --- a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts +++ b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts @@ -99,6 +99,16 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity { }) handleAliases: string; + @WorkspaceField({ + standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.scopes, + type: FieldMetadataType.ARRAY, + label: 'Scopes', + description: 'Scopes', + icon: 'IconSettings', + }) + @WorkspaceIsNullable() + scopes: string[] | null; + @WorkspaceRelation({ standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.accountOwner, type: RelationMetadataType.MANY_TO_ONE, diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts index c5b6e93c6d22..d9fd73740494 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { isDefined } from 'class-validator'; import chunk from 'lodash.chunk'; import compact from 'lodash.compact'; import { Any, EntityManager, Repository } from 'typeorm'; @@ -13,7 +12,6 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; -import { PERSON_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant'; @@ -54,13 +52,6 @@ export class CreateCompanyAndContactService { return []; } - const emailsFieldMetadata = await this.fieldMetadataRepository.findOne({ - where: { - workspaceId: workspaceId, - standardId: PERSON_STANDARD_FIELD_IDS.emails, - }, - }); - const personRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, @@ -89,16 +80,14 @@ export class CreateCompanyAndContactService { } const alreadyCreatedContacts = await personRepository.find({ - where: isDefined(emailsFieldMetadata) - ? { - emails: { primaryEmail: Any(uniqueHandles) }, - } - : { email: Any(uniqueHandles) }, + where: { + emails: { primaryEmail: Any(uniqueHandles) }, + }, }); - const alreadyCreatedContactEmails: string[] = isDefined(emailsFieldMetadata) - ? alreadyCreatedContacts?.map(({ emails }) => emails?.primaryEmail) - : alreadyCreatedContacts?.map(({ email }) => email); + const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.map( + ({ emails }) => emails?.primaryEmail, + ); const filteredContactsToCreate = uniqueContacts.filter( (participant) => @@ -143,11 +132,8 @@ export class CreateCompanyAndContactService { createdByWorkspaceMember: connectedAccount.accountOwner, })); - const shouldUseEmailsField = isDefined(emailsFieldMetadata); - return this.createContactService.createPeople( formattedContactsToCreate, - shouldUseEmailsField, workspaceId, transactionManager, ); diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-contact.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-contact.service.ts index cada68c10acc..400573dddafd 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-contact.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-contact.service.ts @@ -28,7 +28,6 @@ export class CreateContactService { private formatContacts( contactsToCreate: ContactToCreate[], lastPersonPosition: number, - shouldUseEmailsField: boolean, ): DeepPartial<PersonWorkspaceEntity>[] { return contactsToCreate.map((contact) => { const id = v4(); @@ -47,9 +46,7 @@ export class CreateContactService { return { id, - ...(shouldUseEmailsField - ? { emails: { primaryEmail: handle, additionalEmails: null } } - : { email: handle }), + emails: { primaryEmail: handle, additionalEmails: null }, name: { firstName, lastName, @@ -67,7 +64,6 @@ export class CreateContactService { public async createPeople( contactsToCreate: ContactToCreate[], - shouldUseEmailsField: boolean, workspaceId: string, transactionManager?: EntityManager, ): Promise<DeepPartial<PersonWorkspaceEntity>[]> { @@ -87,7 +83,6 @@ export class CreateContactService { const formattedContacts = this.formatContacts( contactsToCreate, lastPersonPosition, - shouldUseEmailsField, ); return personRepository.save( diff --git a/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts b/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts index 0aa6b7d3ae4d..db425cda4e09 100644 --- a/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts +++ b/packages/twenty-server/src/modules/favorite/standard-objects/favorite.workspace-entity.ts @@ -22,6 +22,8 @@ import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-obj import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; +import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; +import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -128,6 +130,48 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity { }) workflowId: string; + @WorkspaceRelation({ + standardId: FAVORITE_STANDARD_FIELD_IDS.workflowVersion, + type: RelationMetadataType.MANY_TO_ONE, + label: 'Workflow', + description: 'Favorite workflow version', + icon: 'IconSettingsAutomation', + inverseSideTarget: () => WorkflowVersionWorkspaceEntity, + inverseSideFieldKey: 'favorites', + }) + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + @WorkspaceIsNullable() + workflowVersion: Relation<WorkflowVersionWorkspaceEntity> | null; + + @WorkspaceJoinColumn('workflowVersion') + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + workflowVersionId: string; + + @WorkspaceRelation({ + standardId: FAVORITE_STANDARD_FIELD_IDS.workflowRun, + type: RelationMetadataType.MANY_TO_ONE, + label: 'Workflow', + description: 'Favorite workflow run', + icon: 'IconSettingsAutomation', + inverseSideTarget: () => WorkflowRunWorkspaceEntity, + inverseSideFieldKey: 'favorites', + }) + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + @WorkspaceIsNullable() + workflowRun: Relation<WorkflowRunWorkspaceEntity> | null; + + @WorkspaceJoinColumn('workflowRun') + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + workflowRunId: string; + @WorkspaceRelation({ standardId: FAVORITE_STANDARD_FIELD_IDS.task, type: RelationMetadataType.MANY_TO_ONE, diff --git a/packages/twenty-server/src/modules/mail-sender/exceptions/mail-sender.exception.ts b/packages/twenty-server/src/modules/mail-sender/exceptions/mail-sender.exception.ts new file mode 100644 index 000000000000..01e1b2ed932f --- /dev/null +++ b/packages/twenty-server/src/modules/mail-sender/exceptions/mail-sender.exception.ts @@ -0,0 +1,13 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class MailSenderException extends CustomException { + code: MailSenderExceptionCode; + constructor(message: string, code: MailSenderExceptionCode) { + super(message, code); + } +} + +export enum MailSenderExceptionCode { + PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED', + CONNECTED_ACCOUNT_NOT_FOUND = 'CONNECTED_ACCOUNT_NOT_FOUND', +} diff --git a/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts b/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts index 3a17c77cf92d..026b4537d898 100644 --- a/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts +++ b/packages/twenty-server/src/modules/mail-sender/workflow-actions/send-email.workflow-action.ts @@ -4,13 +4,24 @@ import { z } from 'zod'; import Handlebars from 'handlebars'; import { JSDOM } from 'jsdom'; import DOMPurify from 'dompurify'; -import { WorkflowActionEmail } from 'twenty-emails'; -import { render } from '@react-email/components'; import { WorkflowActionResult } from 'src/modules/workflow/workflow-executor/types/workflow-action-result.type'; import { WorkflowSendEmailStep } from 'src/modules/workflow/workflow-executor/types/workflow-action.type'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { EmailService } from 'src/engine/core-modules/email/email.service'; +import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { + WorkflowStepExecutorException, + WorkflowStepExecutorExceptionCode, +} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { + MailSenderException, + MailSenderExceptionCode, +} from 'src/modules/mail-sender/exceptions/mail-sender.exception'; +import { GmailClientProvider } from 'src/modules/messaging/message-import-manager/drivers/gmail/providers/gmail-client.provider'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { isDefined } from 'src/utils/is-defined'; @Injectable() export class SendEmailWorkflowAction { @@ -18,8 +29,48 @@ export class SendEmailWorkflowAction { constructor( private readonly environmentService: EnvironmentService, private readonly emailService: EmailService, + private readonly gmailClientProvider: GmailClientProvider, + private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} + private async getEmailClient(step: WorkflowSendEmailStep) { + const { workspaceId } = this.scopedWorkspaceContextFactory.create(); + + if (!workspaceId) { + throw new WorkflowStepExecutorException( + 'Scoped workspace not found', + WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, + ); + } + + const connectedAccountRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>( + workspaceId, + 'connectedAccount', + ); + const connectedAccount = await connectedAccountRepository.findOneBy({ + id: step.settings.connectedAccountId, + }); + + if (!isDefined(connectedAccount)) { + throw new MailSenderException( + `Connected Account '${step.settings.connectedAccountId}' not found`, + MailSenderExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND, + ); + } + + switch (connectedAccount.provider) { + case 'google': + return await this.gmailClientProvider.getGmailClient(connectedAccount); + default: + throw new MailSenderException( + `Provider ${connectedAccount.provider} is not supported`, + MailSenderExceptionCode.PROVIDER_NOT_SUPPORTED, + ); + } + } + async execute({ step, payload, @@ -30,6 +81,8 @@ export class SendEmailWorkflowAction { [key: string]: string; }; }): Promise<WorkflowActionResult> { + const emailProvider = await this.getEmailClient(step); + try { const emailSchema = z.string().trim().email('Invalid email'); @@ -41,34 +94,34 @@ export class SendEmailWorkflowAction { return { result: { success: false } }; } - const mainText = Handlebars.compile(step.settings.template)(payload); + const body = Handlebars.compile(step.settings.body)(payload); + const subject = Handlebars.compile(step.settings.subject)(payload); const window = new JSDOM('').window; const purify = DOMPurify(window); - const safeHTML = purify.sanitize(mainText || ''); + const safeBody = purify.sanitize(body || ''); + const safeSubject = purify.sanitize(subject || ''); - const email = WorkflowActionEmail({ - dangerousHTML: safeHTML, - title: step.settings.title, - callToAction: step.settings.callToAction, - }); - const html = render(email, { - pretty: true, - }); - const text = render(email, { - plainText: true, - }); + const message = [ + `To: ${payload.email}`, + `Subject: ${safeSubject || ''}`, + 'MIME-Version: 1.0', + 'Content-Type: text/plain; charset="UTF-8"', + '', + safeBody, + ].join('\n'); + + const encodedMessage = Buffer.from(message).toString('base64'); - await this.emailService.send({ - from: `${this.environmentService.get( - 'EMAIL_FROM_NAME', - )} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`, - to: payload.email, - subject: step.settings.subject || '', - text, - html, + await emailProvider.users.messages.send({ + userId: 'me', + requestBody: { + raw: encodedMessage, + }, }); + this.logger.log(`Email sent successfully`); + return { result: { success: true } }; } catch (error) { return { error }; diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts index 195a88ea4e3c..e0dc0dd6e2d3 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts @@ -7,7 +7,6 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; -import { PERSON_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; @@ -60,35 +59,19 @@ export class MatchParticipantService< ...new Set(participants.map((participant) => participant.handle)), ]; - const emailsFieldMetadata = await this.fieldMetadataRepository.findOne({ - where: { - workspaceId: workspaceId, - standardId: PERSON_STANDARD_FIELD_IDS.emails, - }, - }); - const personRepository = await this.twentyORMManager.getRepository<PersonWorkspaceEntity>( 'person', ); - const people = emailsFieldMetadata - ? await personRepository.find( - { - where: { - emails: Any(uniqueParticipantsHandles), - }, - }, - transactionManager, - ) - : await personRepository.find( - { - where: { - email: Any(uniqueParticipantsHandles), - }, - }, - transactionManager, - ); + const people = await personRepository.find( + { + where: { + emails: Any(uniqueParticipantsHandles), + }, + }, + transactionManager, + ); const workspaceMemberRepository = await this.twentyORMManager.getRepository<WorkspaceMemberWorkspaceEntity>( @@ -105,10 +88,8 @@ export class MatchParticipantService< ); for (const handle of uniqueParticipantsHandles) { - const person = people.find((person) => - emailsFieldMetadata - ? person.emails?.primaryEmail === handle - : person.email === handle, + const person = people.find( + (person) => person.emails?.primaryEmail === handle, ); const workspaceMember = workspaceMembers.find( diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts index 44141f3dde92..0e30225843ed 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts @@ -5,17 +5,13 @@ import { Any } from 'typeorm'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { isDefined } from 'src/utils/is-defined'; export class CanAccessMessageThreadService { constructor( - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, @InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) private readonly workspaceMemberRepository: WorkspaceMemberRepository, private readonly twentyORMManager: TwentyORMManager, @@ -31,6 +27,7 @@ export class CanAccessMessageThreadService { 'messageChannel', ); const messageChannels = await messageChannelRepository.find({ + select: ['id', 'visibility'], where: { id: Any( messageChannelMessageAssociations.map( @@ -52,20 +49,20 @@ export class CanAccessMessageThreadService { const currentWorkspaceMember = await this.workspaceMemberRepository.getByIdOrFail(userId, workspaceId); - const messageChannelsConnectedAccounts = - await this.connectedAccountRepository.getByIds( - messageChannels - .map((channel) => channel.connectedAccountId) - .filter(isDefined), - workspaceId, + const connectedAccountRepository = + await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>( + 'connectedAccount', ); - const messageChannelsWorkspaceMemberIds = - messageChannelsConnectedAccounts.map( - (connectedAccount) => connectedAccount.accountOwnerId, - ); + const connectedAccounts = await connectedAccountRepository.find({ + select: ['id'], + where: { + messageChannels: Any(messageChannels.map((channel) => channel.id)), + accountOwnerId: currentWorkspaceMember.id, + }, + }); - if (messageChannelsWorkspaceMemberIds.includes(currentWorkspaceMember.id)) { + if (connectedAccounts.length > 0) { return; } diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts index 9c84640768af..1222865f6962 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service'; import { MessageFindManyPreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook'; import { MessageFindOnePreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook'; @@ -9,10 +8,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta @Module({ imports: [ - ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, - WorkspaceMemberWorkspaceEntity, - ]), + ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), ], providers: [ CanAccessMessageThreadService, 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..970290c35841 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 @@ -146,6 +146,7 @@ export class MessageChannelSyncStatusService { syncStage: MessageChannelSyncStage.PARTIAL_MESSAGE_LIST_FETCH_PENDING, throttleFailureCount: 0, syncStageStartedAt: null, + syncedAt: new Date().toISOString(), }); } @@ -161,6 +162,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-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts index 8e837cce6a59..c5c3a033dedc 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts @@ -19,8 +19,8 @@ export class MessagingMessageCleanerConnectedAccountListener { private readonly messageQueueService: MessageQueueService, ) {} - @OnEvent('connectedAccount.deleted') - async handleDeletedEvent( + @OnEvent('connectedAccount.destroyed') + async handleDestroyedEvent( payload: WorkspaceEventBatch< ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity> >, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module.ts index 3fc555a87c96..c6ee8b72cff5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module.ts @@ -2,15 +2,14 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { EnvironmentModule } from 'src/engine/core-modules/environment/environment.module'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { EnvironmentModule } from 'src/engine/core-modules/environment/environment.module'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { EmailAliasManagerModule } from 'src/modules/connected-account/email-alias-manager/email-alias-manager.module'; import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; import { GmailClientProvider } from 'src/modules/messaging/message-import-manager/drivers/gmail/providers/gmail-client.provider'; import { GmailFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-fetch-by-batch.service'; @@ -26,10 +25,7 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p baseURL: 'https://www.googleapis.com/batch/gmail/v1', }), EnvironmentModule, - ObjectMetadataRepositoryModule.forFeature([ - ConnectedAccountWorkspaceEntity, - BlocklistWorkspaceEntity, - ]), + ObjectMetadataRepositoryModule.forFeature([BlocklistWorkspaceEntity]), MessagingCommonModule, TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), OAuth2ClientManagerModule, @@ -46,6 +42,10 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p GmailGetMessageListService, GmailHandleErrorService, ], - exports: [GmailGetMessagesService, GmailGetMessageListService], + exports: [ + GmailGetMessagesService, + GmailGetMessageListService, + GmailClientProvider, + ], }) export class MessagingGmailDriverModule {} diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts index cc66a1ff5dbc..67761ec5d780 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts @@ -3,16 +3,12 @@ import { Logger, Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; import { MessageChannelSyncStage, MessageChannelWorkspaceEntity, } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/message-import-exception-handler.service'; import { MessagingFullMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-full-message-list-fetch.service'; import { MessagingPartialMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service'; import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; @@ -32,11 +28,8 @@ export class MessagingMessageListFetchJob { constructor( private readonly messagingFullMessageListFetchService: MessagingFullMessageListFetchService, private readonly messagingPartialMessageListFetchService: MessagingPartialMessageListFetchService, - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, private readonly messagingTelemetryService: MessagingTelemetryService, private readonly twentyORMManager: TwentyORMManager, - private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, ) {} @Process(MessagingMessageListFetchJob.name) @@ -60,6 +53,7 @@ export class MessagingMessageListFetchJob { where: { id: messageChannelId, }, + relations: ['connectedAccount'], }); if (!messageChannel) { @@ -72,16 +66,6 @@ export class MessagingMessageListFetchJob { return; } - const connectedAccount = - await this.connectedAccountRepository.getByIdOrFail( - messageChannel.connectedAccountId, - workspaceId, - ); - - if (!messageChannel?.isSyncEnabled) { - return; - } - if ( isThrottled( messageChannel.syncStageStartedAt, @@ -100,20 +84,20 @@ export class MessagingMessageListFetchJob { await this.messagingTelemetryService.track({ eventName: 'partial_message_list_fetch.started', workspaceId, - connectedAccountId: connectedAccount.id, + connectedAccountId: messageChannel.connectedAccount.id, messageChannelId: messageChannel.id, }); await this.messagingPartialMessageListFetchService.processMessageListFetch( messageChannel, - connectedAccount, + messageChannel.connectedAccount, workspaceId, ); await this.messagingTelemetryService.track({ eventName: 'partial_message_list_fetch.completed', workspaceId, - connectedAccountId: connectedAccount.id, + connectedAccountId: messageChannel.connectedAccount.id, messageChannelId: messageChannel.id, }); @@ -121,26 +105,26 @@ export class MessagingMessageListFetchJob { case MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING: this.logger.log( - `Fetching full message list for workspace ${workspaceId} and account ${connectedAccount.id}`, + `Fetching full message list for workspace ${workspaceId} and account ${messageChannel.connectedAccount.id}`, ); await this.messagingTelemetryService.track({ eventName: 'full_message_list_fetch.started', workspaceId, - connectedAccountId: connectedAccount.id, + connectedAccountId: messageChannel.connectedAccount.id, messageChannelId: messageChannel.id, }); await this.messagingFullMessageListFetchService.processMessageListFetch( messageChannel, - connectedAccount, + messageChannel.connectedAccount, workspaceId, ); await this.messagingTelemetryService.track({ eventName: 'full_message_list_fetch.completed', workspaceId, - connectedAccountId: connectedAccount.id, + connectedAccountId: messageChannel.connectedAccount.id, messageChannelId: messageChannel.id, }); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts index a22e1b26a769..f062400d19bb 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts @@ -3,16 +3,12 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; import { MessageChannelSyncStage, MessageChannelWorkspaceEntity, } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/message-import-exception-handler.service'; import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; @@ -27,12 +23,9 @@ export type MessagingMessagesImportJobData = { }) export class MessagingMessagesImportJob { constructor( - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, private readonly messagingMessagesImportService: MessagingMessagesImportService, private readonly messagingTelemetryService: MessagingTelemetryService, private readonly twentyORMManager: TwentyORMManager, - private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, ) {} @Process(MessagingMessagesImportJob.name) @@ -56,6 +49,7 @@ export class MessagingMessagesImportJob { where: { id: messageChannelId, }, + relations: ['connectedAccount'], }); if (!messageChannel) { @@ -68,12 +62,6 @@ export class MessagingMessagesImportJob { return; } - const connectedAccount = - await this.connectedAccountRepository.getConnectedAccountOrThrow( - workspaceId, - messageChannel.connectedAccountId, - ); - if (!messageChannel?.isSyncEnabled) { return; } @@ -96,7 +84,7 @@ export class MessagingMessagesImportJob { await this.messagingMessagesImportService.processMessageBatchImport( messageChannel, - connectedAccount, + messageChannel.connectedAccount, workspaceId, ); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts index 513e2672adf4..80802c3fb2c4 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts @@ -19,8 +19,8 @@ export class MessagingMessageImportManagerMessageChannelListener { private readonly messageQueueService: MessageQueueService, ) {} - @OnEvent('messageChannel.deleted') - async handleDeletedEvent( + @OnEvent('messageChannel.destroyed') + async handleDestroyedEvent( payload: WorkspaceEventBatch< ObjectRecordDeleteEvent<MessageChannelWorkspaceEntity> >, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts index 6017441003ae..09c59c55758b 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts @@ -110,7 +110,6 @@ export class MessagingMessagesImportService { await this.emailAliasManagerService.refreshHandleAliases( connectedAccount, - workspaceId, ); messageIdsToFetch = await this.cacheStorage.setPop( 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/src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job.ts index 8529ebaef6f4..ee556d672d96 100644 --- a/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job.ts @@ -6,9 +6,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; @@ -30,8 +28,6 @@ export class MessagingCreateCompanyAndContactAfterSyncJob { ); constructor( private readonly createCompanyAndContactService: CreateCompanyAndContactService, - @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) - private readonly connectedAccountRepository: ConnectedAccountRepository, private readonly twentyORMManager: TwentyORMManager, ) {} @@ -63,10 +59,16 @@ export class MessagingCreateCompanyAndContactAfterSyncJob { return; } - const connectedAccount = await this.connectedAccountRepository.getById( - connectedAccountId, - workspaceId, - ); + const connectedAccountRepository = + await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>( + 'connectedAccount', + ); + + const connectedAccount = await connectedAccountRepository.findOne({ + where: { + id: connectedAccountId, + }, + }); if (!connectedAccount) { throw new Error( diff --git a/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-person.listener.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-person.listener.ts index 1d4a0c6c38aa..53bf3329a2eb 100644 --- a/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-person.listener.ts +++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/listeners/message-participant-person.listener.ts @@ -32,10 +32,7 @@ export class MessageParticipantPersonListener { >, ) { for (const eventPayload of payload.events) { - if ( - !eventPayload.properties.after.emails?.primaryEmail && - !eventPayload.properties.after.email - ) { + if (!eventPayload.properties.after.emails?.primaryEmail) { continue; } @@ -43,9 +40,7 @@ export class MessageParticipantPersonListener { MessageParticipantMatchParticipantJob.name, { workspaceId: payload.workspaceId, - email: - eventPayload.properties.after.emails?.primaryEmail ?? - eventPayload.properties.after.email, + email: eventPayload.properties.after.emails?.primaryEmail, personId: eventPayload.recordId, }, ); @@ -60,10 +55,6 @@ export class MessageParticipantPersonListener { ) { for (const eventPayload of payload.events) { if ( - objectRecordUpdateEventChangedProperties( - eventPayload.properties.before, - eventPayload.properties.after, - ).includes('email') || objectRecordUpdateEventChangedProperties( eventPayload.properties.before, eventPayload.properties.after, @@ -73,9 +64,7 @@ export class MessageParticipantPersonListener { MessageParticipantUnmatchParticipantJob.name, { workspaceId: payload.workspaceId, - email: - eventPayload.properties.before.emails?.primaryEmail ?? - eventPayload.properties.before.email, + email: eventPayload.properties.before.emails?.primaryEmail, personId: eventPayload.recordId, }, ); @@ -84,9 +73,7 @@ export class MessageParticipantPersonListener { MessageParticipantMatchParticipantJob.name, { workspaceId: payload.workspaceId, - email: - eventPayload.properties.after.emails?.primaryEmail ?? - eventPayload.properties.after.email, + email: eventPayload.properties.after.emails?.primaryEmail, personId: eventPayload.recordId, }, ); diff --git a/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts b/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts index 9a91699fb608..8553d7cb8909 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command.ts @@ -6,7 +6,7 @@ import { MessageQueueService } from 'src/engine/core-modules/message-queue/servi import { MESSAGING_MESSAGE_CHANNEL_SYNC_STATUS_MONITORING_CRON_PATTERN, MessagingMessageChannelSyncStatusMonitoringCronJob, -} from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron'; +} from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job'; @Command({ name: 'cron:messaging:monitoring:message-channel-sync-status', diff --git a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.ts b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts similarity index 66% rename from packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.ts rename to packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts index 8a03a5946dc6..04c1fe6affef 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts @@ -5,6 +5,7 @@ import snakeCase from 'lodash.snakecase'; import { Repository } from 'typeorm'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; +import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -30,13 +31,14 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { private readonly workspaceRepository: Repository<Workspace>, private readonly messagingTelemetryService: MessagingTelemetryService, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly exceptionHandlerService: ExceptionHandlerService, ) {} + @Process(MessagingMessageChannelSyncStatusMonitoringCronJob.name) @SentryCronMonitor( MessagingMessageChannelSyncStatusMonitoringCronJob.name, MESSAGING_MESSAGE_CHANNEL_SYNC_STATUS_MONITORING_CRON_PATTERN, ) - @Process(MessagingMessageChannelSyncStatusMonitoringCronJob.name) async handle(): Promise<void> { this.logger.log('Starting message channel sync status monitoring...'); @@ -54,27 +56,35 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { }); for (const activeWorkspace of activeWorkspaces) { - const messageChannelRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>( - activeWorkspace.id, - 'messageChannel', - ); - const messageChannels = await messageChannelRepository.find({ - select: ['id', 'syncStatus', 'connectedAccountId'], - }); + try { + const messageChannelRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>( + activeWorkspace.id, + 'messageChannel', + ); + const messageChannels = await messageChannelRepository.find({ + select: ['id', 'syncStatus', 'connectedAccountId'], + }); - for (const messageChannel of messageChannels) { - if (!messageChannel.syncStatus) { - continue; + for (const messageChannel of messageChannels) { + if (!messageChannel.syncStatus) { + continue; + } + await this.messagingTelemetryService.track({ + eventName: `message_channel.monitoring.sync_status.${snakeCase( + messageChannel.syncStatus, + )}`, + workspaceId: activeWorkspace.id, + connectedAccountId: messageChannel.connectedAccountId, + messageChannelId: messageChannel.id, + message: messageChannel.syncStatus, + }); } - await this.messagingTelemetryService.track({ - eventName: `message_channel.monitoring.sync_status.${snakeCase( - messageChannel.syncStatus, - )}`, - workspaceId: activeWorkspace.id, - connectedAccountId: messageChannel.connectedAccountId, - messageChannelId: messageChannel.id, - message: messageChannel.syncStatus, + } catch (error) { + this.exceptionHandlerService.captureExceptions([error], { + user: { + workspaceId: activeWorkspace.id, + }, }); } } diff --git a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts index 8173ff26d053..e0e77771222d 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts @@ -7,7 +7,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; import { MessagingMessageChannelSyncStatusMonitoringCronCommand } from 'src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command'; -import { MessagingMessageChannelSyncStatusMonitoringCronJob } from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron'; +import { MessagingMessageChannelSyncStatusMonitoringCronJob } from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job'; import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service'; @Module({ diff --git a/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts b/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts index efe5ae6b445e..d9455ed17bcd 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/services/messaging-telemetry.service.ts @@ -29,8 +29,8 @@ export class MessagingTelemetryService { }: MessagingTelemetryTrackInput): Promise<void> { await this.analyticsService.create( { - type: 'track', - data: { + action: 'monitoring', + payload: { eventName: `messaging.${eventName}`, workspaceId, userId, @@ -41,9 +41,6 @@ export class MessagingTelemetryService { }, userId, workspaceId, - '', // voluntarely not retrieving this - '', // to avoid slowing down - this.environmentService.get('SERVER_URL'), ); } } diff --git a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts index 142b000b9a0d..52f1a449f312 100644 --- a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts +++ b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts @@ -1,19 +1,21 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; +import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { ActorMetadata, FieldActorSource, } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { CurrencyMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { RelationMetadataType, RelationOnDeleteAction, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; import { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator'; import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; @@ -22,6 +24,7 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity'; @@ -31,6 +34,8 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; +const NAME_FIELD_NAME = 'name'; + @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.opportunity, namePlural: 'opportunities', @@ -91,7 +96,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity { ], defaultValue: "'NEW'", }) - @WorkspaceIndex() + @WorkspaceFieldIndex() stage: string; @WorkspaceField({ @@ -232,4 +237,20 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity { }) @WorkspaceIsDeprecated() probability: string; + + @WorkspaceField({ + standardId: OPPORTUNITY_STANDARD_FIELD_IDS.searchVector, + type: FieldMetadataType.TS_VECTOR, + label: SEARCH_VECTOR_FIELD.label, + description: SEARCH_VECTOR_FIELD.description, + icon: 'IconUser', + generatedType: 'STORED', + asExpression: getTsVectorColumnExpressionFromFields([ + { name: NAME_FIELD_NAME, type: FieldMetadataType.TEXT }, + ]), + }) + @WorkspaceIsNullable() + @WorkspaceIsSystem() + @WorkspaceFieldIndex({ indexType: IndexType.GIN }) + [SEARCH_VECTOR_FIELD.name]: any; } diff --git a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts index a39a401ca02d..32d9dd50f531 100644 --- a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts +++ b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts @@ -1,5 +1,6 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; +import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { ActorMetadata, FieldActorSource, @@ -7,21 +8,26 @@ import { import { EmailsMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type'; import { FullNameMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type'; import { LinksMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type'; +import { PhonesMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { RelationMetadataType, RelationOnDeleteAction, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; +import { WorkspaceIsUnique } from 'src/engine/twenty-orm/decorators/workspace-is-unique.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { PERSON_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; @@ -33,6 +39,10 @@ import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-obj import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; +const NAME_FIELD_NAME = 'name'; +const EMAILS_FIELD_NAME = 'emails'; +const JOB_TITLE_FIELD_NAME = 'jobTitle'; + @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.person, namePlural: 'people', @@ -52,17 +62,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { icon: 'IconUser', }) @WorkspaceIsNullable() - name: FullNameMetadata | null; - - @WorkspaceField({ - standardId: PERSON_STANDARD_FIELD_IDS.email, - type: FieldMetadataType.EMAIL, - label: 'Email', - description: 'Contactโ€™s Email', - icon: 'IconMail', - }) - @WorkspaceIsDeprecated() - email: string; + [NAME_FIELD_NAME]: FullNameMetadata | null; @WorkspaceField({ standardId: PERSON_STANDARD_FIELD_IDS.emails, @@ -71,7 +71,8 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { description: 'Contactโ€™s Emails', icon: 'IconMail', }) - emails: EmailsMetadata; + @WorkspaceIsUnique() + [EMAILS_FIELD_NAME]: EmailsMetadata; @WorkspaceField({ standardId: PERSON_STANDARD_FIELD_IDS.linkedinLink, @@ -100,7 +101,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { description: 'Contactโ€™s job title', icon: 'IconBriefcase', }) - jobTitle: string; + [JOB_TITLE_FIELD_NAME]: string; @WorkspaceField({ standardId: PERSON_STANDARD_FIELD_IDS.phone, @@ -109,8 +110,18 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { description: 'Contactโ€™s phone number', icon: 'IconPhone', }) + @WorkspaceIsDeprecated() phone: string; + @WorkspaceField({ + standardId: PERSON_STANDARD_FIELD_IDS.phones, + type: FieldMetadataType.PHONES, + label: 'Phones', + description: 'Contactโ€™s phone numbers', + icon: 'IconPhone', + }) + phones: PhonesMetadata; + @WorkspaceField({ standardId: PERSON_STANDARD_FIELD_IDS.city, type: FieldMetadataType.TEXT, @@ -279,4 +290,22 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsNullable() @WorkspaceIsSystem() timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>; + + @WorkspaceField({ + standardId: PERSON_STANDARD_FIELD_IDS.searchVector, + type: FieldMetadataType.TS_VECTOR, + label: SEARCH_VECTOR_FIELD.label, + description: SEARCH_VECTOR_FIELD.description, + icon: 'IconUser', + generatedType: 'STORED', + asExpression: getTsVectorColumnExpressionFromFields([ + { name: NAME_FIELD_NAME, type: FieldMetadataType.FULL_NAME }, + { name: EMAILS_FIELD_NAME, type: FieldMetadataType.EMAILS }, + { name: JOB_TITLE_FIELD_NAME, type: FieldMetadataType.TEXT }, + ]), + }) + @WorkspaceIsNullable() + @WorkspaceIsSystem() + @WorkspaceFieldIndex({ indexType: IndexType.GIN }) + [SEARCH_VECTOR_FIELD.name]: any; } diff --git a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts index d683c1c1f012..8d0eff81ac66 100644 --- a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts +++ b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts @@ -1,5 +1,6 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; @@ -7,6 +8,7 @@ import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-en import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator'; import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; @@ -19,6 +21,9 @@ import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.work import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; +import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; +import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @WorkspaceEntity({ @@ -182,6 +187,69 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceJoinColumn('task') taskId: string | null; + @WorkspaceRelation({ + standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.workflow, + type: RelationMetadataType.MANY_TO_ONE, + label: 'Workflow', + description: 'Event workflow', + icon: 'IconTargetArrow', + inverseSideTarget: () => WorkflowWorkspaceEntity, + inverseSideFieldKey: 'timelineActivities', + }) + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + @WorkspaceIsNullable() + workflow: Relation<WorkflowWorkspaceEntity> | null; + + @WorkspaceJoinColumn('workflow') + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + workflowId: string | null; + + @WorkspaceRelation({ + standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.workflowVersion, + type: RelationMetadataType.MANY_TO_ONE, + label: 'WorkflowVersion', + description: 'Event workflow version', + icon: 'IconTargetArrow', + inverseSideTarget: () => WorkflowVersionWorkspaceEntity, + inverseSideFieldKey: 'timelineActivities', + }) + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + @WorkspaceIsNullable() + workflowVersion: Relation<WorkflowVersionWorkspaceEntity> | null; + + @WorkspaceJoinColumn('workflowVersion') + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + workflowVersionId: string | null; + + @WorkspaceRelation({ + standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.workflowRun, + type: RelationMetadataType.MANY_TO_ONE, + label: 'Workflow Run', + description: 'Event workflow run', + icon: 'IconTargetArrow', + inverseSideTarget: () => WorkflowRunWorkspaceEntity, + inverseSideFieldKey: 'timelineActivities', + }) + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + @WorkspaceIsNullable() + workflowRun: Relation<WorkflowRunWorkspaceEntity> | null; + + @WorkspaceJoinColumn('workflowRun') + @WorkspaceGate({ + featureFlag: FeatureFlagKey.IsWorkflowEnabled, + }) + workflowRunId: string | null; + @WorkspaceDynamicRelation({ type: RelationMetadataType.MANY_TO_ONE, argsFactory: (oppositeObjectMetadata) => ({ diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts index e46ee8c0effd..3fb3d7c532ca 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts @@ -1,16 +1,16 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; -import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { VIEW_FIELD_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewField, @@ -22,6 +22,12 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- }) @WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() +/* +TODO: add soon once we've confirmed it's stabled +@WorkspaceIndex(['fieldMetadataId', 'viewId'], { + isUnique: true, + indexWhereClause: '"deletedAt" IS NULL', +})*/ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: VIEW_FIELD_STANDARD_FIELD_IDS.fieldMetadataId, diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts index 149171e6eb7f..3e45fee95d60 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-filter.workspace-entity.ts @@ -1,18 +1,18 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { VIEW_FILTER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; +import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { VIEW_FILTER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewFilter, diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts index 5abc9848ed7d..f4c65ba5c4de 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-sort.workspace-entity.ts @@ -1,18 +1,18 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { VIEW_SORT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; +import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; -import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; -import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { VIEW_SORT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.viewSort, @@ -24,6 +24,12 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- }) @WorkspaceIsNotAuditLogged() @WorkspaceIsSystem() +/* +TODO: add soon once we've confirmed it's stabled +@WorkspaceIndex(['fieldMetadataId', 'viewId'], { + isUnique: true, + indexWhereClause: '"deletedAt" IS NULL', +})*/ export class ViewSortWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: VIEW_SORT_STANDARD_FIELD_IDS.fieldMetadataId, diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts index 6d163d03c2e4..d59b593d2185 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts @@ -1,9 +1,16 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + import { WorkspaceQueryPostHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, @@ -17,10 +24,15 @@ import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-ob export class WorkflowCreateManyPostQueryHook implements WorkspaceQueryPostHookInstance { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMManager: TwentyORMManager, + private readonly workspaceEventEmitter: WorkspaceEventEmitter, + @InjectRepository(ObjectMetadataEntity, 'metadata') + private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, + ) {} async execute( - _authContext: AuthContext, + authContext: AuthContext, _objectName: string, payload: WorkflowWorkspaceEntity[], ): Promise<void> { @@ -29,14 +41,39 @@ export class WorkflowCreateManyPostQueryHook 'workflowVersion', ); + const workflowVersionsToCreate = payload.map((workflow) => { + return workflowVersionRepository.create({ + workflowId: workflow.id, + status: WorkflowVersionStatus.DRAFT, + name: 'v1', + }); + }); + await Promise.all( - payload.map((workflow) => { - return workflowVersionRepository.insert({ - workflowId: workflow.id, - status: WorkflowVersionStatus.DRAFT, - name: 'v1', - }); + workflowVersionsToCreate.map((workflowVersion) => { + return workflowVersionRepository.save(workflowVersion); + }), + ); + + const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ + where: { + nameSingular: 'workflowVersion', + }, + }); + + this.workspaceEventEmitter.emit( + `workflowVersion.created`, + workflowVersionsToCreate.map((workflowVersionToCreate) => { + return { + userId: authContext.user?.id, + recordId: workflowVersionToCreate.id, + objectMetadata, + properties: { + after: workflowVersionToCreate, + }, + } satisfies ObjectRecordCreateEvent<any>; }), + authContext.workspace.id, ); } } diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts index 78f7f98123be..b9447a0619cf 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts @@ -1,9 +1,16 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + import { WorkspaceQueryPostHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, @@ -17,10 +24,15 @@ import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-ob export class WorkflowCreateOnePostQueryHook implements WorkspaceQueryPostHookInstance { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMManager: TwentyORMManager, + private readonly workspaceEventEmitter: WorkspaceEventEmitter, + @InjectRepository(ObjectMetadataEntity, 'metadata') + private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, + ) {} async execute( - _authContext: AuthContext, + authContext: AuthContext, _objectName: string, payload: WorkflowWorkspaceEntity[], ): Promise<void> { @@ -31,10 +43,33 @@ export class WorkflowCreateOnePostQueryHook 'workflowVersion', ); - await workflowVersionRepository.insert({ + const workflowVersionToCreate = await workflowVersionRepository.create({ workflowId: workflow.id, status: WorkflowVersionStatus.DRAFT, name: 'v1', }); + + await workflowVersionRepository.save(workflowVersionToCreate); + + const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ + where: { + nameSingular: 'workflowVersion', + }, + }); + + this.workspaceEventEmitter.emit( + `workflowVersion.created`, + [ + { + userId: authContext.user?.id, + recordId: workflowVersionToCreate.id, + objectMetadata, + properties: { + after: workflowVersionToCreate, + }, + } satisfies ObjectRecordCreateEvent<any>, + ], + authContext.workspace.id, + ); } } diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts index bbcde310b9c3..870484d75b99 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts @@ -1,7 +1,11 @@ import { Module } from '@nestjs/common'; +import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; + +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkflowCreateManyPostQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook'; import { WorkflowCreateManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-many.pre-query.hook'; +import { WorkflowCreateOnePostQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook'; import { WorkflowCreateOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-one.pre-query.hook'; import { WorkflowRunCreateManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-run-create-many.pre-query.hook'; import { WorkflowRunCreateOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-run-create-one.pre-query.hook'; @@ -17,6 +21,9 @@ import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/work import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service'; @Module({ + imports: [ + NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'), + ], providers: [ WorkflowCreateOnePreQueryHook, WorkflowCreateManyPreQueryHook, @@ -30,6 +37,7 @@ import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/ WorkflowVersionUpdateManyPreQueryHook, WorkflowVersionDeleteOnePreQueryHook, WorkflowVersionDeleteManyPreQueryHook, + WorkflowCreateOnePostQueryHook, WorkflowCreateManyPostQueryHook, WorkflowVersionValidationWorkspaceService, WorkflowCommonWorkspaceService, diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity.ts index 7994c56a348c..402891c1d186 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity.ts @@ -34,7 +34,6 @@ export class WorkflowEventListenerWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.TEXT, label: 'Name', description: 'The workflow event listener name', - icon: 'IconPhoneCheck', }) eventName: string; diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts index c38cf4b5a485..aa14605f74a0 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts @@ -6,7 +6,10 @@ import { FieldActorSource, } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; @@ -17,6 +20,8 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { WORKFLOW_RUN_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; +import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; @@ -27,18 +32,39 @@ export enum WorkflowRunStatus { FAILED = 'FAILED', } +export type WorkflowRunOutput = { + steps: { + id: string; + name: string; + type: string; + attemptCount: number; + result: object | undefined; + error: string | undefined; + }[]; +}; + @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.workflowRun, namePlural: 'workflowRuns', - labelSingular: 'workflowRun', - labelPlural: 'WorkflowRuns', + labelSingular: 'Workflow Run', + labelPlural: 'Workflow Runs', description: 'A workflow run', + labelIdentifierStandardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.name, + icon: 'IconSettingsAutomation', }) @WorkspaceGate({ featureFlag: FeatureFlagKey.IsWorkflowEnabled, }) -@WorkspaceIsSystem() export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { + @WorkspaceField({ + standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.name, + type: FieldMetadataType.TEXT, + label: 'Name', + description: 'Name of the workflow run', + icon: 'IconSettingsAutomation', + }) + name: string; + @WorkspaceField({ standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.startedAt, type: FieldMetadataType.DATE_TIME, @@ -64,7 +90,7 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.SELECT, label: 'Workflow run status', description: 'Workflow run status', - icon: 'IconHistory', + icon: 'IconStatusChange', options: [ { value: WorkflowRunStatus.NOT_STARTED, @@ -108,6 +134,27 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { }) createdBy: ActorMetadata; + @WorkspaceField({ + standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.output, + type: FieldMetadataType.RAW_JSON, + label: 'Output', + description: 'Json object to provide output of the workflow run', + icon: 'IconText', + }) + @WorkspaceIsNullable() + output: WorkflowRunOutput | null; + + @WorkspaceField({ + standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.position, + type: FieldMetadataType.POSITION, + label: 'Position', + description: 'Workflow run position', + icon: 'IconHierarchy2', + }) + @WorkspaceIsSystem() + @WorkspaceIsNullable() + position: number | null; + // Relations @WorkspaceRelation({ standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.workflowVersion, @@ -136,4 +183,27 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceJoinColumn('workflow') workflowId: string; + + @WorkspaceRelation({ + standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.favorites, + type: RelationMetadataType.ONE_TO_MANY, + label: 'Favorites', + description: 'Favorites linked to the workflow run', + icon: 'IconHeart', + inverseSideTarget: () => FavoriteWorkspaceEntity, + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsSystem() + favorites: Relation<FavoriteWorkspaceEntity[]>; + + @WorkspaceRelation({ + standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.timelineActivities, + type: RelationMetadataType.ONE_TO_MANY, + label: 'Timeline Activities', + description: 'Timeline activities linked to the run', + inverseSideTarget: () => TimelineActivityWorkspaceEntity, + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsSystem() + timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>; } diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts index 80545fe7897d..8e39c789ad27 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts @@ -14,11 +14,10 @@ import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace- import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { - WORKFLOW_RUN_STANDARD_FIELD_IDS, - WORKFLOW_VERSION_STANDARD_FIELD_IDS, -} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { WORKFLOW_VERSION_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; +import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { WorkflowStep } from 'src/modules/workflow/workflow-executor/types/workflow-action.type'; @@ -61,23 +60,22 @@ const WorkflowVersionStatusOptions = [ @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.workflowVersion, namePlural: 'workflowVersions', - labelSingular: 'WorkflowVersion', - labelPlural: 'WorkflowVersions', + labelSingular: 'Workflow Version', + labelPlural: 'Workflow Versions', description: 'A workflow version', - icon: 'IconVersions', + icon: 'IconSettingsAutomation', labelIdentifierStandardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.name, }) @WorkspaceGate({ featureFlag: FeatureFlagKey.IsWorkflowEnabled, }) -@WorkspaceIsSystem() export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({ standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.name, type: FieldMetadataType.TEXT, label: 'Name', description: 'The workflow version name', - icon: 'IconVersions', + icon: 'IconSettingsAutomation', }) name: string; @@ -86,6 +84,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.RAW_JSON, label: 'Version trigger', description: 'Json object to provide trigger', + icon: 'IconSettingsAutomation', }) @WorkspaceIsNullable() trigger: WorkflowTrigger | null; @@ -95,6 +94,7 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.RAW_JSON, label: 'Version steps', description: 'Json object to provide steps', + icon: 'IconSettingsAutomation', }) @WorkspaceIsNullable() steps: WorkflowStep[] | null; @@ -104,11 +104,23 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.SELECT, label: 'Version status', description: 'The workflow version status', + icon: 'IconStatusChange', options: WorkflowVersionStatusOptions, defaultValue: "'DRAFT'", }) status: WorkflowVersionStatus; + @WorkspaceField({ + standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.position, + type: FieldMetadataType.POSITION, + label: 'Position', + description: 'Workflow version position', + icon: 'IconHierarchy2', + }) + @WorkspaceIsSystem() + @WorkspaceIsNullable() + position: number | null; + // Relations @WorkspaceRelation({ standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.workflow, @@ -126,14 +138,37 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { workflowId: string; @WorkspaceRelation({ - standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.workflowVersion, + standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.runs, type: RelationMetadataType.ONE_TO_MANY, label: 'Runs', description: 'Workflow runs linked to the version.', - icon: 'IconVersions', + icon: 'IconRun', inverseSideTarget: () => WorkflowRunWorkspaceEntity, onDelete: RelationOnDeleteAction.SET_NULL, }) @WorkspaceIsNullable() runs: Relation<WorkflowRunWorkspaceEntity>; + + @WorkspaceRelation({ + standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.favorites, + type: RelationMetadataType.ONE_TO_MANY, + label: 'Favorites', + description: 'Favorites linked to the workflow version', + icon: 'IconHeart', + inverseSideTarget: () => FavoriteWorkspaceEntity, + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsSystem() + favorites: Relation<FavoriteWorkspaceEntity[]>; + + @WorkspaceRelation({ + standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.timelineActivities, + type: RelationMetadataType.ONE_TO_MANY, + label: 'Timeline Activities', + description: 'Timeline activities linked to the version', + inverseSideTarget: () => TimelineActivityWorkspaceEntity, + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsSystem() + timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>; } diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts index 98dd40417e35..0ff94c93408d 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts @@ -16,6 +16,7 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re import { WORKFLOW_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; +import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; @@ -84,6 +85,7 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.MULTI_SELECT, label: 'Statuses', description: 'The current statuses of the workflow versions', + icon: 'IconStatusChange', options: WorkflowStatusOptions, }) @WorkspaceIsNullable() @@ -108,9 +110,8 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity { description: 'Workflow versions linked to the workflow.', icon: 'IconVersions', inverseSideTarget: () => WorkflowVersionWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, + onDelete: RelationOnDeleteAction.CASCADE, }) - @WorkspaceIsNullable() versions: Relation<WorkflowVersionWorkspaceEntity[]>; @WorkspaceRelation({ @@ -118,34 +119,43 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity { type: RelationMetadataType.ONE_TO_MANY, label: 'Runs', description: 'Workflow runs linked to the workflow.', - icon: 'IconVersions', + icon: 'IconRun', inverseSideTarget: () => WorkflowRunWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, + onDelete: RelationOnDeleteAction.CASCADE, }) - @WorkspaceIsNullable() - runs: Relation<WorkflowRunWorkspaceEntity>; + runs: Relation<WorkflowRunWorkspaceEntity[]>; @WorkspaceRelation({ standardId: WORKFLOW_STANDARD_FIELD_IDS.eventListeners, type: RelationMetadataType.ONE_TO_MANY, label: 'Event Listeners', description: 'Workflow event listeners linked to the workflow.', - icon: 'IconVersions', inverseSideTarget: () => WorkflowEventListenerWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, + onDelete: RelationOnDeleteAction.CASCADE, }) - @WorkspaceIsNullable() + @WorkspaceIsSystem() eventListeners: Relation<WorkflowEventListenerWorkspaceEntity[]>; @WorkspaceRelation({ standardId: WORKFLOW_STANDARD_FIELD_IDS.favorites, type: RelationMetadataType.ONE_TO_MANY, label: 'Favorites', - description: 'Favorites linked to the contact', + description: 'Favorites linked to the workflow', icon: 'IconHeart', inverseSideTarget: () => FavoriteWorkspaceEntity, onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsSystem() favorites: Relation<FavoriteWorkspaceEntity[]>; + + @WorkspaceRelation({ + standardId: WORKFLOW_STANDARD_FIELD_IDS.timelineActivities, + type: RelationMetadataType.ONE_TO_MANY, + label: 'Timeline Activities', + description: 'Timeline activities linked to the workflow', + inverseSideTarget: () => TimelineActivityWorkspaceEntity, + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsSystem() + timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>; } diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts index 1a9054ccc117..fda5d1298fc8 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { IsNull, Not } from 'typeorm'; + import { CreateOneResolverArgs, DeleteOneResolverArgs, @@ -11,12 +13,12 @@ import { WorkflowQueryValidationException, WorkflowQueryValidationExceptionCode, } from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception'; -import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/assert-workflow-version-is-draft.util'; -import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/assert-workflow-version-is-draft.util'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; @Injectable() export class WorkflowVersionValidationWorkspaceService { @@ -48,6 +50,8 @@ export class WorkflowVersionValidationWorkspaceService { where: { workflowId: payload.data.workflowId, status: WorkflowVersionStatus.DRAFT, + // FIXME: soft-deleted rows selection will have to be improved globally + deletedAt: IsNull(), }, }); @@ -84,5 +88,25 @@ export class WorkflowVersionValidationWorkspaceService { ); assertWorkflowVersionIsDraft(workflowVersion); + + const workflowVersionRepository = + await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>( + 'workflowVersion', + ); + + const otherWorkflowVersionsExist = await workflowVersionRepository.exists({ + where: { + workflowId: workflowVersion.workflowId, + deletedAt: IsNull(), + id: Not(workflowVersion.id), + }, + }); + + if (!otherWorkflowVersionsExist) { + throw new WorkflowQueryValidationException( + 'The initial version of a workflow can not be deleted', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + ); + } } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts index 99b334d0f5f5..bb8f8351faab 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-step-settings.type.ts @@ -14,11 +14,7 @@ export type WorkflowCodeStepSettings = BaseWorkflowStepSettings & { }; export type WorkflowSendEmailStepSettings = BaseWorkflowStepSettings & { + connectedAccountId: string; subject?: string; - template?: string; - title?: string; - callToAction?: { - value: string; - href: string; - }; + body?: string; }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts index 0f68b2917c89..24ae66fd7f11 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts @@ -7,9 +7,14 @@ import { CodeWorkflowAction } from 'src/modules/serverless/workflow-actions/code import { SendEmailWorkflowAction } from 'src/modules/mail-sender/workflow-actions/send-email.workflow-action'; import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { MessagingGmailDriverModule } from 'src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module'; @Module({ - imports: [WorkflowCommonModule, ServerlessFunctionModule], + imports: [ + WorkflowCommonModule, + ServerlessFunctionModule, + MessagingGmailDriverModule, + ], providers: [ WorkflowExecutorWorkspaceService, ScopedWorkspaceContextFactory, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts index 593543047e06..c50684f876c6 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts @@ -1,17 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { WorkflowStep } from 'src/modules/workflow/workflow-executor/types/workflow-action.type'; import { - WorkflowExecutorException, - WorkflowExecutorExceptionCode, -} from 'src/modules/workflow/workflow-executor/exceptions/workflow-executor.exception'; + WorkflowRunOutput, + WorkflowRunStatus, +} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory'; +import { WorkflowStep } from 'src/modules/workflow/workflow-executor/types/workflow-action.type'; const MAX_RETRIES_ON_FAILURE = 3; -export type WorkflowExecutionOutput = { - result?: object; - error?: object; +export type WorkflowExecutorOutput = { + steps: WorkflowRunOutput['steps']; + status: WorkflowRunStatus; }; @Injectable() @@ -22,17 +22,17 @@ export class WorkflowExecutorWorkspaceService { currentStepIndex, steps, payload, + output, attemptCount = 1, }: { currentStepIndex: number; steps: WorkflowStep[]; + output: WorkflowExecutorOutput; payload?: object; attemptCount?: number; - }): Promise<WorkflowExecutionOutput> { + }): Promise<WorkflowExecutorOutput> { if (currentStepIndex >= steps.length) { - return { - result: payload, - }; + return { ...output, status: WorkflowRunStatus.COMPLETED }; } const step = steps[currentStepIndex]; @@ -44,19 +44,47 @@ export class WorkflowExecutorWorkspaceService { payload, }); + const baseStepOutput = { + id: step.id, + name: step.name, + type: step.type, + attemptCount, + }; + + const updatedOutput = { + ...output, + steps: [ + ...output.steps, + { + ...baseStepOutput, + result: result.result, + error: result.error?.errorMessage, + }, + ], + }; + if (result.result) { return await this.execute({ currentStepIndex: currentStepIndex + 1, steps, payload: result.result, + output: updatedOutput, }); } if (!result.error) { - throw new WorkflowExecutorException( - 'Execution result error, no data or error', - WorkflowExecutorExceptionCode.WORKFLOW_FAILED, - ); + return { + ...output, + steps: [ + ...output.steps, + { + ...baseStepOutput, + result: undefined, + error: 'Execution result error, no data or error', + }, + ], + status: WorkflowRunStatus.FAILED, + }; } if (step.settings.errorHandlingOptions.continueOnFailure.value) { @@ -64,6 +92,7 @@ export class WorkflowExecutorWorkspaceService { currentStepIndex: currentStepIndex + 1, steps, payload, + output: updatedOutput, }); } @@ -75,13 +104,11 @@ export class WorkflowExecutorWorkspaceService { currentStepIndex, steps, payload, + output: updatedOutput, attemptCount: attemptCount + 1, }); } - throw new WorkflowExecutorException( - `Workflow failed: ${result.error}`, - WorkflowExecutorExceptionCode.WORKFLOW_FAILED, - ); + return { ...updatedOutput, status: WorkflowRunStatus.FAILED }; } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts index 0ca2a3107a6e..5a79462a355c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts @@ -3,8 +3,8 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service'; import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workspace-services/workflow-run.workspace-service'; @@ -36,24 +36,23 @@ export class RunWorkflowJob { workflowVersionId, ); - try { + const { steps, status } = await this.workflowExecutorWorkspaceService.execute({ currentStepIndex: 0, steps: workflowVersion.steps || [], payload, + output: { + steps: [], + status: WorkflowRunStatus.RUNNING, + }, }); - await this.workflowRunWorkspaceService.endWorkflowRun( - workflowRunId, - WorkflowRunStatus.COMPLETED, - ); - } catch (error) { - await this.workflowRunWorkspaceService.endWorkflowRun( - workflowRunId, - WorkflowRunStatus.FAILED, - ); - - throw error; - } + await this.workflowRunWorkspaceService.endWorkflowRun( + workflowRunId, + status, + { + steps, + }, + ); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-run.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-run.workspace-service.ts index 00162c595f96..2f3aca25543c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-run.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-run.workspace-service.ts @@ -2,11 +2,12 @@ import { Injectable } from '@nestjs/common'; import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { + WorkflowRunOutput, WorkflowRunStatus, WorkflowRunWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowRunException, WorkflowRunExceptionCode, @@ -70,7 +71,11 @@ export class WorkflowRunWorkspaceService { }); } - async endWorkflowRun(workflowRunId: string, status: WorkflowRunStatus) { + async endWorkflowRun( + workflowRunId: string, + status: WorkflowRunStatus, + output: WorkflowRunOutput, + ) { const workflowRunRepository = await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>( 'workflowRun', @@ -96,6 +101,7 @@ export class WorkflowRunWorkspaceService { return workflowRunRepository.update(workflowRunToUpdate.id, { status, + output, endedAt: new Date().toISOString(), }); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts index e5b62d59afc6..5ea3f82d4781 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/database-event-trigger/listeners/database-event-trigger.listener.ts @@ -1,11 +1,12 @@ import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; -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'; 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 { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; +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'; 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'; @@ -49,11 +50,19 @@ export class DatabaseEventTriggerListener { await this.handleEvent(payload); } + @OnEvent('*.destroyed') + async handleObjectRecordDestroyEvent( + payload: WorkspaceEventBatch<ObjectRecordDestroyEvent<any>>, + ) { + await this.handleEvent(payload); + } + private async handleEvent( payload: WorkspaceEventBatch< | ObjectRecordCreateEvent<any> | ObjectRecordUpdateEvent<any> | ObjectRecordDeleteEvent<any> + | ObjectRecordDestroyEvent<any> >, ) { const workspaceId = payload.workspaceId; diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index a53b02163482..309f3d158772 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -203,6 +203,10 @@ export class WorkflowTriggerWorkspaceService { workflowVersionNullable, ); + if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { + return; + } + await this.setDeactivatedVersionStatus( workflowVersion, workflowVersionRepository, diff --git a/packages/twenty-server/src/utils/typed-reflect.ts b/packages/twenty-server/src/utils/typed-reflect.ts index 5abec76b8401..0ee6b0604b62 100644 --- a/packages/twenty-server/src/utils/typed-reflect.ts +++ b/packages/twenty-server/src/utils/typed-reflect.ts @@ -9,6 +9,7 @@ export interface ReflectMetadataTypeMap { ['workspace:is-audit-logged-metadata-args']: false; ['workspace:is-primary-field-metadata-args']: true; ['workspace:is-deprecated-field-metadata-args']: true; + ['workspace:is-unique-metadata-args']: true; } export class TypedReflect { diff --git a/packages/twenty-server/test/people.integration-spec.ts b/packages/twenty-server/test/people.integration-spec.ts index b96bb670085c..28b981e22dbe 100644 --- a/packages/twenty-server/test/people.integration-spec.ts +++ b/packages/twenty-server/test/people.integration-spec.ts @@ -11,7 +11,10 @@ describe('peopleResolver (integration)', () => { edges { node { jobTitle - phone + phones { + primaryPhoneNumber + primaryPhoneCountryCode + } city avatarUrl position @@ -21,8 +24,10 @@ describe('peopleResolver (integration)', () => { deletedAt companyId intro - whatsapp - workPrefereance + whatsapp { + primaryPhoneNumber + } + workPreference performanceRating } } @@ -37,6 +42,7 @@ describe('peopleResolver (integration)', () => { .send(queryData) .expect(200) .expect((res) => { + console.log(res.body); expect(res.body.data).toBeDefined(); expect(res.body.errors).toBeUndefined(); }) @@ -52,7 +58,7 @@ describe('peopleResolver (integration)', () => { const people = edges[0].node; expect(people).toHaveProperty('jobTitle'); - expect(people).toHaveProperty('phone'); + expect(people).toHaveProperty('phones'); expect(people).toHaveProperty('city'); expect(people).toHaveProperty('avatarUrl'); expect(people).toHaveProperty('position'); @@ -63,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 132d5b99edaf..70a25e04f37e 100644 --- a/packages/twenty-ui/package.json +++ b/packages/twenty-ui/package.json @@ -1,6 +1,6 @@ { "name": "twenty-ui", - "version": "0.24.2", + "version": "0.32.0-canary", "type": "module", "main": "./src/index.ts", "exports": { diff --git a/packages/twenty-ui/src/display/avatar/components/Avatar.tsx b/packages/twenty-ui/src/display/avatar/components/Avatar.tsx index 81c3a80219ae..5230e67ce07b 100644 --- a/packages/twenty-ui/src/display/avatar/components/Avatar.tsx +++ b/packages/twenty-ui/src/display/avatar/components/Avatar.tsx @@ -7,6 +7,7 @@ import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isI import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPropertiesBySize'; import { AvatarSize } from '@ui/display/avatar/types/AvatarSize'; import { AvatarType } from '@ui/display/avatar/types/AvatarType'; +import { IconComponent } from '@ui/display/icon/types/IconComponent'; import { ThemeContext } from '@ui/theme'; import { Nullable, getImageAbsoluteURI, stringToHslColor } from '@ui/utilities'; @@ -17,13 +18,16 @@ const StyledAvatar = styled.div<{ color: string; backgroundColor: string; backgroundTransparentLight: string; + type?: Nullable<AvatarType>; }>` align-items: center; flex-shrink: 0; overflow: hidden; user-select: none; - border-radius: ${({ rounded }) => (rounded ? '50%' : '2px')}; + border-radius: ${({ rounded, type }) => { + return rounded ? '50%' : type === 'icon' ? '4px' : '2px'; + }}; display: flex; font-size: ${({ size }) => AVATAR_PROPERTIES_BY_SIZE[size].fontSize}; height: ${({ size }) => AVATAR_PROPERTIES_BY_SIZE[size].width}; @@ -51,6 +55,8 @@ export type AvatarProps = { size?: AvatarSize; placeholder: string | undefined; placeholderColorSeed?: string; + Icon?: IconComponent; + iconColor?: string; type?: Nullable<AvatarType>; color?: string; backgroundColor?: string; @@ -63,6 +69,8 @@ export const Avatar = ({ size = 'md', placeholder, placeholderColorSeed = placeholder, + Icon, + iconColor, onClick, type = 'squared', color, @@ -101,14 +109,26 @@ export const Avatar = ({ return ( <StyledAvatar size={size} - backgroundColor={showBackgroundColor ? fixedBackgroundColor : 'none'} + backgroundColor={ + Icon + ? theme.background.tertiary + : showBackgroundColor + ? fixedBackgroundColor + : 'none' + } color={fixedColor} clickable={!isUndefined(onClick)} rounded={type === 'rounded'} + type={type} onClick={onClick} backgroundTransparentLight={theme.background.transparent.light} > - {showPlaceholder ? ( + {Icon ? ( + <Icon + color={iconColor ? iconColor : 'currentColor'} + size={theme.icon.size.xl} + /> + ) : showPlaceholder ? ( placeholderChar ) : ( <StyledImage src={avatarImageURI} onError={handleImageError} alt="" /> diff --git a/packages/twenty-ui/src/display/avatar/types/AvatarType.ts b/packages/twenty-ui/src/display/avatar/types/AvatarType.ts index 9e9b7dc9589e..e267555111db 100644 --- a/packages/twenty-ui/src/display/avatar/types/AvatarType.ts +++ b/packages/twenty-ui/src/display/avatar/types/AvatarType.ts @@ -1 +1 @@ -export type AvatarType = 'squared' | 'rounded'; +export type AvatarType = 'squared' | 'rounded' | 'icon'; diff --git a/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx b/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx index 2c993ba652d1..72b0253eed81 100644 --- a/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx +++ b/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx @@ -14,6 +14,7 @@ export type AvatarChipProps = { avatarType?: Nullable<AvatarType>; variant?: AvatarChipVariant; LeftIcon?: IconComponent; + LeftIconColor?: string; isIconInverted?: boolean; className?: string; placeholderColorSeed?: string; @@ -41,6 +42,7 @@ export const AvatarChip = ({ avatarType = 'rounded', variant = AvatarChipVariant.Regular, LeftIcon, + LeftIconColor, isIconInverted, className, placeholderColorSeed, @@ -71,7 +73,11 @@ export const AvatarChip = ({ /> </StyledInvertedIconContainer> ) : ( - <LeftIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} /> + <LeftIcon + size={theme.icon.size.md} + stroke={theme.icon.stroke.sm} + color={LeftIconColor || 'currentColor'} + /> ) ) : ( <Avatar diff --git a/packages/twenty-ui/src/display/chip/components/Chip.tsx b/packages/twenty-ui/src/display/chip/components/Chip.tsx index 02d7e2e61309..48795fd42551 100644 --- a/packages/twenty-ui/src/display/chip/components/Chip.tsx +++ b/packages/twenty-ui/src/display/chip/components/Chip.tsx @@ -111,6 +111,10 @@ const StyledContainer = withTheme(styled.div< border-radius: ${({ theme, variant }) => variant === ChipVariant.Rounded ? '50px' : theme.border.radius.sm}; + + & > svg { + flex-shrink: 0; + } `); export const Chip = ({ @@ -123,6 +127,7 @@ export const Chip = ({ rightComponent, accent = ChipAccent.TextPrimary, onClick, + className, }: ChipProps) => { return ( <StyledContainer @@ -133,6 +138,7 @@ export const Chip = ({ size={size} variant={variant} onClick={onClick} + className={className} > {leftComponent} <OverflowingTextWithTooltip diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 190275cf9fcc..ffef2fdeda79 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -48,6 +48,7 @@ export { IconCircleX, IconClick, IconClockHour8, + IconClockShare, IconCode, IconCoins, IconColorSwatch, @@ -56,19 +57,56 @@ export { IconCreativeCommonsSa, IconCreditCard, IconCsv, + IconCurrencyAfghani, + IconCurrencyBahraini, IconCurrencyBaht, + IconCurrencyDinar, IconCurrencyDirham, IconCurrencyDollar, + IconCurrencyDollarAustralian, + IconCurrencyDollarBrunei, + IconCurrencyDollarCanadian, + IconCurrencyDollarGuyanese, + IconCurrencyDollarSingapore, + IconCurrencyDong, + IconCurrencyDram, IconCurrencyEuro, + IconCurrencyFlorin, + IconCurrencyForint, IconCurrencyFrank, + IconCurrencyGuarani, + IconCurrencyHryvnia, + IconCurrencyIranianRial, + IconCurrencyKip, IconCurrencyKroneCzech, + IconCurrencyKroneDanish, IconCurrencyKroneSwedish, + IconCurrencyLari, + IconCurrencyLeu, + IconCurrencyLira, + IconCurrencyLyd, + IconCurrencyManat, + IconCurrencyNaira, + IconCurrencyPaanga, + IconCurrencyPeso, IconCurrencyPound, + IconCurrencyQuetzal, IconCurrencyReal, + IconCurrencyRenminbi, IconCurrencyRiyal, + IconCurrencyRubel, + IconCurrencyRufiyaa, + IconCurrencyRupee, + IconCurrencyRupeeNepalese, + IconCurrencyShekel, + IconCurrencySom, + IconCurrencyTaka, + IconCurrencyTenge, + IconCurrencyTugrik, IconCurrencyWon, IconCurrencyYen, IconCurrencyYuan, + IconCurrencyZloty, IconDatabase, IconDeviceFloppy, IconDoorEnter, @@ -134,11 +172,15 @@ export { IconPencil, IconPhone, IconPhoto, + IconPhotoUp, IconPilcrow, IconPlayerPlay, + IconPlayerStop, + IconPlaylistAdd, IconPlaystationSquare, IconPlug, IconPlus, + IconPower, IconPresentation, IconProgressCheck, IconPuzzle, @@ -150,6 +192,7 @@ export { IconReload, IconRepeat, IconRestore, + IconRobot, IconRocket, IconRotate, IconRotate2, @@ -160,6 +203,7 @@ export { IconSortDescending, IconSparkles, IconSql, + IconSquareKey, IconSquareRoundedCheck, IconTable, IconTag, @@ -169,6 +213,7 @@ export { IconTestPipe, IconTextSize, IconTimelineEvent, + IconTool, IconTrash, IconUnlink, IconUpload, @@ -179,7 +224,6 @@ export { IconWand, IconWorld, IconX, - IconPlaylistAdd, } from '@tabler/icons-react'; export type { TablerIconsProps } from '@tabler/icons-react'; diff --git a/packages/twenty-ui/src/display/icon/hooks/useIcons.ts b/packages/twenty-ui/src/display/icon/hooks/useIcons.ts index c07d0a38ac82..981fae839cbc 100644 --- a/packages/twenty-ui/src/display/icon/hooks/useIcons.ts +++ b/packages/twenty-ui/src/display/icon/hooks/useIcons.ts @@ -12,7 +12,10 @@ export const useIcons = () => { }; const getIcon = (iconKey?: string | null) => { - if (!iconKey) return defaultIcon; + if (!iconKey) { + return defaultIcon; + } + return icons[iconKey] ?? defaultIcon; }; diff --git a/packages/twenty-ui/src/display/tooltip/OverflowingTextWithTooltip.tsx b/packages/twenty-ui/src/display/tooltip/OverflowingTextWithTooltip.tsx index c73cd0fda992..7f8df1450282 100644 --- a/packages/twenty-ui/src/display/tooltip/OverflowingTextWithTooltip.tsx +++ b/packages/twenty-ui/src/display/tooltip/OverflowingTextWithTooltip.tsx @@ -1,6 +1,6 @@ +import { styled } from '@linaria/react'; import { useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { styled } from '@linaria/react'; import { THEME_COMMON } from '@ui/theme'; diff --git a/packages/twenty-website/package.json b/packages/twenty-website/package.json index 3219867baf33..9f5104a77697 100644 --- a/packages/twenty-website/package.json +++ b/packages/twenty-website/package.json @@ -1,6 +1,6 @@ { "name": "twenty-website", - "version": "0.24.2", + "version": "0.32.0-canary", "private": true, "scripts": { "nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js", @@ -14,6 +14,9 @@ "database:generate:pg": "npx drizzle-kit generate:pg --config=src/database/drizzle-posgres.config.ts" }, "dependencies": { + "@docsearch/react": "^3.6.2", + "@nivo/calendar": "^0.87.0", + "gray-matter": "^4.0.3", "next-runtime-env": "^3.2.2", "postgres": "^3.4.3" } diff --git a/packages/twenty-website/public/images/readme/Github Read-me banner.png b/packages/twenty-website/public/images/readme/Github Read-me banner.png new file mode 100644 index 000000000000..2b87a59ccc44 Binary files /dev/null and b/packages/twenty-website/public/images/readme/Github Read-me banner.png differ diff --git a/packages/twenty-website/public/images/releases/0.31/0.31-advanced-settings.png b/packages/twenty-website/public/images/releases/0.31/0.31-advanced-settings.png new file mode 100644 index 000000000000..d6f094b5b9cd Binary files /dev/null and b/packages/twenty-website/public/images/releases/0.31/0.31-advanced-settings.png differ diff --git a/packages/twenty-website/public/images/releases/0.31/0.31-search.png b/packages/twenty-website/public/images/releases/0.31/0.31-search.png new file mode 100644 index 000000000000..2d1d6882a9f0 Binary files /dev/null and b/packages/twenty-website/public/images/releases/0.31/0.31-search.png differ diff --git a/packages/twenty-website/src/app/_components/ui/layout/articles/ArticleContent.tsx b/packages/twenty-website/src/app/_components/ui/layout/articles/ArticleContent.tsx index eb87fcc7600b..c337be0bd1c6 100644 --- a/packages/twenty-website/src/app/_components/ui/layout/articles/ArticleContent.tsx +++ b/packages/twenty-website/src/app/_components/ui/layout/articles/ArticleContent.tsx @@ -1,7 +1,7 @@ 'use client'; -import { ReactNode } from 'react'; import styled from '@emotion/styled'; +import { ReactNode } from 'react'; import { Theme } from '@/app/_components/ui/theme/theme'; import { wrapHeadingsWithAnchor } from '@/shared-utils/wrapHeadingsWithAnchor'; @@ -17,6 +17,21 @@ const StyledContent = styled.div` max-width: 100%; line-height: 1.8; color: black; + padding: 4px; + border-radius: 4px; + background: #1414140a; + } + + pre { + background: #1414140a; + padding: 4px; + border-radius: 4px; + + code { + padding: 0; + border-radius: 0; + background: none; + } } p { diff --git a/packages/twenty-website/src/app/layout.css b/packages/twenty-website/src/app/layout.css index 1869c0e7b04b..acff6bd6012c 100644 --- a/packages/twenty-website/src/app/layout.css +++ b/packages/twenty-website/src/app/layout.css @@ -124,9 +124,3 @@ strong, font-weight: 500; text-decoration: none; } - -code { - background: #1414140a; - padding: 4px; - border-radius: 4px; - } \ No newline at end of file diff --git a/packages/twenty-website/src/content/developers/frontend-development/frontend-commands.mdx b/packages/twenty-website/src/content/developers/frontend-development/frontend-commands.mdx index 56dc4e718a37..eb1b02c2dec8 100644 --- a/packages/twenty-website/src/content/developers/frontend-development/frontend-commands.mdx +++ b/packages/twenty-website/src/content/developers/frontend-development/frontend-commands.mdx @@ -68,7 +68,7 @@ To avoid unnecessary [re-renders](/contributor/frontend/best-practices#managing- [Recoil](https://recoiljs.org/docs/introduction/core-concepts) handles state management. -See [best practices](/contributor/frontend/best-practices#state-management) for more information on state management. +See [best practices](/developers/section/frontend-development/best-practices-front#state-management) for more information on state management. ## Testing @@ -78,4 +78,4 @@ Jest is mainly for testing utility functions, and not components themselves. Storybook is for testing the behavior of isolated components, as well as displaying the design system. -<ArticleEditContent></ArticleEditContent> \ No newline at end of file +<ArticleEditContent></ArticleEditContent> diff --git a/packages/twenty-website/src/content/developers/local-setup.mdx b/packages/twenty-website/src/content/developers/local-setup.mdx index 874edf3c2e32..6bae7137c223 100644 --- a/packages/twenty-website/src/content/developers/local-setup.mdx +++ b/packages/twenty-website/src/content/developers/local-setup.mdx @@ -122,7 +122,9 @@ You can access the database at [localhost:5432](localhost:5432), with user `twen ``` </ArticleTab> <ArticleTab> - <b>Option 1 :</b> To provision your database locally: + All the following steps are to be run in the WSL terminal (within your virtual machine) + + <b>Option 1:</b> To provision your database locally: ```bash make postgres-on-linux ``` @@ -138,19 +140,52 @@ You can access the database at [localhost:5432](localhost:5432), with user `twen </ArticleTab> </ArticleTabs> +## Step 4: Set up a Redis Database (cache) +Twenty requires a redis cache to provide the best performances + +<ArticleTabs label1="Linux" label2="Mac OS" label3="Windows (WSL)"> + <ArticleTab> + <b>Option 1:</b> To provision your Redis locally: + Use the following link to install Redis on your Linux machine: [Redis Installation](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/) + + <b>Option 2:</b> If you have docker installed: + ```bash + docker run -d --name my-redis-stack -p 6379:6379 redis/redis-stack-server:latest + ``` + </ArticleTab> + <ArticleTab> + <b>Option 1:</b>To provision your Redis locally with `brew`: + ```bash + brew install redis + ``` + + <b>Option 2:</b> If you have docker installed: + ```bash + docker run -d --name my-redis-stack -p 6379:6379 redis/redis-stack-server:latest + ``` + </ArticleTab> + <ArticleTab> + <b>Option 1:</b> To provision your Redis locally: + Use the following link to install Redis on your Linux virtual machine: [Redis Installation](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/) + + <b>Option 2:</b> If you have docker installed: + ```bash + docker run -d --name my-redis-stack -p 6379:6379 redis/redis-stack-server:latest + ``` + </ArticleTab> +</ArticleTabs> -## Step 4: Setup environment variables +## Step 5: Setup environment variables Use environment variables or `.env` files to configure your project. Copy the `.env.example` files in `/front` and `/server`: ```bash cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env - cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env ``` -## Step 5: Installing dependencies +## Step 6: Installing dependencies <ArticleWarning> @@ -161,13 +196,29 @@ Use `nvm` to install the correct `node` version. The `.nvmrc` ensures all contri To build Twenty server and seed some data into your database, run the following commands: ```bash nvm install # installs recommended node version - nvm use # use recommended node version - yarn ``` -## Step 6: Running the project +## Step 7: Running the project + +Start your redis server: +<ArticleTabs label1="Linux" label2="Mac OS" label3="Windows (WSL)"> + <ArticleTab> + 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. + </ArticleTab> + <ArticleTab> + ```bash + brew services start redis + ``` + </ArticleTab> + <ArticleTab> + 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. + </ArticleTab> +</ArticleTabs> + Setup your database with the following command: ```bash @@ -177,7 +228,6 @@ npx nx database:reset twenty-server Start the server and the frontend: ```bash npx nx start twenty-server - npx nx start twenty-front ``` @@ -186,8 +236,9 @@ Alternatively, you can start both applications at once: npx nx start ``` -Twenty's server will be up and running at [http://localhost:3000/graphql](http://localhost:3000/graphql). -Twenty's frontend will be running at [http://localhost:3001](http://localhost:3001). Just login using the seeded demo account: `tim@apple.dev` (password: `Applecar2025`) to start using Twenty. +Twenty's server will be up and running at [http://localhost:3000](http://localhost:3000). The GraphQL API can be accessed at [http://localhost:3000/graphql](http://localhost:3000/graphql), and the REST API can be reached at [http://localhost:3000/rest](http://localhost:3000/rest). + +Twenty's frontend will be running at [http://localhost:3001](http://localhost:3001). Just log in using the seeded demo account: `tim@apple.dev` (password: `Applecar2025`) to start using Twenty. ## Troubleshooting @@ -218,7 +269,7 @@ Make sure to run yarn in the root directory and then run `npx nx server:dev twen #### Lint on Save not working -This should work out of the box with the eslint extension installed. If this doens't work try adding this to your vscode setting (on the dev container scope): +This should work out of the box with the eslint extension installed. If this doesn't work try adding this to your vscode setting (on the dev container scope): ``` "editor.codeActionsOnSave": { @@ -228,8 +279,4 @@ This should work out of the box with the eslint extension installed. If this doe } ``` -#### Docker container build - -To successfully build Docker images, ensure that your system has a minimum of 2GB of memory available. For users of Docker Desktop, please verify that you've allocated sufficient resources to Docker within the application's settings. - -<ArticleEditContent></ArticleEditContent> \ No newline at end of file +<ArticleEditContent></ArticleEditContent> diff --git a/packages/twenty-website/src/content/developers/self-hosting/docker-compose.mdx b/packages/twenty-website/src/content/developers/self-hosting/docker-compose.mdx index 833fffbcc43e..14aa5821c943 100644 --- a/packages/twenty-website/src/content/developers/self-hosting/docker-compose.mdx +++ b/packages/twenty-website/src/content/developers/self-hosting/docker-compose.mdx @@ -4,6 +4,10 @@ icon: TbBrandDocker image: /images/user-guide/objects/objects.png --- +<ArticleWarning> +Docker containers are for production hosting or self-hosting, for the contribution please check the [Local Setup](https://twenty.com/developers/local-setup). +</ArticleWarning> + ## Overview This guide provides step-by-step instructions to install and configure the Twenty application using Docker Compose. The aim is to make the process straightforward and prevent common pitfalls that could break your setup. diff --git a/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx b/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx index d5219e511fba..cda5fb3a3d1c 100644 --- a/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx +++ b/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx @@ -41,7 +41,7 @@ yarn command:prod cron:calendar:calendar-event-list-fetch ['FRONT_BASE_URL', 'http://localhost:3001', 'Url to the hosted frontend'], ['SERVER_URL', 'http://localhost:3000', 'Url to the hosted server'], ['PORT', '3000', 'Port'], - ['CACHE_STORAGE_TYPE', 'memory', 'Cache type (memory, redis...)'], + ['CACHE_STORAGE_TYPE', 'redis', 'Cache type (memory, redis...)'], ['CACHE_STORAGE_TTL', '3600 * 24 * 7', 'Cache TTL in seconds'] ]}></ArticleTable> @@ -71,7 +71,7 @@ yarn command:prod cron:calendar:calendar-event-list-fetch <ArticleTable options={[ ['MESSAGING_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Gmail API connection'], ['CALENDAR_PROVIDER_GOOGLE_ENABLED', 'false', 'Enable Google Calendar API connection'], - ['AUTH_GOOGLE_APIS_CALLBACK_URL', '', 'Google APIs auth callback'], + ['AUTH_GOOGLE_APIS_CALLBACK_URL', 'http://[YourDomain]/auth/google/redirect', 'Google APIs auth callback'], ['AUTH_PASSWORD_ENABLED', 'false', 'Enable Email/Password login'], ['AUTH_GOOGLE_ENABLED', 'false', 'Enable Google SSO login'], ['AUTH_GOOGLE_CLIENT_ID', '', 'Google client ID'], @@ -81,7 +81,7 @@ yarn command:prod cron:calendar:calendar-event-list-fetch ['AUTH_MICROSOFT_CLIENT_ID', '', 'Microsoft client ID'], ['AUTH_MICROSOFT_TENANT_ID', '', 'Microsoft tenant ID'], ['AUTH_MICROSOFT_CLIENT_SECRET', '', 'Microsoft client secret'], - ['AUTH_MICROSOFT_CALLBACK_URL', '', 'Microsoft auth callback'], + ['AUTH_MICROSOFT_CALLBACK_URL', 'http://[YourDomain]/auth/microsoft/redirect', 'Microsoft auth callback'], ['FRONT_AUTH_CALLBACK_URL', 'http://localhost:3001/verify ', 'Callback used for Login page'], ['IS_SIGN_UP_DISABLED', 'false', 'Disable sign-up'], ['PASSWORD_RESET_TOKEN_EXPIRES_IN', '5m', 'Password reset token expiration time'], @@ -162,7 +162,7 @@ yarn command:prod cron:calendar:calendar-event-list-fetch ### Message Queue <ArticleTable options={[ - ['MESSAGE_QUEUE_TYPE', 'pg-boss', "Queue driver: 'pg-boss' or 'bull-mq'"], + ['MESSAGE_QUEUE_TYPE', 'bull-mq', "Queue driver: 'pg-boss' or 'bull-mq'"], ]}></ArticleTable> ### Logging diff --git a/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx b/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx index d22c3384f0dd..c260b6a04178 100644 --- a/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx +++ b/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx @@ -22,6 +22,8 @@ Migrating a CRM is a bit trickier than migrating a traditional software, because ## v0.21.0 to v0.22.0 +Upgrade your Twenty instance to use v0.22.0 image + Run the following commands: ``` @@ -36,6 +38,8 @@ The `yarn command:prod upgrade-0.22` command will apply specific data transforma ## v0.22.0 to v0.23.0 +Upgrade your Twenty instance to use v0.23.0 image + Run the following commands: ``` @@ -48,6 +52,8 @@ The `yarn command:prod upgrade-0.23` takes care of the data migration, including ## v0.23.0 to v0.24.0 +Upgrade your Twenty instance to use v0.24.0 image + Run the following commands: ``` @@ -58,4 +64,39 @@ yarn command:prod upgrade-0.24 The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas) The `yarn command:prod upgrade-0.24` takes care of the data migration of all workspaces. +# v0.24.0 to v0.30.0 + +Upgrade your Twenty instance to use v0.30.0 image + +**Breaking change**: +To enhance performances, Twenty now requires redis cache to be configured. We have updated our [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) to reflect this. +Make sure to update your configuration and to update your environment variables accordingly: +``` +REDIS_HOST={your-redis-host} +REDIS_PORT={your-redis-port} +CACHE_STORAGE_TYPE=redis +``` + +**Schema and data migration**: +``` +yarn database:migrate:prod +yarn command:prod upgrade-0.30 +``` + +The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas) +The `yarn command:prod upgrade-30` takes care of the data migration of all workspaces. + +# v0.30.0 to v0.31.0 + +Upgrade your Twenty instance to use v0.31.0 image + +**Schema and data migration**: +``` +yarn database:migrate:prod +yarn command:prod upgrade-0.31 +``` + +The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas) +The `yarn command:prod upgrade-31` takes care of the data migration of all workspaces. + <ArticleEditContent></ArticleEditContent> diff --git a/packages/twenty-website/src/content/releases/0.31.0.mdx b/packages/twenty-website/src/content/releases/0.31.0.mdx new file mode 100644 index 000000000000..f8ffb6c5f67f --- /dev/null +++ b/packages/twenty-website/src/content/releases/0.31.0.mdx @@ -0,0 +1,16 @@ +--- +release: 0.31.0 +Date: October 7th 2024 +--- + +# Advanced Settings + +To maintain the simplicity of Twenty, we are introducing "Advanced Settings." This option consolidates all settings intended for advanced use cases, often preferred by developers, such as API and function settings or security settings. + +![](/images/releases/0.31/0.31-advanced-settings.png) + +# More powerful search + +We have significantly enhanced our search performance, making it feel instantaneous when searching for records such as people, companies, or tasks. + +![](/images/releases/0.31/0.31-search.png) \ No newline at end of file diff --git a/packages/twenty-zapier/jest.config.ts b/packages/twenty-zapier/jest.config.ts new file mode 100644 index 000000000000..39406fb49f2c --- /dev/null +++ b/packages/twenty-zapier/jest.config.ts @@ -0,0 +1,9 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + transform: { + '^.+\\.ts?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'js'], + transformIgnorePatterns: ['/node_modules/'], +}; diff --git a/packages/twenty-zapier/package.json b/packages/twenty-zapier/package.json index a87732489045..30790678791d 100644 --- a/packages/twenty-zapier/package.json +++ b/packages/twenty-zapier/package.json @@ -1,6 +1,6 @@ { "name": "twenty-zapier", - "version": "1.0.1", + "version": "1.0.2", "description": "Effortlessly sync Twenty with 3000+ apps. Automate tasks, boost productivity, and supercharge your customer relationships!", "main": "src/index.ts", "scripts": { @@ -25,6 +25,7 @@ "convertedByCLIVersion": "15.4.1" }, "dependencies": { + "dotenv": "^16.4.5", "zapier-platform-core": "15.5.1" }, "devDependencies": { diff --git a/packages/twenty-zapier/src/test/authentication.test.ts b/packages/twenty-zapier/src/test/authentication.test.ts index 96d90e2a4504..66844af5e8ec 100644 --- a/packages/twenty-zapier/src/test/authentication.test.ts +++ b/packages/twenty-zapier/src/test/authentication.test.ts @@ -62,7 +62,7 @@ describe('custom auth', () => { expect(response.data).toHaveProperty('currentWorkspace'); expect(response.data.currentWorkspace).toHaveProperty('displayName'); } catch (error: any) { - expect(error.message).toContain('ENOTFOUND'); + expect(error.message).toBeDefined(); } }); diff --git a/packages/twenty-zapier/src/test/creates/crud_record.test.ts b/packages/twenty-zapier/src/test/creates/crud_record.test.ts index eb763338b82b..0fe5a3f4abf4 100644 --- a/packages/twenty-zapier/src/test/creates/crud_record.test.ts +++ b/packages/twenty-zapier/src/test/creates/crud_record.test.ts @@ -15,9 +15,18 @@ describe('creates.create_company', () => { crudZapierOperation: Operation.create, name: 'Company Name', address: { addressCity: 'Paris' }, - domainName: 'Company Domain Name', - linkedinLink: { url: '/linkedin_url', label: 'Test linkedinUrl' }, - xLink: { url: '/x_url', label: 'Test xUrl' }, + linkedinLink: { + primaryLinkUrl: '/linkedin_url', + primaryLinkLabel: 'Test linkedinUrl', + secondaryLinks: [ + '{ url: "/linkedin_url2", label: "Test linkedinUrl2" }', + ], + }, + xLink: { + primaryLinkUrl: '/x_url', + primaryLinkLabel: 'Test xUrl', + secondaryLinks: ['{ url: "/x_url2", label: "Test xUrl2" }'], + }, annualRecurringRevenue: { amountMicros: 100000000000, currencyCode: 'USD', @@ -49,8 +58,11 @@ describe('creates.create_company', () => { nameSingular: 'Person', crudZapierOperation: Operation.create, name: { firstName: 'John', lastName: 'Doe' }, - email: 'johndoe@gmail.com', - phone: '+33610203040', + phones: { + primaryPhoneNumber: '610203040', + primaryPhoneCountryCode: '+33', + additionalPhones: ['{number: "610203041", countryCode: "+33"}'], + }, city: 'Paris', }); const result = await appTester( @@ -64,11 +76,13 @@ describe('creates.create_company', () => { requestDb( z, bundle, - `query findPerson {person(filter: {id: {eq: "${result.data.createPerson.id}"}}){phone}}`, + `query findPerson {person(filter: {id: {eq: "${result.data.createPerson.id}"}}){phones{primaryPhoneNumber}}}`, ), bundle, ); - expect(checkDbResult.data.person.phone).toEqual('+33610203040'); + expect(checkDbResult.data.person.phones.primaryPhoneNumber).toEqual( + '610203040', + ); }); }); diff --git a/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts b/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts index 3eb1433cd2c0..fe76d7bed70d 100644 --- a/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts +++ b/packages/twenty-zapier/src/test/utils/computeInputFields.test.ts @@ -1,5 +1,5 @@ import { computeInputFields } from '../../utils/computeInputFields'; -import { InputField } from '../../utils/data.types'; +import { FieldMetadataType, InputField } from '../../utils/data.types'; describe('computeInputFields', () => { test('should create Person input fields properly', () => { @@ -11,7 +11,7 @@ describe('computeInputFields', () => { edges: [ { node: { - type: 'RELATION', + type: FieldMetadataType.RELATION, name: 'favorites', label: 'Favorites', description: 'Favorites linked to the contact', @@ -21,7 +21,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'CURRENCY', + type: FieldMetadataType.CURRENCY, name: 'annualSalary', label: 'Annual Salary', description: 'Annual Salary of the Person', @@ -31,7 +31,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'jobTitle', label: 'Job Title', description: 'Contactโ€™s job title', @@ -43,7 +43,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'DATE_TIME', + type: FieldMetadataType.DATE_TIME, name: 'updatedAt', label: 'Update date', description: null, @@ -55,7 +55,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'FULL_NAME', + type: FieldMetadataType.FULL_NAME, name: 'name', label: 'Name', description: 'Contactโ€™s name', @@ -68,7 +68,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'UUID', + type: FieldMetadataType.UUID, name: 'id', label: 'Id', description: null, @@ -81,7 +81,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'NUMBER', + type: FieldMetadataType.NUMBER, name: 'recordPosition', label: 'RecordPosition', description: 'Record Position', @@ -91,7 +91,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'LINK', + type: FieldMetadataType.LINK, name: 'xLink', label: 'X', description: 'Contactโ€™s X/Twitter account', @@ -101,7 +101,17 @@ describe('computeInputFields', () => { }, { node: { - type: 'EMAIL', + type: FieldMetadataType.LINKS, + name: 'whatsapp', + label: 'Whatsapp', + description: 'Contactโ€™s Whatsapp account', + isNullable: true, + defaultValue: null, + }, + }, + { + node: { + type: FieldMetadataType.EMAIL, name: 'email', label: 'Email', description: 'Contactโ€™s Email', @@ -113,7 +123,7 @@ describe('computeInputFields', () => { }, { node: { - type: 'UUID', + type: FieldMetadataType.UUID, name: 'companyId', label: 'Company id (foreign key)', description: 'Contactโ€™s company id foreign key', @@ -132,6 +142,8 @@ describe('computeInputFields', () => { helpText: 'Annual Salary of the Person: Amount Micros. eg: set 3210000 for 3.21$', required: false, + list: false, + placeholder: undefined, }, { key: 'annualSalary__currencyCode', @@ -140,6 +152,8 @@ describe('computeInputFields', () => { helpText: 'Annual Salary of the Person: Currency Code. eg: USD, EUR, etc...', required: false, + list: false, + placeholder: undefined, }, { key: 'jobTitle', @@ -147,6 +161,8 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s job title', required: false, + list: false, + placeholder: undefined, }, { key: 'updatedAt', @@ -154,6 +170,8 @@ describe('computeInputFields', () => { type: 'datetime', helpText: null, required: false, + list: false, + placeholder: undefined, }, { key: 'name__firstName', @@ -161,6 +179,8 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s name: First Name', required: false, + list: false, + placeholder: undefined, }, { key: 'name__lastName', @@ -168,6 +188,8 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s name: Last Name', required: false, + list: false, + placeholder: undefined, }, { key: 'recordPosition', @@ -175,6 +197,8 @@ describe('computeInputFields', () => { type: 'integer', helpText: 'Record Position', required: false, + list: false, + placeholder: undefined, }, { key: 'xLink__url', @@ -182,6 +206,8 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s X/Twitter account: Link Url', required: false, + list: false, + placeholder: undefined, }, { key: 'xLink__label', @@ -189,6 +215,35 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s X/Twitter account: Link Label', required: false, + list: false, + placeholder: undefined, + }, + { + key: 'whatsapp__primaryLinkLabel', + label: 'Whatsapp: Primary Link Label', + type: 'string', + helpText: 'Contactโ€™s Whatsapp account: Primary Link Label', + required: false, + list: false, + placeholder: undefined, + }, + { + key: 'whatsapp__primaryLinkUrl', + label: 'Whatsapp: Primary Link Url', + type: 'string', + helpText: 'Contactโ€™s Whatsapp account: Primary Link Url', + required: false, + list: false, + placeholder: undefined, + }, + { + key: 'whatsapp__secondaryLinks', + label: 'Whatsapp: Secondary Links', + type: 'string', + helpText: 'Contactโ€™s Whatsapp account: Secondary Links', + required: false, + list: true, + placeholder: '{ url: "", label: "" }', }, { key: 'email', @@ -196,6 +251,8 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s Email', required: false, + list: false, + placeholder: undefined, }, { key: 'companyId', @@ -203,6 +260,8 @@ describe('computeInputFields', () => { type: 'string', helpText: 'Contactโ€™s company id foreign key', required: false, + list: false, + placeholder: undefined, }, ]; const idInputField: InputField = { @@ -210,6 +269,8 @@ describe('computeInputFields', () => { label: 'Id', type: 'string', helpText: null, + list: false, + placeholder: undefined, required: false, }; const expectedResult = [idInputField].concat(baseExpectedResult); diff --git a/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts b/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts index 872879900166..d01fb2b38b76 100644 --- a/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts +++ b/packages/twenty-zapier/src/test/utils/handleQueryParams.test.ts @@ -14,6 +14,20 @@ describe('utils.handleQueryParams', () => { domainName: 'Company Domain Name', linkedinUrl__url: '/linkedin_url', linkedinUrl__label: 'Test linkedinUrl', + whatsapp__primaryLinkUrl: '/whatsapp_url', + whatsapp__primaryLinkLabel: 'Whatsapp Link', + whatsapp__secondaryLinks: [ + "{url: '/secondary_whatsapp_url',label: 'Secondary Whatsapp Link'}", + ], + emails: { + primaryEmail: 'primary@email.com', + additionalEmails: ['secondary@email.com'], + }, + phones: { + primaryPhoneNumber: '322110011', + primaryPhoneCountryCode: '+33', + additionalPhones: ["{ phoneNumber: '322110012', countryCode: '+33' }"], + }, xUrl__url: '/x_url', xUrl__label: 'Test xUrl', annualRecurringRevenue: 100000, @@ -23,9 +37,12 @@ describe('utils.handleQueryParams', () => { const result = handleQueryParams(inputData); const expectedResult = 'name: "Company Name", ' + - 'address: { addressCity: "Paris" }, ' + + 'address: {addressCity: "Paris"}, ' + 'domainName: "Company Domain Name", ' + 'linkedinUrl: {url: "/linkedin_url", label: "Test linkedinUrl"}, ' + + 'whatsapp: {primaryLinkUrl: "/whatsapp_url", primaryLinkLabel: "Whatsapp Link", secondaryLinks: [{url: \'/secondary_whatsapp_url\',label: \'Secondary Whatsapp Link\'}]}, ' + + 'emails: {primaryEmail: "primary@email.com", additionalEmails: ["secondary@email.com"]}, ' + + 'phones: {primaryPhoneNumber: "322110011", primaryPhoneCountryCode: "+33", additionalPhones: [{ phoneNumber: \'322110012\', countryCode: \'+33\' }]}, ' + 'xUrl: {url: "/x_url", label: "Test xUrl"}, ' + 'annualRecurringRevenue: 100000, ' + 'idealCustomerProfile: true, ' + diff --git a/packages/twenty-zapier/src/utils/computeInputFields.ts b/packages/twenty-zapier/src/utils/computeInputFields.ts index f2eabc331692..3e9cfbe4136e 100644 --- a/packages/twenty-zapier/src/utils/computeInputFields.ts +++ b/packages/twenty-zapier/src/utils/computeInputFields.ts @@ -5,15 +5,21 @@ import { NodeField, } from '../utils/data.types'; +const getListFromFieldMetadataType = (fieldMetadataType: FieldMetadataType) => { + return fieldMetadataType === FieldMetadataType.ARRAY; +}; + const getTypeFromFieldMetadataType = ( - fieldMetadataType: string, + fieldMetadataType: FieldMetadataType, ): string | undefined => { switch (fieldMetadataType) { case FieldMetadataType.UUID: case FieldMetadataType.TEXT: + case FieldMetadataType.RICH_TEXT: case FieldMetadataType.PHONE: case FieldMetadataType.EMAIL: case FieldMetadataType.LINK: + case FieldMetadataType.ARRAY: case FieldMetadataType.RATING: return 'string'; case FieldMetadataType.DATE_TIME: @@ -23,6 +29,7 @@ const getTypeFromFieldMetadataType = ( case FieldMetadataType.BOOLEAN: return 'boolean'; case FieldMetadataType.NUMBER: + case FieldMetadataType.POSITION: return 'integer'; case FieldMetadataType.NUMERIC: return 'number'; @@ -35,7 +42,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { switch (nodeField.type) { case FieldMetadataType.FULL_NAME: { const firstName: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'firstName', label: 'First Name', description: 'First Name', @@ -43,7 +50,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const lastName: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'lastName', label: 'Last Name', description: 'Last Name', @@ -54,7 +61,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { } case FieldMetadataType.LINK: { const url: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'url', label: 'Url', description: 'Link Url', @@ -62,7 +69,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const label: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'label', label: 'Label', description: 'Link Label', @@ -73,7 +80,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { } case FieldMetadataType.CURRENCY: { const amountMicros: NodeField = { - type: 'NUMBER', + type: FieldMetadataType.NUMBER, name: 'amountMicros', label: 'Amount Micros', description: 'Amount Micros. eg: set 3210000 for 3.21$', @@ -81,7 +88,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const currencyCode: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'currencyCode', label: 'Currency Code', description: 'Currency Code. eg: USD, EUR, etc...', @@ -92,7 +99,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { } case FieldMetadataType.ADDRESS: { const address1: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'addressStreet1', label: 'Address', description: 'Address', @@ -100,7 +107,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const address2: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'addressStreet2', label: 'Address 2', description: 'Address 2', @@ -108,7 +115,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const city: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'addressCity', label: 'City', description: 'City', @@ -116,7 +123,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const state: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'addressState', label: 'State', description: 'State', @@ -124,7 +131,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const postalCode: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'addressPostalCode', label: 'Postal Code', description: 'Postal Code', @@ -132,7 +139,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { defaultValue: null, }; const country: NodeField = { - type: 'TEXT', + type: FieldMetadataType.TEXT, name: 'addressCountry', label: 'Country', description: 'Country', @@ -141,6 +148,84 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => { }; return [address1, address2, city, state, postalCode, country]; } + case FieldMetadataType.PHONES: { + const primaryPhoneNumber: NodeField = { + type: FieldMetadataType.TEXT, + name: 'primaryPhoneNumber', + label: 'Primary Phone Number', + description: 'Primary Phone Number. 600112233', + isNullable: true, + defaultValue: null, + }; + const primaryPhoneCountryCode: NodeField = { + type: FieldMetadataType.TEXT, + name: 'primaryPhoneCountryCode', + label: 'Primary Phone Country Code', + description: 'Primary Phone Country Code. eg: +33', + isNullable: true, + defaultValue: null, + }; + const additionalPhones: NodeField = { + type: FieldMetadataType.TEXT, + name: 'additionalPhones', + label: 'Additional Phones', + description: 'Additional Phones', + isNullable: true, + defaultValue: null, + placeholder: '{ number: "", countryCode: "" }', + list: true, + }; + return [primaryPhoneNumber, primaryPhoneCountryCode, additionalPhones]; + } + case FieldMetadataType.EMAILS: { + const primaryEmail: NodeField = { + type: FieldMetadataType.TEXT, + name: 'primaryEmail', + label: 'Primary Email', + description: 'Primary Email', + isNullable: true, + defaultValue: null, + }; + const additionalEmails: NodeField = { + type: FieldMetadataType.TEXT, + name: 'additionalEmails', + label: 'Additional Emails', + description: 'Additional Emails', + list: true, + isNullable: true, + defaultValue: null, + }; + return [primaryEmail, additionalEmails]; + } + case FieldMetadataType.LINKS: { + const primaryLinkLabel: NodeField = { + type: FieldMetadataType.TEXT, + name: 'primaryLinkLabel', + label: 'Primary Link Label', + description: 'Primary Link Label', + isNullable: true, + defaultValue: null, + }; + const primaryLinkUrl: NodeField = { + type: FieldMetadataType.TEXT, + name: 'primaryLinkUrl', + label: 'Primary Link Url', + description: 'Primary Link Url', + isNullable: true, + defaultValue: null, + }; + const secondaryLinks: NodeField = { + type: FieldMetadataType.TEXT, + name: 'secondaryLinks', + label: 'Secondary Links', + description: 'Secondary Links', + isNullable: true, + defaultValue: null, + placeholder: '{ url: "", label: "" }', + list: true, + }; + return [primaryLinkLabel, primaryLinkUrl, secondaryLinks]; + } default: throw new Error(`Unknown nodeField type: ${nodeField.type}`); } @@ -161,6 +246,9 @@ export const computeInputFields = ( case FieldMetadataType.FULL_NAME: case FieldMetadataType.LINK: case FieldMetadataType.CURRENCY: + case FieldMetadataType.PHONES: + case FieldMetadataType.EMAILS: + case FieldMetadataType.LINKS: case FieldMetadataType.ADDRESS: for (const subNodeField of get_subfieldsFromField(nodeField)) { const field = { @@ -169,12 +257,15 @@ export const computeInputFields = ( type: getTypeFromFieldMetadataType(subNodeField.type), helpText: `${nodeField.description}: ${subNodeField.description}`, required: isFieldRequired(subNodeField), + list: !!subNodeField.list, + placeholder: subNodeField.placeholder, } as InputField; result.push(field); } break; case FieldMetadataType.UUID: case FieldMetadataType.TEXT: + case FieldMetadataType.RICH_TEXT: case FieldMetadataType.PHONE: case FieldMetadataType.EMAIL: case FieldMetadataType.DATE_TIME: @@ -182,6 +273,8 @@ export const computeInputFields = ( case FieldMetadataType.BOOLEAN: case FieldMetadataType.NUMBER: case FieldMetadataType.NUMERIC: + case FieldMetadataType.POSITION: + case FieldMetadataType.ARRAY: case FieldMetadataType.RATING: { const nodeFieldType = getTypeFromFieldMetadataType(nodeField.type); if (!nodeFieldType) { @@ -196,6 +289,8 @@ export const computeInputFields = ( type: nodeFieldType, helpText: nodeField.description, required, + list: getListFromFieldMetadataType(nodeField.type), + placeholder: undefined, }; result.push(field); break; diff --git a/packages/twenty-zapier/src/utils/data.types.ts b/packages/twenty-zapier/src/utils/data.types.ts index ba73ae18e09a..9c6b35d33b4b 100644 --- a/packages/twenty-zapier/src/utils/data.types.ts +++ b/packages/twenty-zapier/src/utils/data.types.ts @@ -3,12 +3,14 @@ export type InputData = { [x: string]: any }; export type ObjectData = { id: string } | { [x: string]: any }; export type NodeField = { - type: string; + type: FieldMetadataType; name: string; label: string; description: string | null; isNullable: boolean; defaultValue: object | null; + list?: boolean; + placeholder?: string; }; export type Node = { @@ -28,26 +30,39 @@ export type InputField = { type: string; helpText: string | null; required: boolean; + list?: boolean; + placeholder?: string; }; export enum FieldMetadataType { UUID = 'UUID', TEXT = 'TEXT', PHONE = 'PHONE', + PHONES = 'PHONES', EMAIL = 'EMAIL', + EMAILS = 'EMAILS', DATE_TIME = 'DATE_TIME', DATE = 'DATE', BOOLEAN = 'BOOLEAN', NUMBER = 'NUMBER', NUMERIC = 'NUMERIC', LINK = 'LINK', + LINKS = 'LINKS', CURRENCY = 'CURRENCY', FULL_NAME = 'FULL_NAME', RATING = 'RATING', SELECT = 'SELECT', MULTI_SELECT = 'MULTI_SELECT', - RELATION = 'RELATION', + POSITION = 'POSITION', ADDRESS = 'ADDRESS', + RICH_TEXT = 'RICH_TEXT', + ARRAY = 'ARRAY', + + // Ignored fieldTypes + RELATION = 'RELATION', + RAW_JSON = 'RAW_JSON', + ACTOR = 'ACTOR', + TS_VECTOR = 'TS_VECTOR', } export type Schema = { diff --git a/packages/twenty-zapier/src/utils/handleQueryParams.ts b/packages/twenty-zapier/src/utils/handleQueryParams.ts index 5bbb58815656..15039e9e35e6 100644 --- a/packages/twenty-zapier/src/utils/handleQueryParams.ts +++ b/packages/twenty-zapier/src/utils/handleQueryParams.ts @@ -1,5 +1,17 @@ import { InputData } from '../utils/data.types'; +const OBJECT_SUBFIELD_NAMES = ['secondaryLinks', 'additionalPhones']; + +const formatArrayInputData = ( + key: string, + arrayInputData: InputData, +): string => { + if (OBJECT_SUBFIELD_NAMES.includes(key)) { + return `${arrayInputData[key].join('","')}`; + } + return `"${arrayInputData[key].join('","')}"`; +}; + const handleQueryParams = (inputData: InputData): string => { const formattedInputData: InputData = {}; Object.keys(inputData).forEach((key) => { @@ -17,7 +29,11 @@ const handleQueryParams = (inputData: InputData): string => { let result = ''; Object.keys(formattedInputData).forEach((key) => { let quote = ''; - if (typeof formattedInputData[key] === 'object') { + if (Array.isArray(formattedInputData[key])) { + result = result.concat( + `${key}: [${formatArrayInputData(key, formattedInputData)}], `, + ); + } else if (typeof formattedInputData[key] === 'object') { result = result.concat( `${key}: {${handleQueryParams(formattedInputData[key])}}, `, ); diff --git a/packages/twenty-zapier/tsconfig.json b/packages/twenty-zapier/tsconfig.json index 1a3eb323e4d1..3e011dd8a419 100644 --- a/packages/twenty-zapier/tsconfig.json +++ b/packages/twenty-zapier/tsconfig.json @@ -9,5 +9,8 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true - } + }, + "exclude": [ + "jest.config.ts" + ] } diff --git a/yarn.lock b/yarn.lock index 293917a2211f..d3400e4a26b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -147,13 +147,6 @@ __metadata: languageName: node linkType: hard -"@algolia/events@npm:^4.0.1": - version: 4.0.1 - resolution: "@algolia/events@npm:4.0.1" - checksum: 10c0/f398d815c6ed21ac08f6caadf1e9155add74ac05d99430191c3b1f1335fd91deaf468c6b304e6225c9885d3d44c06037c53def101e33d9c22daff175b2a65ca9 - languageName: node - linkType: hard - "@algolia/logger-common@npm:4.24.0": version: 4.24.0 resolution: "@algolia/logger-common@npm:4.24.0" @@ -1607,7 +1600,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.8.3": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.7": version: 7.24.7 resolution: "@babel/code-frame@npm:7.24.7" dependencies: @@ -1624,7 +1617,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.5, @babel/core@npm:^7.18.9, @babel/core@npm:^7.20.12, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.3, @babel/core@npm:^7.23.5, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.5, @babel/core@npm:^7.7.5": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.5, @babel/core@npm:^7.18.9, @babel/core@npm:^7.20.12, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.5, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.5, @babel/core@npm:^7.7.5": version: 7.25.2 resolution: "@babel/core@npm:7.25.2" dependencies: @@ -1647,7 +1640,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.23.0, @babel/generator@npm:^7.23.3, @babel/generator@npm:^7.23.5, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.23.0, @babel/generator@npm:^7.23.5, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.7.2": version: 7.25.0 resolution: "@babel/generator@npm:7.25.0" dependencies: @@ -2878,7 +2871,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-runtime@npm:^7.22.9, @babel/plugin-transform-runtime@npm:^7.23.2": +"@babel/plugin-transform-runtime@npm:^7.23.2": version: 7.24.7 resolution: "@babel/plugin-transform-runtime@npm:7.24.7" dependencies: @@ -3012,7 +3005,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.22.9, @babel/preset-env@npm:^7.23.2": +"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.23.2": version: 7.25.3 resolution: "@babel/preset-env@npm:7.25.3" dependencies: @@ -3131,7 +3124,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.14.5, @babel/preset-react@npm:^7.18.6, @babel/preset-react@npm:^7.22.5": +"@babel/preset-react@npm:^7.14.5, @babel/preset-react@npm:^7.18.6": version: 7.24.7 resolution: "@babel/preset-react@npm:7.24.7" dependencies: @@ -3184,7 +3177,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime-corejs3@npm:^7.22.6, @babel/runtime-corejs3@npm:^7.24.4": +"@babel/runtime-corejs3@npm:^7.24.4": version: 7.25.0 resolution: "@babel/runtime-corejs3@npm:7.25.0" dependencies: @@ -3194,7 +3187,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.23.8, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.23.8, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.25.0 resolution: "@babel/runtime@npm:7.25.0" dependencies: @@ -3214,7 +3207,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.5, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": +"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.5, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": version: 7.25.3 resolution: "@babel/traverse@npm:7.25.3" dependencies: @@ -3600,27 +3593,27 @@ __metadata: languageName: node linkType: hard -"@discoveryjs/json-ext@npm:0.5.7, @discoveryjs/json-ext@npm:^0.5.3": +"@discoveryjs/json-ext@npm:^0.5.3": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" checksum: 10c0/e10f1b02b78e4812646ddf289b7d9f2cb567d336c363b266bd50cd223cf3de7c2c74018d91cd2613041568397ef3a4a2b500aba588c6e5bd78c38374ba68f38c languageName: node linkType: hard -"@docsearch/css@npm:3.6.1": - version: 3.6.1 - resolution: "@docsearch/css@npm:3.6.1" - checksum: 10c0/546b7b725044d006fe5fd2061763fbd1f944d9db21c7b86adb2d11e7bd5eee41b102f1ecccb001bb1603ef7503282cc9ad204482db62e4bc0b038c46a9cd9e6d +"@docsearch/css@npm:3.6.2": + version: 3.6.2 + resolution: "@docsearch/css@npm:3.6.2" + checksum: 10c0/f9f8af55814a8a8dfbac78972cff2c264d4e5508de61d893dbc07544c8e1dcb044803ba150c56f4d245f8f5f88d84fa7f6226038b813850bd602f4bf48123793 languageName: node linkType: hard -"@docsearch/react@npm:^3.5.2": - version: 3.6.1 - resolution: "@docsearch/react@npm:3.6.1" +"@docsearch/react@npm:^3.6.2": + version: 3.6.2 + resolution: "@docsearch/react@npm:3.6.2" dependencies: "@algolia/autocomplete-core": "npm:1.9.3" "@algolia/autocomplete-preset-algolia": "npm:1.9.3" - "@docsearch/css": "npm:3.6.1" + "@docsearch/css": "npm:3.6.2" algoliasearch: "npm:^4.19.1" peerDependencies: "@types/react": ">= 16.8.0 < 19.0.0" @@ -3636,533 +3629,7 @@ __metadata: optional: true search-insights: optional: true - checksum: 10c0/890d46ed1f971a6af9f64377c9e510e4b39324bfedcc143c7bd35ba883f8fdac3dc844b0a0000059fd3dec16a0443e7f723d65c468ca7bafd03be546caf38479 - languageName: node - linkType: hard - -"@docusaurus/core@npm:3.4.0, @docusaurus/core@npm:^3.1.0": - version: 3.4.0 - resolution: "@docusaurus/core@npm:3.4.0" - dependencies: - "@babel/core": "npm:^7.23.3" - "@babel/generator": "npm:^7.23.3" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-transform-runtime": "npm:^7.22.9" - "@babel/preset-env": "npm:^7.22.9" - "@babel/preset-react": "npm:^7.22.5" - "@babel/preset-typescript": "npm:^7.22.5" - "@babel/runtime": "npm:^7.22.6" - "@babel/runtime-corejs3": "npm:^7.22.6" - "@babel/traverse": "npm:^7.22.8" - "@docusaurus/cssnano-preset": "npm:3.4.0" - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/mdx-loader": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - autoprefixer: "npm:^10.4.14" - babel-loader: "npm:^9.1.3" - babel-plugin-dynamic-import-node: "npm:^2.3.3" - boxen: "npm:^6.2.1" - chalk: "npm:^4.1.2" - chokidar: "npm:^3.5.3" - clean-css: "npm:^5.3.2" - cli-table3: "npm:^0.6.3" - combine-promises: "npm:^1.1.0" - commander: "npm:^5.1.0" - copy-webpack-plugin: "npm:^11.0.0" - core-js: "npm:^3.31.1" - css-loader: "npm:^6.8.1" - css-minimizer-webpack-plugin: "npm:^5.0.1" - cssnano: "npm:^6.1.2" - del: "npm:^6.1.1" - detect-port: "npm:^1.5.1" - escape-html: "npm:^1.0.3" - eta: "npm:^2.2.0" - eval: "npm:^0.1.8" - file-loader: "npm:^6.2.0" - fs-extra: "npm:^11.1.1" - html-minifier-terser: "npm:^7.2.0" - html-tags: "npm:^3.3.1" - html-webpack-plugin: "npm:^5.5.3" - leven: "npm:^3.1.0" - lodash: "npm:^4.17.21" - mini-css-extract-plugin: "npm:^2.7.6" - p-map: "npm:^4.0.0" - postcss: "npm:^8.4.26" - postcss-loader: "npm:^7.3.3" - prompts: "npm:^2.4.2" - react-dev-utils: "npm:^12.0.1" - react-helmet-async: "npm:^1.3.0" - react-loadable: "npm:@docusaurus/react-loadable@6.0.0" - react-loadable-ssr-addon-v5-slorber: "npm:^1.0.1" - react-router: "npm:^5.3.4" - react-router-config: "npm:^5.1.1" - react-router-dom: "npm:^5.3.4" - rtl-detect: "npm:^1.0.4" - semver: "npm:^7.5.4" - serve-handler: "npm:^6.1.5" - shelljs: "npm:^0.8.5" - terser-webpack-plugin: "npm:^5.3.9" - tslib: "npm:^2.6.0" - update-notifier: "npm:^6.0.2" - url-loader: "npm:^4.1.1" - webpack: "npm:^5.88.1" - webpack-bundle-analyzer: "npm:^4.9.0" - webpack-dev-server: "npm:^4.15.1" - webpack-merge: "npm:^5.9.0" - webpackbar: "npm:^5.0.2" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - bin: - docusaurus: bin/docusaurus.mjs - checksum: 10c0/28a9d2c4c893930e7fa7bbf5df2aed79a5cdc618c9f40d5b867846cb78ee371a52af41a59c6adf677cd480cb4350dfad4866de4b06928b4169c295c601472867 - languageName: node - linkType: hard - -"@docusaurus/cssnano-preset@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/cssnano-preset@npm:3.4.0" - dependencies: - cssnano-preset-advanced: "npm:^6.1.2" - postcss: "npm:^8.4.38" - postcss-sort-media-queries: "npm:^5.2.0" - tslib: "npm:^2.6.0" - checksum: 10c0/bcbdfb9d34d8b26bf89c8e414f5fc775bae5c12a0c280064a8aaf30c7260ffb760dee5ce86171f87cf4dcdeddb39a815ebfc6bdfd5ec14fb26c5cb340c51af55 - languageName: node - linkType: hard - -"@docusaurus/logger@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/logger@npm:3.4.0" - dependencies: - chalk: "npm:^4.1.2" - tslib: "npm:^2.6.0" - checksum: 10c0/0759eee9bc01cf0c16da70ccd0cd3363e649f00bb6d04595bf436a4d40235b6f78d6d18f8a5499244693f067a708e3fb3126c122c01b1c0fa3665198d24a80a2 - languageName: node - linkType: hard - -"@docusaurus/mdx-loader@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/mdx-loader@npm:3.4.0" - dependencies: - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - "@mdx-js/mdx": "npm:^3.0.0" - "@slorber/remark-comment": "npm:^1.0.0" - escape-html: "npm:^1.0.3" - estree-util-value-to-estree: "npm:^3.0.1" - file-loader: "npm:^6.2.0" - fs-extra: "npm:^11.1.1" - image-size: "npm:^1.0.2" - mdast-util-mdx: "npm:^3.0.0" - mdast-util-to-string: "npm:^4.0.0" - rehype-raw: "npm:^7.0.0" - remark-directive: "npm:^3.0.0" - remark-emoji: "npm:^4.0.0" - remark-frontmatter: "npm:^5.0.0" - remark-gfm: "npm:^4.0.0" - stringify-object: "npm:^3.3.0" - tslib: "npm:^2.6.0" - unified: "npm:^11.0.3" - unist-util-visit: "npm:^5.0.0" - url-loader: "npm:^4.1.1" - vfile: "npm:^6.0.1" - webpack: "npm:^5.88.1" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/3a3295e01571ceefdc74abbfdc5125b6acd2c7bfe13cac0c6c79f61c9fc16e1244594748c92ceb01baae648d4aedd594fa1b513aca66f7a74244d347c91820b0 - languageName: node - linkType: hard - -"@docusaurus/module-type-aliases@npm:3.4.0, @docusaurus/module-type-aliases@npm:^3.1.0": - version: 3.4.0 - resolution: "@docusaurus/module-type-aliases@npm:3.4.0" - dependencies: - "@docusaurus/types": "npm:3.4.0" - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - "@types/react-router-config": "npm:*" - "@types/react-router-dom": "npm:*" - react-helmet-async: "npm:*" - react-loadable: "npm:@docusaurus/react-loadable@6.0.0" - peerDependencies: - react: "*" - react-dom: "*" - checksum: 10c0/37645717442eaf2d62dcb972db544f5231392f1dbeb7499d725cef50b4c2762d7a95facff8a759f9127814861c6ccb859f69661f1634b7bf8c27be13f9d3e626 - languageName: node - linkType: hard - -"@docusaurus/plugin-content-blog@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-content-blog@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/mdx-loader": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - cheerio: "npm:^1.0.0-rc.12" - feed: "npm:^4.2.2" - fs-extra: "npm:^11.1.1" - lodash: "npm:^4.17.21" - reading-time: "npm:^1.5.0" - srcset: "npm:^4.0.0" - tslib: "npm:^2.6.0" - unist-util-visit: "npm:^5.0.0" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/016804ee40bd8c9e2a097eb76e618a00cbedd48967df8da2670961086dc61ce44d7a3bc959050c82b469b2efa07b0f3faedb11caf7a9983c181bab30b9f2054e - languageName: node - linkType: hard - -"@docusaurus/plugin-content-docs@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-content-docs@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/mdx-loader": "npm:3.4.0" - "@docusaurus/module-type-aliases": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - "@types/react-router-config": "npm:^5.0.7" - combine-promises: "npm:^1.1.0" - fs-extra: "npm:^11.1.1" - js-yaml: "npm:^4.1.0" - lodash: "npm:^4.17.21" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/dc12d09c7ecd9f18bf48ee16432f01a974880f251a6c68b0be6cc6876edd2f25561cf4b0829a34bc9daa9ec5d44c8797a0b096dc7480346fb502482734ea728c - languageName: node - linkType: hard - -"@docusaurus/plugin-content-pages@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-content-pages@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/mdx-loader": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - fs-extra: "npm:^11.1.1" - tslib: "npm:^2.6.0" - webpack: "npm:^5.88.1" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/2d741a710ebb7b5687f4635d1f67ef1297e3db400093c84152688aeb95d9a6728b56be8080f0fcee095800e55e3fa72cf358782ef76cf61230c171443f21f480 - languageName: node - linkType: hard - -"@docusaurus/plugin-debug@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-debug@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - fs-extra: "npm:^11.1.1" - react-json-view-lite: "npm:^1.2.0" - tslib: "npm:^2.6.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/2cda3492e3da55d81a26dc2e03c1bb96ab4243985a0b9b1c47589ed320a5917e535fd0b88669cad2c42fa50e41344ac8323dd5fa408b7e07c5ebd0aa5cee4117 - languageName: node - linkType: hard - -"@docusaurus/plugin-google-analytics@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-google-analytics@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - tslib: "npm:^2.6.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/6b2c1785c202b527ec68777d1cf5fbe5211a02793e86f6821e4b403a1f0e4359b2a4be28e837be641ac7e436eb26f0e961854124c74b8ad8d9bba01f302ac908 - languageName: node - linkType: hard - -"@docusaurus/plugin-google-gtag@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-google-gtag@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - "@types/gtag.js": "npm:^0.0.12" - tslib: "npm:^2.6.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/1ab2bad4dd47c3aab8393b241efbdad7c5f8b2248970cf9f5e04537de279ffe0d92bf428f8690abf8b522142433e8ea7eb61fbf98a70d606b4e45b95663cb563 - languageName: node - linkType: hard - -"@docusaurus/plugin-google-tag-manager@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-google-tag-manager@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - tslib: "npm:^2.6.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/91242ee1d7f7f755de512501cd297d6c1e4546b706310e78cfac83e5e96ce45f37434aca1052567365ba904559f40bd7a713e6271535d3ade8781db012d5d730 - languageName: node - linkType: hard - -"@docusaurus/plugin-sitemap@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/plugin-sitemap@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - fs-extra: "npm:^11.1.1" - sitemap: "npm:^7.1.1" - tslib: "npm:^2.6.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/498754e04a29d1715d8c4c91a1c30526294de5714f14dff31563339ad0966dc7e2ffca5cd5ffd059268be364fb1eb4f6ccc3f397f69f7537c9f655a29f56bd9f - languageName: node - linkType: hard - -"@docusaurus/preset-classic@npm:^3.1.0": - version: 3.4.0 - resolution: "@docusaurus/preset-classic@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/plugin-content-blog": "npm:3.4.0" - "@docusaurus/plugin-content-docs": "npm:3.4.0" - "@docusaurus/plugin-content-pages": "npm:3.4.0" - "@docusaurus/plugin-debug": "npm:3.4.0" - "@docusaurus/plugin-google-analytics": "npm:3.4.0" - "@docusaurus/plugin-google-gtag": "npm:3.4.0" - "@docusaurus/plugin-google-tag-manager": "npm:3.4.0" - "@docusaurus/plugin-sitemap": "npm:3.4.0" - "@docusaurus/theme-classic": "npm:3.4.0" - "@docusaurus/theme-common": "npm:3.4.0" - "@docusaurus/theme-search-algolia": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/faa95f52b459b91903e35e0a90ccc6fea040657eacd64c6cdd9ac93f8d981b17adbf978ab97b1e4bdb7621f10febb2633c94c598f45feb5106aeed62f6485a91 - languageName: node - linkType: hard - -"@docusaurus/theme-classic@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/theme-classic@npm:3.4.0" - dependencies: - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/mdx-loader": "npm:3.4.0" - "@docusaurus/module-type-aliases": "npm:3.4.0" - "@docusaurus/plugin-content-blog": "npm:3.4.0" - "@docusaurus/plugin-content-docs": "npm:3.4.0" - "@docusaurus/plugin-content-pages": "npm:3.4.0" - "@docusaurus/theme-common": "npm:3.4.0" - "@docusaurus/theme-translations": "npm:3.4.0" - "@docusaurus/types": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - "@mdx-js/react": "npm:^3.0.0" - clsx: "npm:^2.0.0" - copy-text-to-clipboard: "npm:^3.2.0" - infima: "npm:0.2.0-alpha.43" - lodash: "npm:^4.17.21" - nprogress: "npm:^0.2.0" - postcss: "npm:^8.4.26" - prism-react-renderer: "npm:^2.3.0" - prismjs: "npm:^1.29.0" - react-router-dom: "npm:^5.3.4" - rtlcss: "npm:^4.1.0" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/9b8cfd8c6350f3bfe7d23480b860e3f18ec1be2c0cc563e6b5bf8ba9e4b7be75d01d1601362f8615a33635cd2bf76deb48628e367372899dc87f9eadab8e7b0f - languageName: node - linkType: hard - -"@docusaurus/theme-common@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/theme-common@npm:3.4.0" - dependencies: - "@docusaurus/mdx-loader": "npm:3.4.0" - "@docusaurus/module-type-aliases": "npm:3.4.0" - "@docusaurus/plugin-content-blog": "npm:3.4.0" - "@docusaurus/plugin-content-docs": "npm:3.4.0" - "@docusaurus/plugin-content-pages": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - "@types/react-router-config": "npm:*" - clsx: "npm:^2.0.0" - parse-numeric-range: "npm:^1.3.0" - prism-react-renderer: "npm:^2.3.0" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/a52ca6849674e216f1584e3d9ba13fafa70deb12192534c213473fb702ba656c686c09de8400f2041e7224c79097832149d3a3d0d434ec8d346bb67f8ef73847 - languageName: node - linkType: hard - -"@docusaurus/theme-search-algolia@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/theme-search-algolia@npm:3.4.0" - dependencies: - "@docsearch/react": "npm:^3.5.2" - "@docusaurus/core": "npm:3.4.0" - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/plugin-content-docs": "npm:3.4.0" - "@docusaurus/theme-common": "npm:3.4.0" - "@docusaurus/theme-translations": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-validation": "npm:3.4.0" - algoliasearch: "npm:^4.18.0" - algoliasearch-helper: "npm:^3.13.3" - clsx: "npm:^2.0.0" - eta: "npm:^2.2.0" - fs-extra: "npm:^11.1.1" - lodash: "npm:^4.17.21" - tslib: "npm:^2.6.0" - utility-types: "npm:^3.10.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/cdc04522ecd64dad67ca2a276bc1938df40a72a68819db14e36fe770a393fcf2448491534e657ac684c8dfcbb8af8f45a202b5e5464cb26f5098927a2526228a - languageName: node - linkType: hard - -"@docusaurus/theme-translations@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/theme-translations@npm:3.4.0" - dependencies: - fs-extra: "npm:^11.1.1" - tslib: "npm:^2.6.0" - checksum: 10c0/e32ce684d2c9269534ab6f1a71086ae2520e68cd5c42ecb0222da7d7b8a60cd96dc23dbcd0f0856b5439b71efd6552f321c66f17d218967c6b02b46589181e2a - languageName: node - linkType: hard - -"@docusaurus/tsconfig@npm:3.1.0": - version: 3.1.0 - resolution: "@docusaurus/tsconfig@npm:3.1.0" - checksum: 10c0/bb2bfdc16aaa37a92a13ecb00d3e041e578c906618c563955b174e7a4ef602863e8d13debf0e2b60db6de118ad684275be5e73c036e695a3027fab0d16800f85 - languageName: node - linkType: hard - -"@docusaurus/types@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/types@npm:3.4.0" - dependencies: - "@mdx-js/mdx": "npm:^3.0.0" - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - commander: "npm:^5.1.0" - joi: "npm:^17.9.2" - react-helmet-async: "npm:^1.3.0" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" - webpack-merge: "npm:^5.9.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10c0/c86b95dfbf02db6faa9bb4d6c552d54f2e57924a95937cff6f1884e0ef66f7bbaf84e645fffa229f2571fea6ee469d3dd15abff20f81f7dc886ad38c4c79cbdb - languageName: node - linkType: hard - -"@docusaurus/utils-common@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/utils-common@npm:3.4.0" - dependencies: - tslib: "npm:^2.6.0" - peerDependencies: - "@docusaurus/types": "*" - peerDependenciesMeta: - "@docusaurus/types": - optional: true - checksum: 10c0/df8e27a88621f5984624ac8c15061dd3eb2f33d3c3f5e08381d3aa316347a62884b2b5f051f54050516ceea1d4656012893be09ca81eae56809f4eb723924f56 - languageName: node - linkType: hard - -"@docusaurus/utils-validation@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/utils-validation@npm:3.4.0" - dependencies: - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/utils": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - fs-extra: "npm:^11.2.0" - joi: "npm:^17.9.2" - js-yaml: "npm:^4.1.0" - lodash: "npm:^4.17.21" - tslib: "npm:^2.6.0" - checksum: 10c0/5a4c13bd41f1c5132b33c09f29f788fb76c3a9b0c4326e8bb2661041ab8c9cabd682f5d3f6203fae49e28bc975217b99e485dcc23065afb16498978774b37ee6 - languageName: node - linkType: hard - -"@docusaurus/utils@npm:3.4.0": - version: 3.4.0 - resolution: "@docusaurus/utils@npm:3.4.0" - dependencies: - "@docusaurus/logger": "npm:3.4.0" - "@docusaurus/utils-common": "npm:3.4.0" - "@svgr/webpack": "npm:^8.1.0" - escape-string-regexp: "npm:^4.0.0" - file-loader: "npm:^6.2.0" - fs-extra: "npm:^11.1.1" - github-slugger: "npm:^1.5.0" - globby: "npm:^11.1.0" - gray-matter: "npm:^4.0.3" - jiti: "npm:^1.20.0" - js-yaml: "npm:^4.1.0" - lodash: "npm:^4.17.21" - micromatch: "npm:^4.0.5" - prompts: "npm:^2.4.2" - resolve-pathname: "npm:^3.0.0" - shelljs: "npm:^0.8.5" - tslib: "npm:^2.6.0" - url-loader: "npm:^4.1.1" - utility-types: "npm:^3.10.0" - webpack: "npm:^5.88.1" - peerDependencies: - "@docusaurus/types": "*" - peerDependenciesMeta: - "@docusaurus/types": - optional: true - checksum: 10c0/b80444985e97c1c9586a2b2669438b02e3a122d382b38633f0a7317365b5d3ad05beb882328ada4b09bb3de7e18d29f89d2a4e02fadf4acebdc5dd768b2265b9 + checksum: 10c0/8fcf47de8786d097005912347fe566577361193026d58b610d5540ef26fd3bf1b30bfe986e23357fd1ee5b97f0a5deb102de3bda79c069536e49a9f3d4b0fc76 languageName: node linkType: hard @@ -6976,13 +6443,6 @@ __metadata: languageName: node linkType: hard -"@leichtgewicht/ip-codec@npm:^2.0.1": - version: 2.0.5 - resolution: "@leichtgewicht/ip-codec@npm:2.0.5" - checksum: 10c0/14a0112bd59615eef9e3446fea018045720cd3da85a98f801a685a818b0d96ef2a1f7227e8d271def546b2e2a0fe91ef915ba9dc912ab7967d2317b1a051d66b - languageName: node - linkType: hard - "@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0": version: 1.2.1 resolution: "@lezer/common@npm:1.2.1" @@ -7164,37 +6624,6 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^3.0.0": - version: 3.0.1 - resolution: "@mdx-js/mdx@npm:3.0.1" - dependencies: - "@types/estree": "npm:^1.0.0" - "@types/estree-jsx": "npm:^1.0.0" - "@types/hast": "npm:^3.0.0" - "@types/mdx": "npm:^2.0.0" - collapse-white-space: "npm:^2.0.0" - devlop: "npm:^1.0.0" - estree-util-build-jsx: "npm:^3.0.0" - estree-util-is-identifier-name: "npm:^3.0.0" - estree-util-to-js: "npm:^2.0.0" - estree-walker: "npm:^3.0.0" - hast-util-to-estree: "npm:^3.0.0" - hast-util-to-jsx-runtime: "npm:^2.0.0" - markdown-extensions: "npm:^2.0.0" - periscopic: "npm:^3.0.0" - remark-mdx: "npm:^3.0.0" - remark-parse: "npm:^11.0.0" - remark-rehype: "npm:^11.0.0" - source-map: "npm:^0.7.0" - unified: "npm:^11.0.0" - unist-util-position-from-estree: "npm:^2.0.0" - unist-util-stringify-position: "npm:^4.0.0" - unist-util-visit: "npm:^5.0.0" - vfile: "npm:^6.0.0" - checksum: 10c0/8cd7084f1242209bbeef81f69ea670ffffa0656dda2893bbd46b1b2b26078a57f9d993f8f82ad8ba16bc969189235140007185276d7673471827331521eae2e0 - languageName: node - linkType: hard - "@mdx-js/react@npm:^2.1.5, @mdx-js/react@npm:^2.2.1": version: 2.3.0 resolution: "@mdx-js/react@npm:2.3.0" @@ -8106,106 +7535,173 @@ __metadata: languageName: node linkType: hard -"@nivo/calendar@npm:^0.84.0": - version: 0.84.0 - resolution: "@nivo/calendar@npm:0.84.0" +"@nivo/annotations@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/annotations@npm:0.87.0" dependencies: - "@nivo/core": "npm:0.84.0" - "@nivo/legends": "npm:0.84.0" - "@nivo/tooltip": "npm:0.84.0" - "@types/d3-scale": "npm:^3.2.3" + "@nivo/colors": "npm:0.87.0" + "@nivo/core": "npm:0.87.0" + "@react-spring/web": "npm:9.4.5 || ^9.7.2" + lodash: "npm:^4.17.21" + peerDependencies: + react: ">= 16.14.0 < 19.0.0" + checksum: 10c0/603599c5e697b987ccd360fc835fd4152b43e9facf5542369669faad9352e56a093c6b9dff8c3b4114d25267144478c89ad834fe4929b0ec223c7600795b276b + languageName: node + linkType: hard + +"@nivo/axes@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/axes@npm:0.87.0" + dependencies: + "@nivo/core": "npm:0.87.0" + "@nivo/scales": "npm:0.87.0" + "@react-spring/web": "npm:9.4.5 || ^9.7.2" + "@types/d3-format": "npm:^1.4.1" + "@types/d3-time-format": "npm:^2.3.1" + d3-format: "npm:^1.4.4" + d3-time-format: "npm:^3.0.0" + peerDependencies: + react: ">= 16.14.0 < 19.0.0" + checksum: 10c0/7af5f6ea3101ab957b171713276ff2acb1dee608fe96876a3afac8ede5327856ddfcd3046d084fbe417ef032080caa87736187d3b77d2f6f79f65c7d70fa5bb2 + languageName: node + linkType: hard + +"@nivo/calendar@npm:^0.87.0": + version: 0.87.0 + resolution: "@nivo/calendar@npm:0.87.0" + dependencies: + "@nivo/core": "npm:0.87.0" + "@nivo/legends": "npm:0.87.0" + "@nivo/tooltip": "npm:0.87.0" + "@types/d3-scale": "npm:^4.0.8" "@types/d3-time": "npm:^1.0.10" "@types/d3-time-format": "npm:^3.0.0" - d3-scale: "npm:^3.2.3" + d3-scale: "npm:^4.0.2" d3-time: "npm:^1.0.10" d3-time-format: "npm:^3.0.0" lodash: "npm:^4.17.21" peerDependencies: react: ">= 16.14.0 < 19.0.0" - checksum: 10c0/7c83753a6f6b830df6823eaefcaf22883aca0b2ba630085075a39ef9cdd35313ca153416d88037ebf4cc31a356bb356f26e581dda770c38d7402c97dad182572 + checksum: 10c0/5cb0f4ceb45109695f1b86e4215ef464b7c33449c50d45a436f71a30029bcfca6b91956d6be9862d17cd0d8bf4dd80e2ce281cb85ace83ee07159a0e9f603242 languageName: node linkType: hard -"@nivo/colors@npm:0.84.0": - version: 0.84.0 - resolution: "@nivo/colors@npm:0.84.0" +"@nivo/colors@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/colors@npm:0.87.0" dependencies: - "@nivo/core": "npm:0.84.0" - "@types/d3-color": "npm:^2.0.0" - "@types/d3-scale": "npm:^3.2.3" - "@types/d3-scale-chromatic": "npm:^2.0.0" + "@nivo/core": "npm:0.87.0" + "@types/d3-color": "npm:^3.0.0" + "@types/d3-scale": "npm:^4.0.8" + "@types/d3-scale-chromatic": "npm:^3.0.0" "@types/prop-types": "npm:^15.7.2" d3-color: "npm:^3.1.0" - d3-scale: "npm:^3.2.3" - d3-scale-chromatic: "npm:^2.0.0" + d3-scale: "npm:^4.0.2" + d3-scale-chromatic: "npm:^3.0.0" lodash: "npm:^4.17.21" prop-types: "npm:^15.7.2" peerDependencies: react: ">= 16.14.0 < 19.0.0" - checksum: 10c0/036d9128bd451711c66bd377d80470fa9aa4a763fc2e89ac379fbde4661b3899096d1a8926e64a0edf56fa403d75e68eaead02bd5d4649883bb5c375796a63cc + checksum: 10c0/872f4e2d8392f89633531250e0474a365e0456dff146d2e8ba8576226effd1eda7e721ce1dd15a792b05d06caa28a11510a5ca8e55fb84aedb8ba3d964f357f5 languageName: node linkType: hard -"@nivo/core@npm:0.84.0, @nivo/core@npm:^0.84.0": - version: 0.84.0 - resolution: "@nivo/core@npm:0.84.0" +"@nivo/core@npm:0.87.0, @nivo/core@npm:^0.87.0": + version: 0.87.0 + resolution: "@nivo/core@npm:0.87.0" dependencies: - "@nivo/recompose": "npm:0.84.0" - "@nivo/tooltip": "npm:0.84.0" + "@nivo/tooltip": "npm:0.87.0" "@react-spring/web": "npm:9.4.5 || ^9.7.2" - "@types/d3-shape": "npm:^2.0.0" + "@types/d3-shape": "npm:^3.1.6" d3-color: "npm:^3.1.0" d3-format: "npm:^1.4.4" d3-interpolate: "npm:^3.0.1" - d3-scale: "npm:^3.2.3" + d3-scale: "npm:^4.0.2" d3-scale-chromatic: "npm:^3.0.0" - d3-shape: "npm:^1.3.5" + d3-shape: "npm:^3.2.0" d3-time-format: "npm:^3.0.0" lodash: "npm:^4.17.21" + prop-types: "npm:^15.7.2" peerDependencies: - prop-types: ">= 15.5.10 < 16.0.0" react: ">= 16.14.0 < 19.0.0" - checksum: 10c0/c92cca26ca7b33ae29c04a4da429843bc0a9c7b7a49be1b8d98b01af74384719a5f7e35d31e42a37f2191e0359d8d44e8e911acedae5371a02c4f7abdfb40fa3 + checksum: 10c0/75bb5e48cf57c8e31fbd9b9f33121fb4bffb98d47a967e5135527f8fde51537b68da3d894dc7231bf3c969523985eac6a5fc6d50827b958d51e6907a6391ed84 languageName: node linkType: hard -"@nivo/legends@npm:0.84.0": - version: 0.84.0 - resolution: "@nivo/legends@npm:0.84.0" +"@nivo/legends@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/legends@npm:0.87.0" dependencies: - "@nivo/colors": "npm:0.84.0" - "@nivo/core": "npm:0.84.0" - "@types/d3-scale": "npm:^3.2.3" - "@types/prop-types": "npm:^15.7.2" - d3-scale: "npm:^3.2.3" - prop-types: "npm:^15.7.2" + "@nivo/colors": "npm:0.87.0" + "@nivo/core": "npm:0.87.0" + "@types/d3-scale": "npm:^4.0.8" + d3-scale: "npm:^4.0.2" peerDependencies: react: ">= 16.14.0 < 19.0.0" - checksum: 10c0/25f64a068910a411b5073a30e74c5da0eb8e91d00d39619d62472726686349536bca95de8eb7e3c7e4a29872f652bdcd272f68f315ec54248c1cfdb15e3c37b6 + checksum: 10c0/1501bf698cefa2695d1124c38da5fc7712a5ce9911683c6694cbea1a481b9daef8ca7b0fc49eb85530c50347c571ea5b686caff3d6a81174b66bc38318bd317d languageName: node linkType: hard -"@nivo/recompose@npm:0.84.0": - version: 0.84.0 - resolution: "@nivo/recompose@npm:0.84.0" +"@nivo/line@npm:^0.87.0": + version: 0.87.0 + resolution: "@nivo/line@npm:0.87.0" dependencies: - "@types/prop-types": "npm:^15.7.2" - "@types/react-lifecycles-compat": "npm:^3.0.1" - prop-types: "npm:^15.7.2" - react-lifecycles-compat: "npm:^3.0.4" + "@nivo/annotations": "npm:0.87.0" + "@nivo/axes": "npm:0.87.0" + "@nivo/colors": "npm:0.87.0" + "@nivo/core": "npm:0.87.0" + "@nivo/legends": "npm:0.87.0" + "@nivo/scales": "npm:0.87.0" + "@nivo/tooltip": "npm:0.87.0" + "@nivo/voronoi": "npm:0.87.0" + "@react-spring/web": "npm:9.4.5 || ^9.7.2" + d3-shape: "npm:^3.2.0" peerDependencies: react: ">= 16.14.0 < 19.0.0" - checksum: 10c0/5a182544d445bf277cabb82e4139681231ab9659275e35e8e15f79b31ae96ebb223b19b8326c101c419bc82c26a4f26eb9980ff21937d84f971ce5833fe17d30 + checksum: 10c0/8ca8e4fbfe988a9c59c20f3c20caa391589f19af94d460e22925b9659e9058b82fddd4039c76e8242c6fd2676c15c6c8202041a9d3115397bdc9fe322ea7a06d + languageName: node + linkType: hard + +"@nivo/scales@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/scales@npm:0.87.0" + dependencies: + "@types/d3-scale": "npm:^4.0.8" + "@types/d3-time": "npm:^1.1.1" + "@types/d3-time-format": "npm:^3.0.0" + d3-scale: "npm:^4.0.2" + d3-time: "npm:^1.0.11" + d3-time-format: "npm:^3.0.0" + lodash: "npm:^4.17.21" + checksum: 10c0/7df01cd72fecbd82791c8aeb99439aa23ff54229ad9044e111fc02d51d0a2f7e8d67a75ed39854d829a7ab274580ca2e252931be5062eff3d08c48ab1580c7bd languageName: node linkType: hard -"@nivo/tooltip@npm:0.84.0": - version: 0.84.0 - resolution: "@nivo/tooltip@npm:0.84.0" +"@nivo/tooltip@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/tooltip@npm:0.87.0" dependencies: - "@nivo/core": "npm:0.84.0" + "@nivo/core": "npm:0.87.0" "@react-spring/web": "npm:9.4.5 || ^9.7.2" - checksum: 10c0/c9b52157951359c573b9bbe8e976d73f217694d89f2df470272a22d89b428fcab8d4e2c1e030104de5c2cf4fc06c0895ba7f4e56d93be2f42c25a836e1503038 + peerDependencies: + react: ">= 16.14.0 < 19.0.0" + checksum: 10c0/ac6b1b0bb0a09017c0e5055432e4c5ee771301615db3ee3d34abb55c900f765af78830eb2b18c8d94ff49ddeaa19895e907c793fb431943ade11cc2d7369b7e4 + languageName: node + linkType: hard + +"@nivo/voronoi@npm:0.87.0": + version: 0.87.0 + resolution: "@nivo/voronoi@npm:0.87.0" + dependencies: + "@nivo/core": "npm:0.87.0" + "@nivo/tooltip": "npm:0.87.0" + "@types/d3-delaunay": "npm:^6.0.4" + "@types/d3-scale": "npm:^4.0.8" + d3-delaunay: "npm:^6.0.4" + d3-scale: "npm:^4.0.2" + peerDependencies: + react: ">= 16.14.0 < 19.0.0" + checksum: 10c0/02bc6bb36ef1211bfdc440c135766f041dfba47b0d6c1e3b2748f7cde20ccec106621e559640a611b36b3e9d6a0e8d0492cace9143efbbf7aeb5499741eecf33 languageName: node linkType: hard @@ -9941,33 +9437,6 @@ __metadata: languageName: node linkType: hard -"@pnpm/config.env-replace@npm:^1.1.0": - version: 1.1.0 - resolution: "@pnpm/config.env-replace@npm:1.1.0" - checksum: 10c0/4cfc4a5c49ab3d0c6a1f196cfd4146374768b0243d441c7de8fa7bd28eaab6290f514b98490472cc65dbd080d34369447b3e9302585e1d5c099befd7c8b5e55f - languageName: node - linkType: hard - -"@pnpm/network.ca-file@npm:^1.0.1": - version: 1.0.2 - resolution: "@pnpm/network.ca-file@npm:1.0.2" - dependencies: - graceful-fs: "npm:4.2.10" - checksum: 10c0/95f6e0e38d047aca3283550719155ce7304ac00d98911e4ab026daedaf640a63bd83e3d13e17c623fa41ac72f3801382ba21260bcce431c14fbbc06430ecb776 - languageName: node - linkType: hard - -"@pnpm/npm-conf@npm:^2.1.0": - version: 2.3.0 - resolution: "@pnpm/npm-conf@npm:2.3.0" - dependencies: - "@pnpm/config.env-replace": "npm:^1.1.0" - "@pnpm/network.ca-file": "npm:^1.0.1" - config-chain: "npm:^1.1.11" - checksum: 10c0/605e986805b5bc46bde3d17cdc5a58f9da7da28ac331b83acde055eddefa8ca0e027844d8a97d337b8179ee6964db985214cec1206b76c29d0fcd5496c60abf2 - languageName: node - linkType: hard - "@polka/url@npm:^1.0.0-next.24": version: 1.0.0-next.25 resolution: "@polka/url@npm:1.0.0-next.25" @@ -12437,20 +11906,13 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^4.0.0, @sindresorhus/is@npm:^4.6.0": +"@sindresorhus/is@npm:^4.0.0": version: 4.6.0 resolution: "@sindresorhus/is@npm:4.6.0" checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e languageName: node linkType: hard -"@sindresorhus/is@npm:^5.2.0": - version: 5.6.0 - resolution: "@sindresorhus/is@npm:5.6.0" - checksum: 10c0/66727344d0c92edde5760b5fd1f8092b717f2298a162a5f7f29e4953e001479927402d9d387e245fb9dc7d3b37c72e335e93ed5875edfc5203c53be8ecba1b52 - languageName: node - linkType: hard - "@sinonjs/commons@npm:^3.0.0": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" @@ -12469,17 +11931,6 @@ __metadata: languageName: node linkType: hard -"@slorber/remark-comment@npm:^1.0.0": - version: 1.0.0 - resolution: "@slorber/remark-comment@npm:1.0.0" - dependencies: - micromark-factory-space: "npm:^1.0.0" - micromark-util-character: "npm:^1.1.0" - micromark-util-symbol: "npm:^1.0.1" - checksum: 10c0/b8da9d8f560740959c421d3ce5be43952eace1c95cb65402d9473a15e66463346a37fb5f121a6b22a83af51e8845b0b4ff3c321f14ce31bd58fb126acf6c8ed9 - languageName: node - linkType: hard - "@smithy/abort-controller@npm:^3.1.1": version: 3.1.1 resolution: "@smithy/abort-controller@npm:3.1.1" @@ -14857,7 +14308,7 @@ __metadata: languageName: node linkType: hard -"@svgr/webpack@npm:^8.0.1, @svgr/webpack@npm:^8.1.0": +"@svgr/webpack@npm:^8.0.1": version: 8.1.0 resolution: "@svgr/webpack@npm:8.1.0" dependencies: @@ -15244,15 +14695,6 @@ __metadata: languageName: node linkType: hard -"@szmarczak/http-timer@npm:^5.0.1": - version: 5.0.1 - resolution: "@szmarczak/http-timer@npm:5.0.1" - dependencies: - defer-to-connect: "npm:^2.0.1" - checksum: 10c0/4629d2fbb2ea67c2e9dc03af235c0991c79ebdddcbc19aed5d5732fb29ce01c13331e9b1a491584b9069bd6ecde6581dcbf871f11b7eefdebbab34de6cf2197e - languageName: node - linkType: hard - "@tabler/icons-react@npm:^2.44.0": version: 2.47.0 resolution: "@tabler/icons-react@npm:2.47.0" @@ -15807,15 +15249,6 @@ __metadata: languageName: node linkType: hard -"@types/bonjour@npm:^3.5.9": - version: 3.5.13 - resolution: "@types/bonjour@npm:3.5.13" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/eebedbca185ac3c39dd5992ef18d9e2a9f99e7f3c2f52f5561f90e9ed482c5d224c7962db95362712f580ed5713264e777a98d8f0bd8747f4eadf62937baed16 - languageName: node - linkType: hard - "@types/bytes@npm:^3.1.1": version: 3.1.4 resolution: "@types/bytes@npm:3.1.4" @@ -15870,16 +15303,6 @@ __metadata: languageName: node linkType: hard -"@types/connect-history-api-fallback@npm:^1.3.5": - version: 1.5.4 - resolution: "@types/connect-history-api-fallback@npm:1.5.4" - dependencies: - "@types/express-serve-static-core": "npm:*" - "@types/node": "npm:*" - checksum: 10c0/1b4035b627dcd714b05a22557f942e24a57ca48e7377dde0d2f86313fe685bc0a6566512a73257a55b5665b96c3041fb29228ac93331d8133011716215de8244 - languageName: node - linkType: hard - "@types/connect@npm:*": version: 3.4.38 resolution: "@types/connect@npm:3.4.38" @@ -15979,20 +15402,13 @@ __metadata: languageName: node linkType: hard -"@types/d3-color@npm:*": +"@types/d3-color@npm:*, @types/d3-color@npm:^3.0.0": version: 3.1.3 resolution: "@types/d3-color@npm:3.1.3" checksum: 10c0/65eb0487de606eb5ad81735a9a5b3142d30bc5ea801ed9b14b77cb14c9b909f718c059f13af341264ee189acf171508053342142bdf99338667cea26a2d8d6ae languageName: node linkType: hard -"@types/d3-color@npm:^2.0.0": - version: 2.0.6 - resolution: "@types/d3-color@npm:2.0.6" - checksum: 10c0/3d4b064d304fce21e9dccea3b8e11d11b7f1393df9bf577ea8b26fe16e0ea4b4ee4710c4fc4147c95c2db96512a23f80345dc22ebbb8d9c6dc473c4b709af47d - languageName: node - linkType: hard - "@types/d3-contour@npm:*": version: 3.0.6 resolution: "@types/d3-contour@npm:3.0.6" @@ -16003,7 +15419,7 @@ __metadata: languageName: node linkType: hard -"@types/d3-delaunay@npm:*": +"@types/d3-delaunay@npm:*, @types/d3-delaunay@npm:^6.0.4": version: 6.0.4 resolution: "@types/d3-delaunay@npm:6.0.4" checksum: 10c0/d154a8864f08c4ea23ecb9bdabcef1c406a25baa8895f0cb08a0ed2799de0d360e597552532ce7086ff0cdffa8f3563f9109d18f0191459d32bb620a36939123 @@ -16063,6 +15479,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-format@npm:^1.4.1": + version: 1.4.5 + resolution: "@types/d3-format@npm:1.4.5" + checksum: 10c0/d4dbfff22afdf1ad60db7115e877b891864fac380537534dbacf9b5f87cdcd0a418e8d83d4947c59ed8715befa7d018aecd8445f05ae3a5b0796dd495508c082 + languageName: node + linkType: hard + "@types/d3-geo@npm:*": version: 3.1.0 resolution: "@types/d3-geo@npm:3.1.0" @@ -16095,13 +15518,6 @@ __metadata: languageName: node linkType: hard -"@types/d3-path@npm:^2": - version: 2.0.4 - resolution: "@types/d3-path@npm:2.0.4" - checksum: 10c0/82214a9644cfffe0c1f9a7aab00e3912aaba89115c60d94ecf716d282eac71671761962a9e911a8ebc457777e3db42f80c355b61010e5e27218f6aed32128d39 - languageName: node - linkType: hard - "@types/d3-polygon@npm:*": version: 3.0.2 resolution: "@types/d3-polygon@npm:3.0.2" @@ -16123,21 +15539,14 @@ __metadata: languageName: node linkType: hard -"@types/d3-scale-chromatic@npm:*": +"@types/d3-scale-chromatic@npm:*, @types/d3-scale-chromatic@npm:^3.0.0": version: 3.0.3 resolution: "@types/d3-scale-chromatic@npm:3.0.3" checksum: 10c0/2f48c6f370edba485b57b73573884ded71914222a4580140ff87ee96e1d55ccd05b1d457f726e234a31269b803270ac95d5554229ab6c43c7e4a9894e20dd490 languageName: node linkType: hard -"@types/d3-scale-chromatic@npm:^2.0.0": - version: 2.0.4 - resolution: "@types/d3-scale-chromatic@npm:2.0.4" - checksum: 10c0/d545ea57b4c2fb539d60fce090bc2d265df48047702b8762c7decca1557edf9f761722a5e47d4a65bbf9c7271421a4f6088dde5ee700f94ba8f798c8b0ca3af6 - languageName: node - linkType: hard - -"@types/d3-scale@npm:*": +"@types/d3-scale@npm:*, @types/d3-scale@npm:^4.0.8": version: 4.0.8 resolution: "@types/d3-scale@npm:4.0.8" dependencies: @@ -16146,15 +15555,6 @@ __metadata: languageName: node linkType: hard -"@types/d3-scale@npm:^3.2.3": - version: 3.3.5 - resolution: "@types/d3-scale@npm:3.3.5" - dependencies: - "@types/d3-time": "npm:^2" - checksum: 10c0/2689ab13092e3fded22cdd1b888afd91aa60190be40c8eddc12b2d42de59b00917778340f90317c68c5ffc3a1bee68f5ca155434cd466bc7804f400f3f9e7529 - languageName: node - linkType: hard - "@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10, @types/d3-selection@npm:^3.0.3": version: 3.0.10 resolution: "@types/d3-selection@npm:3.0.10" @@ -16162,7 +15562,7 @@ __metadata: languageName: node linkType: hard -"@types/d3-shape@npm:*": +"@types/d3-shape@npm:*, @types/d3-shape@npm:^3.1.6": version: 3.1.6 resolution: "@types/d3-shape@npm:3.1.6" dependencies: @@ -16171,15 +15571,6 @@ __metadata: languageName: node linkType: hard -"@types/d3-shape@npm:^2.0.0": - version: 2.1.7 - resolution: "@types/d3-shape@npm:2.1.7" - dependencies: - "@types/d3-path": "npm:^2" - checksum: 10c0/2433f073b20a1f0180406a83e070a8d862101e637c1f6be8fbe814065d6627848b84b2bd33251752f5b469cd8e02217d21c43a8454ea1b56d7a0f493fa1a75a0 - languageName: node - linkType: hard - "@types/d3-time-format@npm:*": version: 4.0.3 resolution: "@types/d3-time-format@npm:4.0.3" @@ -16187,6 +15578,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-time-format@npm:^2.3.1": + version: 2.3.4 + resolution: "@types/d3-time-format@npm:2.3.4" + checksum: 10c0/37b447f7338ab99d1591c7c2e55dde3b35916904132040046de4ad68a5691580bc29f23d04d6ce262454bc2713f1fbeaac912b5b44efcd8b733adc30b08ce28a + languageName: node + linkType: hard + "@types/d3-time-format@npm:^3.0.0": version: 3.0.4 resolution: "@types/d3-time-format@npm:3.0.4" @@ -16201,20 +15599,13 @@ __metadata: languageName: node linkType: hard -"@types/d3-time@npm:^1.0.10": +"@types/d3-time@npm:^1.0.10, @types/d3-time@npm:^1.1.1": version: 1.1.4 resolution: "@types/d3-time@npm:1.1.4" checksum: 10c0/d1dafa4605c10739de216bdf3dfe9c3953e583e849dc5586216525897c96bbbae8972c50e9c11a4c54e700c089914cf7a9764e9806d316a84838ecf9e5c52722 languageName: node linkType: hard -"@types/d3-time@npm:^2": - version: 2.1.4 - resolution: "@types/d3-time@npm:2.1.4" - checksum: 10c0/b597bfa51a163d4231e953d6903b06fd6341d0f11a28222a79fafaddb46155d7f458a67c814de53df84926a47dd535897228a475679d228576b0cda87351e534 - languageName: node - linkType: hard - "@types/d3-timer@npm:*": version: 3.0.2 resolution: "@types/d3-timer@npm:3.0.2" @@ -16385,7 +15776,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.5, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5": +"@types/estree@npm:*, @types/estree@npm:1.0.5, @types/estree@npm:^1.0.0": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d @@ -16406,26 +15797,26 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18, @types/express-serve-static-core@npm:^4.17.30, @types/express-serve-static-core@npm:^4.17.33": - version: 4.19.5 - resolution: "@types/express-serve-static-core@npm:4.19.5" +"@types/express-serve-static-core@npm:4.17.31": + version: 4.17.31 + resolution: "@types/express-serve-static-core@npm:4.17.31" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/ba8d8d976ab797b2602c60e728802ff0c98a00f13d420d82770f3661b67fa36ea9d3be0b94f2ddd632afe1fbc6e41620008b01db7e4fabdd71a2beb5539b0725 + checksum: 10c0/c24f28f77413e16e1eea765c530ee8dc4797379a44323e9788f92fabb29c2c31beab17c4e64dec8eb8166f8d2abd40e45bd8bc876e55de271a5688b603ae1162 languageName: node linkType: hard -"@types/express-serve-static-core@npm:4.17.31": - version: 4.17.31 - resolution: "@types/express-serve-static-core@npm:4.17.31" +"@types/express-serve-static-core@npm:^4.17.18, @types/express-serve-static-core@npm:^4.17.30, @types/express-serve-static-core@npm:^4.17.33": + version: 4.19.5 + resolution: "@types/express-serve-static-core@npm:4.19.5" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" - checksum: 10c0/c24f28f77413e16e1eea765c530ee8dc4797379a44323e9788f92fabb29c2c31beab17c4e64dec8eb8166f8d2abd40e45bd8bc876e55de271a5688b603ae1162 + "@types/send": "npm:*" + checksum: 10c0/ba8d8d976ab797b2602c60e728802ff0c98a00f13d420d82770f3661b67fa36ea9d3be0b94f2ddd632afe1fbc6e41620008b01db7e4fabdd71a2beb5539b0725 languageName: node linkType: hard @@ -16544,13 +15935,6 @@ __metadata: languageName: node linkType: hard -"@types/gtag.js@npm:^0.0.12": - version: 0.0.12 - resolution: "@types/gtag.js@npm:0.0.12" - checksum: 10c0/fee8f4c6e627301b89ab616c9e219bd53fa6ea1ffd1d0a8021e21363f0bdb2cf7eb1a5bcda0c6f1502186379bc7784ec29c932e21634f4e07f9e7a8c56887400 - languageName: node - linkType: hard - "@types/har-format@npm:*, @types/har-format@npm:^1.2.10": version: 1.2.15 resolution: "@types/har-format@npm:1.2.15" @@ -16576,13 +15960,6 @@ __metadata: languageName: node linkType: hard -"@types/history@npm:^4.7.11": - version: 4.7.11 - resolution: "@types/history@npm:4.7.11" - checksum: 10c0/3facf37c2493d1f92b2e93a22cac7ea70b06351c2ab9aaceaa3c56aa6099fb63516f6c4ec1616deb5c56b4093c026a043ea2d3373e6c0644d55710364d02c934 - languageName: node - linkType: hard - "@types/hoist-non-react-statics@npm:^3.3.1": version: 3.3.5 resolution: "@types/hoist-non-react-statics@npm:3.3.5" @@ -16593,13 +15970,6 @@ __metadata: languageName: node linkType: hard -"@types/html-minifier-terser@npm:^6.0.0": - version: 6.1.0 - resolution: "@types/html-minifier-terser@npm:6.1.0" - checksum: 10c0/a62fb8588e2f3818d82a2d7b953ad60a4a52fd767ae04671de1c16f5788bd72f1ed3a6109ed63fd190c06a37d919e3c39d8adbc1793a005def76c15a3f5f5dab - languageName: node - linkType: hard - "@types/http-assert@npm:*": version: 1.5.5 resolution: "@types/http-assert@npm:1.5.5" @@ -16607,7 +15977,7 @@ __metadata: languageName: node linkType: hard -"@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.2": +"@types/http-cache-semantics@npm:*": version: 4.0.4 resolution: "@types/http-cache-semantics@npm:4.0.4" checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6 @@ -16621,15 +15991,6 @@ __metadata: languageName: node linkType: hard -"@types/http-proxy@npm:^1.17.8": - version: 1.17.15 - resolution: "@types/http-proxy@npm:1.17.15" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/e2bf2fcdf23c88141b8d2c85ed5e5418b62ef78285884a2b5a717af55f4d9062136aa475489d10292093343df58fb81975f34bebd6b9df322288fd9821cbee07 - languageName: node - linkType: hard - "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.4": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" @@ -17020,15 +16381,6 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.2": - version: 4.0.4 - resolution: "@types/mdast@npm:4.0.4" - dependencies: - "@types/unist": "npm:*" - checksum: 10c0/84f403dbe582ee508fd9c7643ac781ad8597fcbfc9ccb8d4715a2c92e4545e5772cbd0dbdf18eda65789386d81b009967fdef01b24faf6640f817287f54d9c82 - languageName: node - linkType: hard - "@types/mdurl@npm:^1.0.0": version: 1.0.5 resolution: "@types/mdurl@npm:1.0.5" @@ -17113,15 +16465,6 @@ __metadata: languageName: node linkType: hard -"@types/node-forge@npm:^1.3.0": - version: 1.3.11 - resolution: "@types/node-forge@npm:1.3.11" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/3d7d23ca0ba38ac0cf74028393bd70f31169ab9aba43f21deb787840170d307d662644bac07287495effe2812ddd7ac8a14dbd43f16c2936bbb06312e96fc3b9 - languageName: node - linkType: hard - "@types/node@npm:*, @types/node@npm:>=8.1.0, @types/node@npm:^22.1.0": version: 22.1.0 resolution: "@types/node@npm:22.1.0" @@ -17154,13 +16497,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^17.0.5": - version: 17.0.45 - resolution: "@types/node@npm:17.0.45" - checksum: 10c0/0db377133d709b33a47892581a21a41cd7958f22723a3cc6c71d55ac018121382de42fbfc7970d5ae3e7819dbe5f40e1c6a5174aedf7e7964e9cb8fa72b580b0 - languageName: node - linkType: hard - "@types/node@npm:^18.0.0, @types/node@npm:^18.11.18": version: 18.19.43 resolution: "@types/node@npm:18.19.43" @@ -17310,6 +16646,13 @@ __metadata: languageName: node linkType: hard +"@types/pluralize@npm:^0.0.33": + version: 0.0.33 + resolution: "@types/pluralize@npm:0.0.33" + checksum: 10c0/24899caf85b79dd291a6b6e9b9f3b67b452b18d578d0ac0d531a705bf5ee0361d9386ea1f8532c64de9e22c6e9606c5497787bb5e31bd299c487980436c59785 + languageName: node + linkType: hard + "@types/pretty-hrtime@npm:^1.0.0": version: 1.0.3 resolution: "@types/pretty-hrtime@npm:1.0.3" @@ -17374,47 +16717,6 @@ __metadata: languageName: node linkType: hard -"@types/react-lifecycles-compat@npm:^3.0.1": - version: 3.0.4 - resolution: "@types/react-lifecycles-compat@npm:3.0.4" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/3c33fcd7d52d44031b21cf8a6ae9c0f208fe3b972ee4f03fcbe4509d2c50da474bfdd3330f5a09046b7fd63a1f7f23b194bc8d774823c1981cc13929744b90d2 - languageName: node - linkType: hard - -"@types/react-router-config@npm:*, @types/react-router-config@npm:^5.0.7": - version: 5.0.11 - resolution: "@types/react-router-config@npm:5.0.11" - dependencies: - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - "@types/react-router": "npm:^5.1.0" - checksum: 10c0/3fa4daf8c14689a05f34e289fc53c4a892e97f35715455c507a8048d9875b19cd3d3142934ca973effed6a6c38f33539b6e173cd254f67e2021ecd5458d551c8 - languageName: node - linkType: hard - -"@types/react-router-dom@npm:*": - version: 5.3.3 - resolution: "@types/react-router-dom@npm:5.3.3" - dependencies: - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - "@types/react-router": "npm:*" - checksum: 10c0/a9231a16afb9ed5142678147eafec9d48582809295754fb60946e29fcd3757a4c7a3180fa94b45763e4c7f6e3f02379e2fcb8dd986db479dcab40eff5fc62a91 - languageName: node - linkType: hard - -"@types/react-router@npm:*, @types/react-router@npm:^5.1.0": - version: 5.1.20 - resolution: "@types/react-router@npm:5.1.20" - dependencies: - "@types/history": "npm:^4.7.11" - "@types/react": "npm:*" - checksum: 10c0/1f7eee61981d2f807fa01a34a0ef98ebc0774023832b6611a69c7f28fdff01de5a38cabf399f32e376bf8099dcb7afaf724775bea9d38870224492bea4cb5737 - languageName: node - linkType: hard - "@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:^18.2.39": version: 18.3.3 resolution: "@types/react@npm:18.3.3" @@ -17459,15 +16761,6 @@ __metadata: languageName: node linkType: hard -"@types/sax@npm:^1.2.1": - version: 1.2.7 - resolution: "@types/sax@npm:1.2.7" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/d077a761a0753b079bf8279b3993948030ca86ed9125437b9b29c1de40db9b2deb7fddc369f014b58861d450e8b8cc75f163aa29dc8cea81952efbfd859168cf - languageName: node - linkType: hard - "@types/scheduler@npm:^0.16": version: 0.16.8 resolution: "@types/scheduler@npm:0.16.8" @@ -17499,16 +16792,7 @@ __metadata: languageName: node linkType: hard -"@types/serve-index@npm:^1.9.1": - version: 1.9.4 - resolution: "@types/serve-index@npm:1.9.4" - dependencies: - "@types/express": "npm:*" - checksum: 10c0/94c1b9e8f1ea36a229e098e1643d5665d9371f8c2658521718e259130a237c447059b903bac0dcc96ee2c15fd63f49aa647099b7d0d437a67a6946527a837438 - languageName: node - linkType: hard - -"@types/serve-static@npm:*, @types/serve-static@npm:^1.13.10": +"@types/serve-static@npm:*": version: 1.15.7 resolution: "@types/serve-static@npm:1.15.7" dependencies: @@ -17526,15 +16810,6 @@ __metadata: languageName: node linkType: hard -"@types/sockjs@npm:^0.3.33": - version: 0.3.36 - resolution: "@types/sockjs@npm:0.3.36" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/b20b7820ee813f22de4f2ce98bdd12c68c930e016a8912b1ed967595ac0d8a4cbbff44f4d486dd97f77f5927e7b5725bdac7472c9ec5b27f53a5a13179f0612f - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -17686,7 +16961,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.0.0, @types/ws@npm:^8.5.5": +"@types/ws@npm:^8.0.0": version: 8.5.12 resolution: "@types/ws@npm:8.5.12" dependencies: @@ -18001,7 +17276,7 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": +"@ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" checksum: 10c0/8209c937cb39119f44eb63cf90c0b73e7c754209a6411c707be08e50e29ee81356dca1a848a405c8bdeebfe2f5e4f831ad310ae1689eeef65e7445c090c6657d @@ -18231,7 +17506,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.11.5, @webassemblyjs/ast@npm:^1.12.1": +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.11.5": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: @@ -18317,7 +17592,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5, @webassemblyjs/wasm-edit@npm:^1.12.1": +"@webassemblyjs/wasm-edit@npm:^1.11.5": version: 1.12.1 resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: @@ -18358,7 +17633,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.11.5, @webassemblyjs/wasm-parser@npm:^1.12.1": +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.11.5": version: 1.12.1 resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: @@ -18725,7 +18000,7 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.5, accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": +"accepts@npm:^1.3.5, accepts@npm:~1.3.5, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -18790,7 +18065,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.0.2, acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.2.0, acorn-walk@npm:^8.3.2": +"acorn-walk@npm:^8.0.2, acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.2.0, acorn-walk@npm:^8.3.2": version: 8.3.3 resolution: "acorn-walk@npm:8.3.3" dependencies: @@ -18808,7 +18083,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.12.1, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.1, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": +"acorn@npm:^8.0.0, acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.12.1, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.1, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.12.1 resolution: "acorn@npm:8.12.1" bin: @@ -18824,7 +18099,7 @@ __metadata: languageName: node linkType: hard -"address@npm:^1.0.1, address@npm:^1.1.2": +"address@npm:^1.0.1": version: 1.2.2 resolution: "address@npm:1.2.2" checksum: 10c0/1c8056b77fb124456997b78ed682ecc19d2fd7ea8bd5850a2aa8c3e3134c913847c57bcae418622efd32ba858fa1e242a40a251ac31da0515664fc0ac03a047d @@ -18896,7 +18171,7 @@ __metadata: languageName: node linkType: hard -"ajv-formats@npm:2.1.1, ajv-formats@npm:^2.1.1": +"ajv-formats@npm:2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" dependencies: @@ -18910,7 +18185,7 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": +"ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" peerDependencies: @@ -18919,17 +18194,6 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^5.1.0": - version: 5.1.0 - resolution: "ajv-keywords@npm:5.1.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - peerDependencies: - ajv: ^8.8.2 - checksum: 10c0/18bec51f0171b83123ba1d8883c126e60c6f420cef885250898bf77a8d3e65e3bfb9e8564f497e30bdbe762a83e0d144a36931328616a973ee669dc74d4a9590 - languageName: node - linkType: hard - "ajv@npm:8.12.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" @@ -18942,7 +18206,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:~6.12.6": +"ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:~6.12.6": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -18954,7 +18218,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.9.0": +"ajv@npm:^8.0.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -18966,18 +18230,7 @@ __metadata: languageName: node linkType: hard -"algoliasearch-helper@npm:^3.13.3": - version: 3.22.3 - resolution: "algoliasearch-helper@npm:3.22.3" - dependencies: - "@algolia/events": "npm:^4.0.1" - peerDependencies: - algoliasearch: ">= 3.1 < 6" - checksum: 10c0/c522eedd6cef022cd5c23ad3ec24691ce555ea1401cdd8c1cd650070b083dbd10bb6e859436d3a22659cc7a3ec9c056accbc6c02f957e1e316c2f5b3ec387f92 - languageName: node - linkType: hard - -"algoliasearch@npm:^4.18.0, algoliasearch@npm:^4.19.1": +"algoliasearch@npm:^4.19.1": version: 4.24.0 resolution: "algoliasearch@npm:4.24.0" dependencies: @@ -19007,7 +18260,7 @@ __metadata: languageName: node linkType: hard -"ansi-align@npm:^3.0.0, ansi-align@npm:^3.0.1": +"ansi-align@npm:^3.0.0": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" dependencies: @@ -19055,15 +18308,6 @@ __metadata: languageName: node linkType: hard -"ansi-html-community@npm:^0.0.8": - version: 0.0.8 - resolution: "ansi-html-community@npm:0.0.8" - bin: - ansi-html: bin/ansi-html - checksum: 10c0/45d3a6f0b4f10b04fdd44bef62972e2470bfd917bf00439471fa7473d92d7cbe31369c73db863cc45dda115cb42527f39e232e9256115534b8ee5806b0caeed4 - languageName: node - linkType: hard - "ansi-regex@npm:^2.0.0": version: 2.1.1 resolution: "ansi-regex@npm:2.1.1" @@ -19467,13 +18711,6 @@ __metadata: languageName: node linkType: hard -"arg@npm:^5.0.0": - version: 5.0.2 - resolution: "arg@npm:5.0.2" - checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e - languageName: node - linkType: hard - "argparse@npm:^1.0.7, argparse@npm:~1.0.9": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -19733,7 +18970,7 @@ __metadata: languageName: node linkType: hard -"assert@npm:^2.0.0, assert@npm:^2.1.0": +"assert@npm:^2.1.0": version: 2.1.0 resolution: "assert@npm:2.1.0" dependencies: @@ -19861,24 +19098,6 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:^10.4.14, autoprefixer@npm:^10.4.19": - version: 10.4.20 - resolution: "autoprefixer@npm:10.4.20" - dependencies: - browserslist: "npm:^4.23.3" - caniuse-lite: "npm:^1.0.30001646" - fraction.js: "npm:^4.3.7" - normalize-range: "npm:^0.1.2" - picocolors: "npm:^1.0.1" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.1.0 - bin: - autoprefixer: bin/autoprefixer - checksum: 10c0/e1f00978a26e7c5b54ab12036d8c13833fad7222828fc90914771b1263f51b28c7ddb5803049de4e77696cbd02bb25cfc3634e80533025bb26c26aacdf938940 - languageName: node - linkType: hard - "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -20176,19 +19395,6 @@ __metadata: languageName: node linkType: hard -"babel-loader@npm:^9.1.3": - version: 9.1.3 - resolution: "babel-loader@npm:9.1.3" - dependencies: - find-cache-dir: "npm:^4.0.0" - schema-utils: "npm:^4.0.0" - peerDependencies: - "@babel/core": ^7.12.0 - webpack: ">=5" - checksum: 10c0/e3fc3c9e02bd908b37e8e8cd4f3d7280cf6ac45e33fc203aedbb615135a0fecc33bf92573b71a166a827af029d302c0b060354985cd91d510320bd70a2f949eb - languageName: node - linkType: hard - "babel-merge@npm:^3.0.0": version: 3.0.0 resolution: "babel-merge@npm:3.0.0" @@ -20232,15 +19438,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-dynamic-import-node@npm:^2.3.3": - version: 2.3.3 - resolution: "babel-plugin-dynamic-import-node@npm:2.3.3" - dependencies: - object.assign: "npm:^4.1.0" - checksum: 10c0/1bd80df981e1fc1aff0cd4e390cf27aaa34f95f7620cd14dff07ba3bad56d168c098233a7d2deb2c9b1dc13643e596a6b94fc608a3412ee3c56e74a25cd2167e - languageName: node - linkType: hard - "babel-plugin-istanbul@npm:^6.1.1": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" @@ -21106,13 +20303,6 @@ __metadata: languageName: node linkType: hard -"batch@npm:0.6.1": - version: 0.6.1 - resolution: "batch@npm:0.6.1" - checksum: 10c0/925a13897b4db80d4211082fe287bcf96d297af38e26448c857cee3e095c9792e3b8f26b37d268812e7f38a589f694609de8534a018b1937d7dc9f84e6b387c5 - languageName: node - linkType: hard - "bcrypt-pbkdf@npm:^1.0.0": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -21320,16 +20510,6 @@ __metadata: languageName: node linkType: hard -"bonjour-service@npm:^1.0.11": - version: 1.2.1 - resolution: "bonjour-service@npm:1.2.1" - dependencies: - fast-deep-equal: "npm:^3.1.3" - multicast-dns: "npm:^7.2.5" - checksum: 10c0/953cbfc27fc9e36e6f988012993ab2244817d82426603e0390d4715639031396c932b6657b1aa4ec30dbb5fa903d6b2c7f1be3af7a8ba24165c93e987c849730 - languageName: node - linkType: hard - "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -21360,38 +20540,6 @@ __metadata: languageName: node linkType: hard -"boxen@npm:^6.2.1": - version: 6.2.1 - resolution: "boxen@npm:6.2.1" - dependencies: - ansi-align: "npm:^3.0.1" - camelcase: "npm:^6.2.0" - chalk: "npm:^4.1.2" - cli-boxes: "npm:^3.0.0" - string-width: "npm:^5.0.1" - type-fest: "npm:^2.5.0" - widest-line: "npm:^4.0.1" - wrap-ansi: "npm:^8.0.1" - checksum: 10c0/2a50d059c950a50d9f3c873093702747740814ce8819225c4f8cbe92024c9f5a9219d2b7128f5cfa17c022644d929bbbc88b9591de67249c6ebe07f7486bdcfd - languageName: node - linkType: hard - -"boxen@npm:^7.0.0": - version: 7.1.1 - resolution: "boxen@npm:7.1.1" - dependencies: - ansi-align: "npm:^3.0.1" - camelcase: "npm:^7.0.1" - chalk: "npm:^5.2.0" - cli-boxes: "npm:^3.0.0" - string-width: "npm:^5.1.2" - type-fest: "npm:^2.13.0" - widest-line: "npm:^4.0.1" - wrap-ansi: "npm:^8.1.0" - checksum: 10c0/3a9891dc98ac40d582c9879e8165628258e2c70420c919e70fff0a53ccc7b42825e73cda6298199b2fbc1f41f5d5b93b492490ad2ae27623bed3897ddb4267f8 - languageName: node - linkType: hard - "bplist-parser@npm:^0.2.0": version: 0.2.0 resolution: "bplist-parser@npm:0.2.0" @@ -21558,7 +20706,7 @@ __metadata: languageName: node linkType: hard -"browserify-zlib@npm:^0.2.0, browserify-zlib@npm:~0.2.0": +"browserify-zlib@npm:~0.2.0": version: 0.2.0 resolution: "browserify-zlib@npm:0.2.0" dependencies: @@ -21625,7 +20773,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.18.1, browserslist@npm:^4.21.10, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": +"browserslist@npm:^4.14.5, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": version: 4.23.3 resolution: "browserslist@npm:4.23.3" dependencies: @@ -21937,28 +21085,6 @@ __metadata: languageName: node linkType: hard -"cacheable-lookup@npm:^7.0.0": - version: 7.0.0 - resolution: "cacheable-lookup@npm:7.0.0" - checksum: 10c0/63a9c144c5b45cb5549251e3ea774c04d63063b29e469f7584171d059d3a88f650f47869a974e2d07de62116463d742c287a81a625e791539d987115cb081635 - languageName: node - linkType: hard - -"cacheable-request@npm:^10.2.8": - version: 10.2.14 - resolution: "cacheable-request@npm:10.2.14" - dependencies: - "@types/http-cache-semantics": "npm:^4.0.2" - get-stream: "npm:^6.0.1" - http-cache-semantics: "npm:^4.1.1" - keyv: "npm:^4.5.3" - mimic-response: "npm:^4.0.0" - normalize-url: "npm:^8.0.0" - responselike: "npm:^3.0.0" - checksum: 10c0/41b6658db369f20c03128227ecd219ca7ac52a9d24fc0f499cc9aa5d40c097b48b73553504cebd137024d957c0ddb5b67cf3ac1439b136667f3586257763f88d - languageName: node - linkType: hard - "cacheable-request@npm:^6.0.0": version: 6.1.0 resolution: "cacheable-request@npm:6.1.0" @@ -22075,19 +21201,7 @@ __metadata: languageName: node linkType: hard -"caniuse-api@npm:^3.0.0": - version: 3.0.0 - resolution: "caniuse-api@npm:3.0.0" - dependencies: - browserslist: "npm:^4.0.0" - caniuse-lite: "npm:^1.0.0" - lodash.memoize: "npm:^4.1.2" - lodash.uniq: "npm:^4.5.0" - checksum: 10c0/60f9e85a3331e6d761b1b03eec71ca38ef7d74146bece34694853033292156b815696573ed734b65583acf493e88163618eda915c6c826d46a024c71a9572b4c - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001646": +"caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001646": version: 1.0.30001651 resolution: "caniuse-lite@npm:1.0.30001651" checksum: 10c0/7821278952a6dbd17358e5d08083d258f092e2a530f5bc1840657cb140fbbc5ec44293bc888258c44a18a9570cde149ed05819ac8320b9710cf22f699891e6ad @@ -22197,7 +21311,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.0.1, chalk@npm:^5.2.0, chalk@npm:^5.3.0": +"chalk@npm:^5.2.0, chalk@npm:^5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09 @@ -22367,7 +21481,7 @@ __metadata: languageName: node linkType: hard -"cheerio@npm:^1.0.0-rc.10, cheerio@npm:^1.0.0-rc.12": +"cheerio@npm:^1.0.0-rc.10": version: 1.0.0-rc.12 resolution: "cheerio@npm:1.0.0-rc.12" dependencies: @@ -22401,7 +21515,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.6.0, chokidar@npm:^3.4.2, chokidar@npm:^3.5.1, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": +"chokidar@npm:3.6.0, chokidar@npm:^3.5.1, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -22549,15 +21663,6 @@ __metadata: languageName: node linkType: hard -"clean-css@npm:^5.2.2, clean-css@npm:^5.3.2, clean-css@npm:~5.3.2": - version: 5.3.3 - resolution: "clean-css@npm:5.3.3" - dependencies: - source-map: "npm:~0.6.0" - checksum: 10c0/381de7523e23f3762eb180e327dcc0cedafaf8cb1cd8c26b7cc1fc56e0829a92e734729c4f955394d65ed72fb62f82d8baf78af34b33b8a7d41ebad2accdd6fb - languageName: node - linkType: hard - "clean-regexp@npm:^1.0.0": version: 1.0.0 resolution: "clean-regexp@npm:1.0.0" @@ -22597,13 +21702,6 @@ __metadata: languageName: node linkType: hard -"cli-boxes@npm:^3.0.0": - version: 3.0.0 - resolution: "cli-boxes@npm:3.0.0" - checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 - languageName: node - linkType: hard - "cli-color@npm:^2.0.0": version: 2.0.4 resolution: "cli-color@npm:2.0.4" @@ -22951,13 +22049,6 @@ __metadata: languageName: node linkType: hard -"collapse-white-space@npm:^2.0.0": - version: 2.1.0 - resolution: "collapse-white-space@npm:2.1.0" - checksum: 10c0/b2e2800f4ab261e62eb27a1fbe853378296e3a726d6695117ed033e82d61fb6abeae4ffc1465d5454499e237005de9cfc52c9562dc7ca4ac759b9a222ef14453 - languageName: node - linkType: hard - "collect-v8-coverage@npm:^1.0.0": version: 1.0.2 resolution: "collect-v8-coverage@npm:1.0.2" @@ -23026,14 +22117,7 @@ __metadata: languageName: node linkType: hard -"colord@npm:^2.9.3": - version: 2.9.3 - resolution: "colord@npm:2.9.3" - checksum: 10c0/9699e956894d8996b28c686afe8988720785f476f59335c80ce852ded76ab3ebe252703aec53d9bef54f6219aea6b960fb3d9a8300058a1d0c0d4026460cd110 - languageName: node - linkType: hard - -"colorette@npm:^2.0.10, colorette@npm:^2.0.16, colorette@npm:^2.0.20": +"colorette@npm:^2.0.16, colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 @@ -23064,13 +22148,6 @@ __metadata: languageName: node linkType: hard -"combine-promises@npm:^1.1.0": - version: 1.2.0 - resolution: "combine-promises@npm:1.2.0" - checksum: 10c0/906ebf056006eff93c11548df0415053b6756145dae1f5a89579e743cb15fceeb0604555791321db4fba5072aa39bb4de6547e9cdf14589fe949b33d1613422c - languageName: node - linkType: hard - "combine-source-map@npm:^0.8.0, combine-source-map@npm:~0.8.0": version: 0.8.0 resolution: "combine-source-map@npm:0.8.0" @@ -23196,13 +22273,6 @@ __metadata: languageName: node linkType: hard -"common-path-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "common-path-prefix@npm:3.0.0" - checksum: 10c0/c4a74294e1b1570f4a8ab435285d185a03976c323caa16359053e749db4fde44e3e6586c29cd051100335e11895767cbbd27ea389108e327d62f38daf4548fdb - languageName: node - linkType: hard - "common-tags@npm:1.8.2": version: 1.8.2 resolution: "common-tags@npm:1.8.2" @@ -23360,7 +22430,7 @@ __metadata: languageName: node linkType: hard -"config-chain@npm:^1.1.11, config-chain@npm:^1.1.13": +"config-chain@npm:^1.1.13": version: 1.1.13 resolution: "config-chain@npm:1.1.13" dependencies: @@ -23384,19 +22454,6 @@ __metadata: languageName: node linkType: hard -"configstore@npm:^6.0.0": - version: 6.0.0 - resolution: "configstore@npm:6.0.0" - dependencies: - dot-prop: "npm:^6.0.1" - graceful-fs: "npm:^4.2.6" - unique-string: "npm:^3.0.0" - write-file-atomic: "npm:^3.0.3" - xdg-basedir: "npm:^5.0.1" - checksum: 10c0/6681a96038ab3e0397cbdf55e6e1624ac3dfa3afe955e219f683df060188a418bda043c9114a59a337e7aec9562b0a0c838ed7db24289e6d0c266bc8313b9580 - languageName: node - linkType: hard - "confusing-browser-globals@npm:^1.0.9": version: 1.0.11 resolution: "confusing-browser-globals@npm:1.0.11" @@ -23404,13 +22461,6 @@ __metadata: languageName: node linkType: hard -"connect-history-api-fallback@npm:^2.0.0": - version: 2.0.0 - resolution: "connect-history-api-fallback@npm:2.0.0" - checksum: 10c0/90fa8b16ab76e9531646cc70b010b1dbd078153730c510d3142f6cf07479ae8a812c5a3c0e40a28528dd1681a62395d0cfdef67da9e914c4772ac85d69a3ed87 - languageName: node - linkType: hard - "connect-injector@npm:^0.4.4": version: 0.4.4 resolution: "connect-injector@npm:0.4.4" @@ -23423,7 +22473,7 @@ __metadata: languageName: node linkType: hard -"consola@npm:^2.15.0, consola@npm:^2.15.3": +"consola@npm:^2.15.0": version: 2.15.3 resolution: "consola@npm:2.15.3" checksum: 10c0/34a337e6b4a1349ee4d7b4c568484344418da8fdb829d7d71bfefcd724f608f273987633b6eef465e8de510929907a092e13cb7a28a5d3acb3be446fcc79fd5e @@ -23437,7 +22487,7 @@ __metadata: languageName: node linkType: hard -"console-browserify@npm:^1.1.0, console-browserify@npm:^1.2.0": +"console-browserify@npm:^1.1.0": version: 1.2.0 resolution: "console-browserify@npm:1.2.0" checksum: 10c0/89b99a53b7d6cee54e1e64fa6b1f7ac24b844b4019c5d39db298637e55c1f4ffa5c165457ad984864de1379df2c8e1886cbbdac85d9dbb6876a9f26c3106f226 @@ -23462,20 +22512,13 @@ __metadata: languageName: node linkType: hard -"constants-browserify@npm:^1.0.0, constants-browserify@npm:~1.0.0": +"constants-browserify@npm:~1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" checksum: 10c0/ab49b1d59a433ed77c964d90d19e08b2f77213fb823da4729c0baead55e3c597f8f97ebccfdfc47bd896d43854a117d114c849a6f659d9986420e97da0f83ac5 languageName: node linkType: hard -"content-disposition@npm:0.5.2": - version: 0.5.2 - resolution: "content-disposition@npm:0.5.2" - checksum: 10c0/49eebaa0da1f9609b192e99d7fec31d1178cb57baa9d01f5b63b29787ac31e9d18b5a1033e854c68c9b6cce790e700a6f7fa60e43f95e2e416404e114a8f2f49 - languageName: node - linkType: hard - "content-disposition@npm:0.5.4, content-disposition@npm:^0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -23550,13 +22593,6 @@ __metadata: languageName: node linkType: hard -"copy-text-to-clipboard@npm:^3.2.0": - version: 3.2.0 - resolution: "copy-text-to-clipboard@npm:3.2.0" - checksum: 10c0/d60fdadc59d526e19d56ad23cec2b292d33c771a5091621bd322d138804edd3c10eb2367d46ec71b39f5f7f7116a2910b332281aeb36a5b679199d746a8a5381 - languageName: node - linkType: hard - "copy-to-clipboard@npm:^3.2.0, copy-to-clipboard@npm:^3.3.1": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -23566,22 +22602,6 @@ __metadata: languageName: node linkType: hard -"copy-webpack-plugin@npm:^11.0.0": - version: 11.0.0 - resolution: "copy-webpack-plugin@npm:11.0.0" - dependencies: - fast-glob: "npm:^3.2.11" - glob-parent: "npm:^6.0.1" - globby: "npm:^13.1.1" - normalize-path: "npm:^3.0.0" - schema-utils: "npm:^4.0.0" - serialize-javascript: "npm:^6.0.0" - peerDependencies: - webpack: ^5.1.0 - checksum: 10c0/a667dd226b26f148584a35fb705f5af926d872584912cf9fd203c14f2b3a68f473a1f5cf768ec1dd5da23820823b850e5d50458b685c468e4a224b25c12a15b4 - languageName: node - linkType: hard - "core-js-compat@npm:^3.34.0, core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": version: 3.38.0 resolution: "core-js-compat@npm:3.38.0" @@ -23605,7 +22625,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.31.1, core-js@npm:^3.8.2": +"core-js@npm:^3.8.2": version: 3.38.0 resolution: "core-js@npm:3.38.0" checksum: 10c0/3218ae19bfe0c6560663012cbd3e7f3dc1b36d50fc71e8c365f3b119185e8a35ac4e8bb9698ae510b3c201ef93f40bdc29f9215716ccf31aca28f77969bb4ed0 @@ -23655,7 +22675,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:8.3.6, cosmiconfig@npm:^8.0.0, cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0, cosmiconfig@npm:^8.3.5": +"cosmiconfig@npm:8.3.6, cosmiconfig@npm:^8.0.0, cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -23887,7 +22907,7 @@ __metadata: languageName: node linkType: hard -"crypto-browserify@npm:^3.0.0, crypto-browserify@npm:^3.12.0": +"crypto-browserify@npm:^3.0.0": version: 3.12.0 resolution: "crypto-browserify@npm:3.12.0" dependencies: @@ -23913,15 +22933,6 @@ __metadata: languageName: node linkType: hard -"crypto-random-string@npm:^4.0.0": - version: 4.0.0 - resolution: "crypto-random-string@npm:4.0.0" - dependencies: - type-fest: "npm:^1.0.1" - checksum: 10c0/16e11a3c8140398f5408b7fded35a961b9423c5dac39a60cbbd08bd3f0e07d7de130e87262adea7db03ec1a7a4b7551054e0db07ee5408b012bac5400cfc07a5 - languageName: node - linkType: hard - "css-box-model@npm:^1.2.1": version: 1.2.1 resolution: "css-box-model@npm:1.2.1" @@ -23931,15 +22942,6 @@ __metadata: languageName: node linkType: hard -"css-declaration-sorter@npm:^7.2.0": - version: 7.2.0 - resolution: "css-declaration-sorter@npm:7.2.0" - peerDependencies: - postcss: ^8.0.9 - checksum: 10c0/d8516be94f8f2daa233ef021688b965c08161624cbf830a4d7ee1099429437c0ee124d35c91b1c659cfd891a68e8888aa941726dab12279bc114aaed60a94606 - languageName: node - linkType: hard - "css-in-js-utils@npm:^3.1.0": version: 3.1.0 resolution: "css-in-js-utils@npm:3.1.0" @@ -23949,9 +22951,9 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:^6.8.1": - version: 6.11.0 - resolution: "css-loader@npm:6.11.0" +"css-loader@npm:^7.1.2": + version: 7.1.2 + resolution: "css-loader@npm:7.1.2" dependencies: icss-utils: "npm:^5.1.0" postcss: "npm:^8.4.33" @@ -23963,13 +22965,13 @@ __metadata: semver: "npm:^7.5.4" peerDependencies: "@rspack/core": 0.x || 1.x - webpack: ^5.0.0 + webpack: ^5.27.0 peerDependenciesMeta: "@rspack/core": optional: true webpack: optional: true - checksum: 10c0/bb52434138085fed06a33e2ffbdae9ee9014ad23bf60f59d6b7ee67f28f26c6b1764024d3030bd19fd884d6ee6ee2224eaed64ad19eb18fbbb23d148d353a965 + checksum: 10c0/edec9ed71e3c416c9c6ad41c138834c94baf7629de3b97a3337ae8cec4a45e05c57bdb7c4b4d267229fc04b8970d0d1c0734ded8dcd0ac8c7c286b36facdbbf0 languageName: node linkType: hard @@ -23980,48 +22982,6 @@ __metadata: languageName: node linkType: hard -"css-minimizer-webpack-plugin@npm:^5.0.1": - version: 5.0.1 - resolution: "css-minimizer-webpack-plugin@npm:5.0.1" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.18" - cssnano: "npm:^6.0.1" - jest-worker: "npm:^29.4.3" - postcss: "npm:^8.4.24" - schema-utils: "npm:^4.0.1" - serialize-javascript: "npm:^6.0.1" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - "@parcel/css": - optional: true - "@swc/css": - optional: true - clean-css: - optional: true - csso: - optional: true - esbuild: - optional: true - lightningcss: - optional: true - checksum: 10c0/1792259e18f7c5ee25b6bbf60b38b64201747add83d1f751c8c654159b46ebacd0d1103d35f17d97197033e21e02d2ba4a4e9aa14c9c0d067b7c7653c721814e - languageName: node - linkType: hard - -"css-select@npm:^4.1.3": - version: 4.3.0 - resolution: "css-select@npm:4.3.0" - dependencies: - boolbase: "npm:^1.0.0" - css-what: "npm:^6.0.1" - domhandler: "npm:^4.3.1" - domutils: "npm:^2.8.0" - nth-check: "npm:^2.0.1" - checksum: 10c0/a489d8e5628e61063d5a8fe0fa1cc7ae2478cb334a388a354e91cf2908154be97eac9fa7ed4dffe87a3e06cf6fcaa6016553115335c4fd3377e13dac7bd5a8e1 - languageName: node - linkType: hard - "css-select@npm:^5.1.0": version: 5.1.0 resolution: "css-select@npm:5.1.0" @@ -24072,7 +23032,7 @@ __metadata: languageName: node linkType: hard -"css-what@npm:^6.0.1, css-what@npm:^6.1.0": +"css-what@npm:^6.1.0": version: 6.1.0 resolution: "css-what@npm:6.1.0" checksum: 10c0/a09f5a6b14ba8dcf57ae9a59474722e80f20406c53a61e9aedb0eedc693b135113ffe2983f4efc4b5065ae639442e9ae88df24941ef159c218b231011d733746 @@ -24102,84 +23062,6 @@ __metadata: languageName: node linkType: hard -"cssnano-preset-advanced@npm:^6.1.2": - version: 6.1.2 - resolution: "cssnano-preset-advanced@npm:6.1.2" - dependencies: - autoprefixer: "npm:^10.4.19" - browserslist: "npm:^4.23.0" - cssnano-preset-default: "npm:^6.1.2" - postcss-discard-unused: "npm:^6.0.5" - postcss-merge-idents: "npm:^6.0.3" - postcss-reduce-idents: "npm:^6.0.3" - postcss-zindex: "npm:^6.0.2" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/22d3ddab258e6b31e7e2e7c48712f023b60fadb2813929752dace0326e28cd250830b5420a33f81b01df52d2460cb5f999fff5907f58508809efe1a8a739a707 - languageName: node - linkType: hard - -"cssnano-preset-default@npm:^6.1.2": - version: 6.1.2 - resolution: "cssnano-preset-default@npm:6.1.2" - dependencies: - browserslist: "npm:^4.23.0" - css-declaration-sorter: "npm:^7.2.0" - cssnano-utils: "npm:^4.0.2" - postcss-calc: "npm:^9.0.1" - postcss-colormin: "npm:^6.1.0" - postcss-convert-values: "npm:^6.1.0" - postcss-discard-comments: "npm:^6.0.2" - postcss-discard-duplicates: "npm:^6.0.3" - postcss-discard-empty: "npm:^6.0.3" - postcss-discard-overridden: "npm:^6.0.2" - postcss-merge-longhand: "npm:^6.0.5" - postcss-merge-rules: "npm:^6.1.1" - postcss-minify-font-values: "npm:^6.1.0" - postcss-minify-gradients: "npm:^6.0.3" - postcss-minify-params: "npm:^6.1.0" - postcss-minify-selectors: "npm:^6.0.4" - postcss-normalize-charset: "npm:^6.0.2" - postcss-normalize-display-values: "npm:^6.0.2" - postcss-normalize-positions: "npm:^6.0.2" - postcss-normalize-repeat-style: "npm:^6.0.2" - postcss-normalize-string: "npm:^6.0.2" - postcss-normalize-timing-functions: "npm:^6.0.2" - postcss-normalize-unicode: "npm:^6.1.0" - postcss-normalize-url: "npm:^6.0.2" - postcss-normalize-whitespace: "npm:^6.0.2" - postcss-ordered-values: "npm:^6.0.2" - postcss-reduce-initial: "npm:^6.1.0" - postcss-reduce-transforms: "npm:^6.0.2" - postcss-svgo: "npm:^6.0.3" - postcss-unique-selectors: "npm:^6.0.4" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/af99021f936763850f5f35dc9e6a9dfb0da30856dea36e0420b011da2a447099471db2a5f3d1f5f52c0489da186caf9a439d8f048a80f82617077efb018333fa - languageName: node - linkType: hard - -"cssnano-utils@npm:^4.0.2": - version: 4.0.2 - resolution: "cssnano-utils@npm:4.0.2" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/260b8c8ffa48b908aa77ef129f9b8648ecd92aed405b20e7fe6b8370779dd603530344fc9d96683d53533246e48b36ac9d2aa5a476b4f81c547bbad86d187f35 - languageName: node - linkType: hard - -"cssnano@npm:^6.0.1, cssnano@npm:^6.1.2": - version: 6.1.2 - resolution: "cssnano@npm:6.1.2" - dependencies: - cssnano-preset-default: "npm:^6.1.2" - lilconfig: "npm:^3.1.1" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/4df0dc0389b34b38acb09b7cfb07267b0eda95349c6d5e9b7666acc7200bb33359650869a60168e9d878298b05f4ad2c7f070815c90551720a3f4e1037f79691 - languageName: node - linkType: hard - "csso@npm:^5.0.5": version: 5.0.5 resolution: "csso@npm:5.0.5" @@ -24238,7 +23120,7 @@ __metadata: languageName: node linkType: hard -"d3-array@npm:2, d3-array@npm:^2.3.0": +"d3-array@npm:2": version: 2.12.1 resolution: "d3-array@npm:2.12.1" dependencies: @@ -24247,10 +23129,12 @@ __metadata: languageName: node linkType: hard -"d3-color@npm:1 - 2": - version: 2.0.0 - resolution: "d3-color@npm:2.0.0" - checksum: 10c0/5aa58dfb78e3db764373a904eabb643dc024ff6071128a41e86faafa100e0e17a796e06ac3f2662e9937242bb75b8286788629773d76936f11c17bd5fe5e15cd +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: "npm:1 - 2" + checksum: 10c0/08b95e91130f98c1375db0e0af718f4371ccacef7d5d257727fe74f79a24383e79aba280b9ffae655483ffbbad4fd1dec4ade0119d88c4749f388641c8bf8c50 languageName: node linkType: hard @@ -24261,6 +23145,15 @@ __metadata: languageName: node linkType: hard +"d3-delaunay@npm:^6.0.4": + version: 6.0.4 + resolution: "d3-delaunay@npm:6.0.4" + dependencies: + delaunator: "npm:5" + checksum: 10c0/57c3aecd2525664b07c4c292aa11cf49b2752c0cf3f5257f752999399fe3c592de2d418644d79df1f255471eec8057a9cc0c3062ed7128cb3348c45f69597754 + languageName: node + linkType: hard + "d3-dispatch@npm:1 - 3": version: 3.0.1 resolution: "d3-dispatch@npm:3.0.1" @@ -24285,10 +23178,10 @@ __metadata: languageName: node linkType: hard -"d3-format@npm:1 - 2": - version: 2.0.0 - resolution: "d3-format@npm:2.0.0" - checksum: 10c0/c869af459e20767dc3d9cbb2946ba79cc266ae4fb35d11c50c63fc89ea4ed168c702c7e3db94d503b3618de9609bf3bf2d855ef53e21109ddd7eb9c8f3fcf8a1 +"d3-format@npm:1 - 3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: 10c0/049f5c0871ebce9859fc5e2f07f336b3c5bfff52a2540e0bac7e703fce567cd9346f4ad1079dd18d6f1e0eaa0599941c1810898926f10ac21a31fd0a34b4aa75 languageName: node linkType: hard @@ -24299,16 +23192,7 @@ __metadata: languageName: node linkType: hard -"d3-interpolate@npm:1 - 2, d3-interpolate@npm:1.2.0 - 2": - version: 2.0.1 - resolution: "d3-interpolate@npm:2.0.1" - dependencies: - d3-color: "npm:1 - 2" - checksum: 10c0/2a5725b0c9c7fef3e8878cf75ad67be851b1472de3dda1f694c441786a1a32e198ddfaa6880d6b280401c1af5b844b61ccdd63d85d1607c1e6bb3a3f0bf532ea - languageName: node - linkType: hard - -"d3-interpolate@npm:1 - 3, d3-interpolate@npm:^3.0.1": +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:^3.0.1": version: 3.0.1 resolution: "d3-interpolate@npm:3.0.1" dependencies: @@ -24317,20 +23201,10 @@ __metadata: languageName: node linkType: hard -"d3-path@npm:1": - version: 1.0.9 - resolution: "d3-path@npm:1.0.9" - checksum: 10c0/e35e84df5abc18091f585725b8235e1fa97efc287571585427d3a3597301e6c506dea56b11dfb3c06ca5858b3eb7f02c1bf4f6a716aa9eade01c41b92d497eb5 - languageName: node - linkType: hard - -"d3-scale-chromatic@npm:^2.0.0": - version: 2.0.0 - resolution: "d3-scale-chromatic@npm:2.0.0" - dependencies: - d3-color: "npm:1 - 2" - d3-interpolate: "npm:1 - 2" - checksum: 10c0/93cafe497b00046b1d4e237a8bb8981fbb35ba03070f420bd913872f6e9d2c9628ed8bb8c84c6a6ffe16029359fa74b646c5c5129732ef4186ab059a77da3021 +"d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 10c0/dc1d58ec87fa8319bd240cf7689995111a124b141428354e9637aa83059eb12e681f77187e0ada5dedfce346f7e3d1f903467ceb41b379bfd01cd8e31721f5da languageName: node linkType: hard @@ -24344,16 +23218,16 @@ __metadata: languageName: node linkType: hard -"d3-scale@npm:^3.2.3": - version: 3.3.0 - resolution: "d3-scale@npm:3.3.0" +"d3-scale@npm:^4.0.2": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" dependencies: - d3-array: "npm:^2.3.0" - d3-format: "npm:1 - 2" - d3-interpolate: "npm:1.2.0 - 2" - d3-time: "npm:^2.1.1" - d3-time-format: "npm:2 - 3" - checksum: 10c0/cb63c271ec9c5b632c245c63e0d0716b32adcc468247972c552f5be62fb34a17f71e4ac29fd8976704369f4b958bc6789c61a49427efe2160ae979d7843569dc + d3-array: "npm:2.10.0 - 3" + d3-format: "npm:1 - 3" + d3-interpolate: "npm:1.2.0 - 3" + d3-time: "npm:2.1.1 - 3" + d3-time-format: "npm:2 - 4" + checksum: 10c0/65d9ad8c2641aec30ed5673a7410feb187a224d6ca8d1a520d68a7d6eac9d04caedbff4713d1e8545be33eb7fec5739983a7ab1d22d4e5ad35368c6729d362f1 languageName: node linkType: hard @@ -24364,16 +23238,25 @@ __metadata: languageName: node linkType: hard -"d3-shape@npm:^1.3.5": - version: 1.3.7 - resolution: "d3-shape@npm:1.3.7" +"d3-shape@npm:^3.2.0": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: "npm:^3.1.0" + checksum: 10c0/f1c9d1f09926daaf6f6193ae3b4c4b5521e81da7d8902d24b38694517c7f527ce3c9a77a9d3a5722ad1e3ff355860b014557b450023d66a944eabf8cfde37132 + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" dependencies: - d3-path: "npm:1" - checksum: 10c0/548057ce59959815decb449f15632b08e2a1bdce208f9a37b5f98ec7629dda986c2356bc7582308405ce68aedae7d47b324df41507404df42afaf352907577ae + d3-time: "npm:1 - 3" + checksum: 10c0/735e00fb25a7fd5d418fac350018713ae394eefddb0d745fab12bbff0517f9cdb5f807c7bbe87bb6eeb06249662f8ea84fec075f7d0cd68609735b2ceb29d206 languageName: node linkType: hard -"d3-time-format@npm:2 - 3, d3-time-format@npm:^3.0.0": +"d3-time-format@npm:^3.0.0": version: 3.0.0 resolution: "d3-time-format@npm:3.0.0" dependencies: @@ -24382,7 +23265,7 @@ __metadata: languageName: node linkType: hard -"d3-time@npm:1 - 2, d3-time@npm:^2.1.1": +"d3-time@npm:1 - 2": version: 2.1.1 resolution: "d3-time@npm:2.1.1" dependencies: @@ -24391,7 +23274,16 @@ __metadata: languageName: node linkType: hard -"d3-time@npm:^1.0.10": +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: "npm:2 - 3" + checksum: 10c0/a984f77e1aaeaa182679b46fbf57eceb6ebdb5f67d7578d6f68ef933f8eeb63737c0949991618a8d29472dbf43736c7d7f17c452b2770f8c1271191cba724ca1 + languageName: node + linkType: hard + +"d3-time@npm:^1.0.10, d3-time@npm:^1.0.11": version: 1.1.0 resolution: "d3-time@npm:1.1.0" checksum: 10c0/69ab137adff5b22d0fa148ea514a207bd9cd7d2c042ccf34a268f2ef73720b404f0be6e7b56c95650c53caf52080b5254e2a27f0a676f41d1dd22ef8872c8335 @@ -24660,7 +23552,7 @@ __metadata: languageName: node linkType: hard -"debounce@npm:^1.2.0, debounce@npm:^1.2.1": +"debounce@npm:^1.2.0": version: 1.2.1 resolution: "debounce@npm:1.2.1" checksum: 10c0/6c9320aa0973fc42050814621a7a8a78146c1975799b5b3cc1becf1f77ba9a5aa583987884230da0842a03f385def452fad5d60db97c3d1c8b824e38a8edf500 @@ -24674,7 +23566,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.0.0, debug@npm:^2.6.0, debug@npm:^2.6.8, debug@npm:^2.6.9": +"debug@npm:2.6.9, debug@npm:^2.0.0, debug@npm:^2.6.8, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -24863,15 +23755,6 @@ __metadata: languageName: node linkType: hard -"default-gateway@npm:^6.0.3": - version: 6.0.3 - resolution: "default-gateway@npm:6.0.3" - dependencies: - execa: "npm:^5.0.0" - checksum: 10c0/5184f9e6e105d24fb44ade9e8741efa54bb75e84625c1ea78c4ef8b81dff09ca52d6dbdd1185cf0dc655bb6b282a64fffaf7ed2dd561b8d9ad6f322b1f039aba - languageName: node - linkType: hard - "default-require-extensions@npm:^3.0.0": version: 3.0.1 resolution: "default-require-extensions@npm:3.0.1" @@ -24897,7 +23780,7 @@ __metadata: languageName: node linkType: hard -"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1": +"defer-to-connect@npm:^2.0.0": version: 2.0.1 resolution: "defer-to-connect@npm:2.0.1" checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 @@ -24947,7 +23830,7 @@ __metadata: languageName: node linkType: hard -"del@npm:^6.0.0, del@npm:^6.1.1": +"del@npm:^6.0.0": version: 6.1.1 resolution: "del@npm:6.1.1" dependencies: @@ -24963,6 +23846,15 @@ __metadata: languageName: node linkType: hard +"delaunator@npm:5": + version: 5.0.1 + resolution: "delaunator@npm:5.0.1" + dependencies: + robust-predicates: "npm:^3.0.2" + checksum: 10c0/3d7ea4d964731c5849af33fec0a271bc6753487b331fd7d43ccb17d77834706e1c383e6ab8fda0032da955e7576d1083b9603cdaf9cbdfd6b3ebd1fb8bb675a5 + languageName: node + linkType: hard + "delay@npm:^5.0.0": version: 5.0.0 resolution: "delay@npm:5.0.0" @@ -25119,19 +24011,6 @@ __metadata: languageName: node linkType: hard -"detect-port-alt@npm:^1.1.6": - version: 1.1.6 - resolution: "detect-port-alt@npm:1.1.6" - dependencies: - address: "npm:^1.0.1" - debug: "npm:^2.6.0" - bin: - detect: ./bin/detect-port - detect-port: ./bin/detect-port - checksum: 10c0/7269e6aef7b782d98c77505c07a7a0f5e2ee98a9607dc791035fc0192fc58aa03cc833fae605e10eaf239a2a5a55cd938e0bb141dea764ac6180ca082fd62b23 - languageName: node - linkType: hard - "detect-port@npm:^1.3.0, detect-port@npm:^1.5.1": version: 1.6.1 resolution: "detect-port@npm:1.6.1" @@ -25158,15 +24037,6 @@ __metadata: languageName: node linkType: hard -"devlop@npm:^1.0.0, devlop@npm:^1.1.0": - version: 1.1.0 - resolution: "devlop@npm:1.1.0" - dependencies: - dequal: "npm:^2.0.0" - checksum: 10c0/e0928ab8f94c59417a2b8389c45c55ce0a02d9ac7fd74ef62d01ba48060129e1d594501b77de01f3eeafc7cb00773819b0df74d96251cf20b31c5b3071f45c0e - languageName: node - linkType: hard - "dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -25252,15 +24122,6 @@ __metadata: languageName: node linkType: hard -"dns-packet@npm:^5.2.2": - version: 5.6.1 - resolution: "dns-packet@npm:5.6.1" - dependencies: - "@leichtgewicht/ip-codec": "npm:^2.0.1" - checksum: 10c0/8948d3d03063fb68e04a1e386875f8c3bcc398fc375f535f2b438fad8f41bf1afa6f5e70893ba44f4ae884c089247e0a31045722fa6ff0f01d228da103f1811d - languageName: node - linkType: hard - "doc-path@npm:4.1.1": version: 4.1.1 resolution: "doc-path@npm:4.1.1" @@ -25286,19 +24147,6 @@ __metadata: languageName: node linkType: hard -"docusaurus-node-polyfills@npm:^1.0.0": - version: 1.0.0 - resolution: "docusaurus-node-polyfills@npm:1.0.0" - dependencies: - node-polyfill-webpack-plugin: "npm:^1.1.2" - os-browserify: "npm:^0.3.0" - process: "npm:^0.11.10" - peerDependencies: - webpack: ">=5" - checksum: 10c0/f25f5a18d79b192252fada137b337195f4b1ed5f97959b17061276fa36147871239c00c1d3c260b0822b391617fa63daed73133565ceecee9992a340db8cdeb1 - languageName: node - linkType: hard - "dom-accessibility-api@npm:^0.5.9": version: 0.5.16 resolution: "dom-accessibility-api@npm:0.5.16" @@ -25313,15 +24161,6 @@ __metadata: languageName: node linkType: hard -"dom-converter@npm:^0.2.0": - version: 0.2.0 - resolution: "dom-converter@npm:0.2.0" - dependencies: - utila: "npm:~0.4" - checksum: 10c0/e96aa63bd8c6ee3cd9ce19c3aecfc2c42e50a460e8087114794d4f5ecf3a4f052b34ea3bf2d73b5d80b4da619073b49905e6d7d788ceb7814ca4c29be5354a11 - languageName: node - linkType: hard - "dom-helpers@npm:^3.3.1": version: 3.4.0 resolution: "dom-helpers@npm:3.4.0" @@ -25341,17 +24180,6 @@ __metadata: languageName: node linkType: hard -"dom-serializer@npm:^1.0.1": - version: 1.4.1 - resolution: "dom-serializer@npm:1.4.1" - dependencies: - domelementtype: "npm:^2.0.1" - domhandler: "npm:^4.2.0" - entities: "npm:^2.0.0" - checksum: 10c0/67d775fa1ea3de52035c98168ddcd59418356943b5eccb80e3c8b3da53adb8e37edb2cc2f885802b7b1765bf5022aec21dfc32910d7f9e6de4c3148f095ab5e0 - languageName: node - linkType: hard - "dom-serializer@npm:^2.0.0": version: 2.0.0 resolution: "dom-serializer@npm:2.0.0" @@ -25370,13 +24198,6 @@ __metadata: languageName: node linkType: hard -"domain-browser@npm:^4.19.0": - version: 4.23.0 - resolution: "domain-browser@npm:4.23.0" - checksum: 10c0/dfcc6ba070a2c968a4d922e7d99ef440d1076812af0d983404aadf64729f746bb4a0ad2c5e73ccd5d9cf41bc79037f2a1e4a915bdf33d07e0d77f487b635b5b2 - languageName: node - linkType: hard - "domelementtype@npm:1, domelementtype@npm:^1.3.1": version: 1.3.1 resolution: "domelementtype@npm:1.3.1" @@ -25384,7 +24205,7 @@ __metadata: languageName: node linkType: hard -"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 @@ -25409,15 +24230,6 @@ __metadata: languageName: node linkType: hard -"domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": - version: 4.3.1 - resolution: "domhandler@npm:4.3.1" - dependencies: - domelementtype: "npm:^2.2.0" - checksum: 10c0/5c199c7468cb052a8b5ab80b13528f0db3d794c64fc050ba793b574e158e67c93f8336e87fd81e9d5ee43b0e04aea4d8b93ed7be4899cb726a1601b3ba18538b - languageName: node - linkType: hard - "domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": version: 5.0.3 resolution: "domhandler@npm:5.0.3" @@ -25444,17 +24256,6 @@ __metadata: languageName: node linkType: hard -"domutils@npm:^2.5.2, domutils@npm:^2.8.0": - version: 2.8.0 - resolution: "domutils@npm:2.8.0" - dependencies: - dom-serializer: "npm:^1.0.1" - domelementtype: "npm:^2.2.0" - domhandler: "npm:^4.2.0" - checksum: 10c0/d58e2ae01922f0dd55894e61d18119924d88091837887bf1438f2327f32c65eb76426bd9384f81e7d6dcfb048e0f83c19b222ad7101176ad68cdc9c695b563db - languageName: node - linkType: hard - "domutils@npm:^3.0.1": version: 3.1.0 resolution: "domutils@npm:3.1.0" @@ -25485,15 +24286,6 @@ __metadata: languageName: node linkType: hard -"dot-prop@npm:^6.0.1": - version: 6.0.1 - resolution: "dot-prop@npm:6.0.1" - dependencies: - is-obj: "npm:^2.0.0" - checksum: 10c0/30e51ec6408978a6951b21e7bc4938aad01a86f2fdf779efe52330205c6bb8a8ea12f35925c2029d6dc9d1df22f916f32f828ce1e9b259b1371c580541c22b5a - languageName: node - linkType: hard - "dotenv-cli@npm:^7.2.1": version: 7.4.2 resolution: "dotenv-cli@npm:7.4.2" @@ -25529,7 +24321,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3.0": +"dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3.0, dotenv@npm:^16.4.5": version: 16.4.5 resolution: "dotenv@npm:16.4.5" checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f @@ -25677,7 +24469,7 @@ __metadata: languageName: node linkType: hard -"duplexer@npm:^0.1.1, duplexer@npm:^0.1.2": +"duplexer@npm:^0.1.1": version: 0.1.2 resolution: "duplexer@npm:0.1.2" checksum: 10c0/c57bcd4bdf7e623abab2df43a7b5b23d18152154529d166c1e0da6bee341d84c432d157d7e97b32fecb1bf3a8b8857dd85ed81a915789f550637ed25b8e64fc2 @@ -25804,13 +24596,6 @@ __metadata: languageName: node linkType: hard -"emojilib@npm:^2.4.0": - version: 2.4.0 - resolution: "emojilib@npm:2.4.0" - checksum: 10c0/6e66ba8921175842193f974e18af448bb6adb0cf7aeea75e08b9d4ea8e9baba0e4a5347b46ed901491dcaba277485891c33a8d70b0560ca5cc9672a94c21ab8f - languageName: node - linkType: hard - "emojis-list@npm:^3.0.0": version: 3.0.0 resolution: "emojis-list@npm:3.0.0" @@ -25818,13 +24603,6 @@ __metadata: languageName: node linkType: hard -"emoticon@npm:^4.0.1": - version: 4.1.0 - resolution: "emoticon@npm:4.1.0" - checksum: 10c0/b3bc0a9b370445ac1e980ccba7baea614b4648199cc6fa0a51696a6d2393733e8f985edc4f1af381a1903f625789483dd155de427ec9fa2ea415fac116adc06d - languageName: node - linkType: hard - "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -25850,7 +24628,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.12.0, enhanced-resolve@npm:^5.14.0, enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.17.0, enhanced-resolve@npm:^5.7.0": +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.12.0, enhanced-resolve@npm:^5.14.0, enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.7.0": version: 5.17.1 resolution: "enhanced-resolve@npm:5.17.1" dependencies: @@ -26545,14 +25323,7 @@ __metadata: languageName: node linkType: hard -"escape-goat@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-goat@npm:4.0.0" - checksum: 10c0/9d2a8314e2370f2dd9436d177f6b3b1773525df8f895c8f3e1acb716f5fd6b10b336cb1cd9862d4709b36eb207dbe33664838deca9c6d55b8371be4eebb972f6 - languageName: node - linkType: hard - -"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": +"escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 @@ -27037,15 +25808,6 @@ __metadata: languageName: node linkType: hard -"estree-util-attach-comments@npm:^3.0.0": - version: 3.0.0 - resolution: "estree-util-attach-comments@npm:3.0.0" - dependencies: - "@types/estree": "npm:^1.0.0" - checksum: 10c0/ee69bb5c45e2ad074725b90ed181c1c934b29d81bce4b0c7761431e83c4c6ab1b223a6a3d6a4fbeb92128bc5d5ee201d5dd36cf1770aa5e16a40b0cf36e8a1f1 - languageName: node - linkType: hard - "estree-util-build-jsx@npm:^2.0.0": version: 2.2.2 resolution: "estree-util-build-jsx@npm:2.2.2" @@ -27057,18 +25819,6 @@ __metadata: languageName: node linkType: hard -"estree-util-build-jsx@npm:^3.0.0": - version: 3.0.1 - resolution: "estree-util-build-jsx@npm:3.0.1" - dependencies: - "@types/estree-jsx": "npm:^1.0.0" - devlop: "npm:^1.0.0" - estree-util-is-identifier-name: "npm:^3.0.0" - estree-walker: "npm:^3.0.0" - checksum: 10c0/274c119817b8e7caa14a9778f1e497fea56cdd2b01df1a1ed037f843178992d3afe85e0d364d485e1e2e239255763553d1b647b15e4a7ba50851bcb43dc6bf80 - languageName: node - linkType: hard - "estree-util-is-identifier-name@npm:^2.0.0": version: 2.1.0 resolution: "estree-util-is-identifier-name@npm:2.1.0" @@ -27076,13 +25826,6 @@ __metadata: languageName: node linkType: hard -"estree-util-is-identifier-name@npm:^3.0.0": - version: 3.0.0 - resolution: "estree-util-is-identifier-name@npm:3.0.0" - checksum: 10c0/d1881c6ed14bd588ebd508fc90bf2a541811dbb9ca04dec2f39d27dcaa635f85b5ed9bbbe7fc6fb1ddfca68744a5f7c70456b4b7108b6c4c52780631cc787c5b - languageName: node - linkType: hard - "estree-util-to-js@npm:^1.1.0": version: 1.2.0 resolution: "estree-util-to-js@npm:1.2.0" @@ -27094,26 +25837,6 @@ __metadata: languageName: node linkType: hard -"estree-util-to-js@npm:^2.0.0": - version: 2.0.0 - resolution: "estree-util-to-js@npm:2.0.0" - dependencies: - "@types/estree-jsx": "npm:^1.0.0" - astring: "npm:^1.8.0" - source-map: "npm:^0.7.0" - checksum: 10c0/ac88cb831401ef99e365f92f4af903755d56ae1ce0e0f0fb8ff66e678141f3d529194f0fb15f6c78cd7554c16fda36854df851d58f9e05cfab15bddf7a97cea0 - languageName: node - linkType: hard - -"estree-util-value-to-estree@npm:^3.0.1": - version: 3.1.2 - resolution: "estree-util-value-to-estree@npm:3.1.2" - dependencies: - "@types/estree": "npm:^1.0.0" - checksum: 10c0/fb0fa42f44488eeb2357b60dc3fd5581422b0a36144fd90639fd3963c7396f225e7d7efeee0144b0a7293ea00e4ec9647b8302d057d48f894e8d5775c3c72eb7 - languageName: node - linkType: hard - "estree-util-visit@npm:^1.0.0": version: 1.2.1 resolution: "estree-util-visit@npm:1.2.1" @@ -27124,16 +25847,6 @@ __metadata: languageName: node linkType: hard -"estree-util-visit@npm:^2.0.0": - version: 2.0.0 - resolution: "estree-util-visit@npm:2.0.0" - dependencies: - "@types/estree-jsx": "npm:^1.0.0" - "@types/unist": "npm:^3.0.0" - checksum: 10c0/acda8b03cc8f890d79c7c7361f6c95331ba84b7ccc0c32b49f447fc30206b20002b37ffdfc97b6ad16e6fe065c63ecbae1622492e2b6b4775c15966606217f39 - languageName: node - linkType: hard - "estree-walker@npm:^0.6.1": version: 0.6.1 resolution: "estree-walker@npm:0.6.1" @@ -27164,13 +25877,6 @@ __metadata: languageName: node linkType: hard -"eta@npm:^2.2.0": - version: 2.2.0 - resolution: "eta@npm:2.2.0" - checksum: 10c0/643b54d9539d2761bf6c5f4f48df1a5ea2d46c7f5a5fdc47a7d4802a8aa2b6262d4d61f724452e226c18cf82db02d48e65293fcc548f26a3f9d75a5ba7c3b859 - languageName: node - linkType: hard - "etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" @@ -27178,16 +25884,6 @@ __metadata: languageName: node linkType: hard -"eval@npm:^0.1.8": - version: 0.1.8 - resolution: "eval@npm:0.1.8" - dependencies: - "@types/node": "npm:*" - require-like: "npm:>= 0.1.1" - checksum: 10c0/258e700bff09e3ce3344273d5b6691b8ec5b043538d84f738f14d8b0aded33d64c00c15b380de725b1401b15f428ab35a9e7ca19a7d25f162c4f877c71586be9 - languageName: node - linkType: hard - "event-emitter@npm:^0.3.5": version: 0.3.5 resolution: "event-emitter@npm:0.3.5" @@ -27632,7 +26328,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:3.3.2, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.1, fast-glob@npm:^3.3.2": +"fast-glob@npm:3.3.2, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.1, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -27703,7 +26399,7 @@ __metadata: languageName: node linkType: hard -"fast-url-parser@npm:1.1.3, fast-url-parser@npm:^1.1.3": +"fast-url-parser@npm:^1.1.3": version: 1.1.3 resolution: "fast-url-parser@npm:1.1.3" dependencies: @@ -27755,24 +26451,6 @@ __metadata: languageName: node linkType: hard -"fault@npm:^2.0.0": - version: 2.0.1 - resolution: "fault@npm:2.0.1" - dependencies: - format: "npm:^0.2.0" - checksum: 10c0/b80fbf1019b9ce8b08ee09ce86e02b028563e13a32ac3be34e42bfac00a97b96d8dee6d31e26578ffc16224eb6729e01ff1f97ddfeee00494f4f56c0aeed4bdd - languageName: node - linkType: hard - -"faye-websocket@npm:^0.11.3": - version: 0.11.4 - resolution: "faye-websocket@npm:0.11.4" - dependencies: - websocket-driver: "npm:>=0.5.1" - checksum: 10c0/c6052a0bb322778ce9f89af92890f6f4ce00d5ec92418a35e5f4c6864a4fe736fec0bcebd47eac7c0f0e979b01530746b1c85c83cb04bae789271abf19737420 - languageName: node - linkType: hard - "fb-watchman@npm:^2.0.0": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" @@ -27813,15 +26491,6 @@ __metadata: languageName: node linkType: hard -"feed@npm:^4.2.2": - version: 4.2.2 - resolution: "feed@npm:4.2.2" - dependencies: - xml-js: "npm:^1.6.11" - checksum: 10c0/c0849bde569da94493224525db00614fd1855a5d7c2e990f6e8637bd0298e85c3d329efe476cba77e711e438c3fb48af60cd5ef0c409da5bcd1f479790b0a372 - languageName: node - linkType: hard - "fetch-retry@npm:^5.0.2": version: 5.0.6 resolution: "fetch-retry@npm:5.0.6" @@ -27965,13 +26634,6 @@ __metadata: languageName: node linkType: hard -"filesize@npm:^8.0.6": - version: 8.0.7 - resolution: "filesize@npm:8.0.7" - checksum: 10c0/82072d94816484df5365d4d5acbb2327a65dc49704c64e403e8c40d8acb7364de1cf1e65cb512c77a15d353870f73e4fed46dad5c6153d0618d9ce7a64d09cfc - languageName: node - linkType: hard - "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -27988,13 +26650,6 @@ __metadata: languageName: node linkType: hard -"filter-obj@npm:^2.0.2": - version: 2.0.2 - resolution: "filter-obj@npm:2.0.2" - checksum: 10c0/65899fb1151e16d3289c23e7d6c2a9276592de1e16ab8e14c29d225768273ac48a92d3be4182496a16d89a046cf24ebcbecef7fdac8c27c3c29feafc4fb9fdb3 - languageName: node - linkType: hard - "finalhandler@npm:1.2.0": version: 1.2.0 resolution: "finalhandler@npm:1.2.0" @@ -28032,16 +26687,6 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:^4.0.0": - version: 4.0.0 - resolution: "find-cache-dir@npm:4.0.0" - dependencies: - common-path-prefix: "npm:^3.0.0" - pkg-dir: "npm:^7.0.0" - checksum: 10c0/0faa7956974726c8769671de696d24c643ca1e5b8f7a2401283caa9e07a5da093293e0a0f4bd18c920ec981d2ef945c7f5b946cde268dfc9077d833ad0293cff - languageName: node - linkType: hard - "find-file-up@npm:^0.1.2": version: 0.1.3 resolution: "find-file-up@npm:0.1.3" @@ -28110,16 +26755,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^6.3.0": - version: 6.3.0 - resolution: "find-up@npm:6.3.0" - dependencies: - locate-path: "npm:^7.1.0" - path-exists: "npm:^5.0.0" - checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 - languageName: node - linkType: hard - "find-versions@npm:^5.0.0": version: 5.1.0 resolution: "find-versions@npm:5.1.0" @@ -28297,37 +26932,6 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^6.5.0": - version: 6.5.3 - resolution: "fork-ts-checker-webpack-plugin@npm:6.5.3" - dependencies: - "@babel/code-frame": "npm:^7.8.3" - "@types/json-schema": "npm:^7.0.5" - chalk: "npm:^4.1.0" - chokidar: "npm:^3.4.2" - cosmiconfig: "npm:^6.0.0" - deepmerge: "npm:^4.2.2" - fs-extra: "npm:^9.0.0" - glob: "npm:^7.1.6" - memfs: "npm:^3.1.2" - minimatch: "npm:^3.0.4" - schema-utils: "npm:2.7.0" - semver: "npm:^7.3.2" - tapable: "npm:^1.0.0" - peerDependencies: - eslint: ">= 6" - typescript: ">= 2.7" - vue-template-compiler: "*" - webpack: ">= 4" - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true - checksum: 10c0/0885ea75474de011d4068ca3e2d3ca6e4cd318f5cfa018e28ff8fef23ef3a1f1c130160ef192d3e5d31ef7b6fe9f8fb1d920eab5e9e449fb30ce5cc96647245c - languageName: node - linkType: hard - "form-data-encoder@npm:1.7.2": version: 1.7.2 resolution: "form-data-encoder@npm:1.7.2" @@ -28335,13 +26939,6 @@ __metadata: languageName: node linkType: hard -"form-data-encoder@npm:^2.1.2": - version: 2.1.4 - resolution: "form-data-encoder@npm:2.1.4" - checksum: 10c0/4c06ae2b79ad693a59938dc49ebd020ecb58e4584860a90a230f80a68b026483b022ba5e4143cff06ae5ac8fd446a0b500fabc87bbac3d1f62f2757f8dabcaf7 - languageName: node - linkType: hard - "form-data@npm:4.0.0, form-data@npm:^4.0.0": version: 4.0.0 resolution: "form-data@npm:4.0.0" @@ -28400,13 +26997,6 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:^4.3.7": - version: 4.3.7 - resolution: "fraction.js@npm:4.3.7" - checksum: 10c0/df291391beea9ab4c263487ffd9d17fed162dbb736982dee1379b2a8cc94e4e24e46ed508c6d278aded9080ba51872f1bc5f3a5fd8d7c74e5f105b508ac28711 - languageName: node - linkType: hard - "framer-motion@npm:^10.12.17": version: 10.18.0 resolution: "framer-motion@npm:10.18.0" @@ -28522,7 +27112,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.1.0, fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0": +"fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": version: 11.2.0 resolution: "fs-extra@npm:11.2.0" dependencies: @@ -28849,7 +27439,7 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": +"get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 @@ -28928,7 +27518,7 @@ __metadata: languageName: node linkType: hard -"github-slugger@npm:^1.0.0, github-slugger@npm:^1.3.0, github-slugger@npm:^1.5.0": +"github-slugger@npm:^1.0.0, github-slugger@npm:^1.3.0": version: 1.5.0 resolution: "github-slugger@npm:1.5.0" checksum: 10c0/116f99732925f939cbfd6f2e57db1aa7e111a460db0d103e3b3f2fce6909d44311663d4542350706cad806345b9892358cc3b153674f88eeae77f43380b3bfca @@ -28960,7 +27550,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^6.0.1, glob-parent@npm:^6.0.2": +"glob-parent@npm:^6.0.2": version: 6.0.2 resolution: "glob-parent@npm:6.0.2" dependencies: @@ -29090,15 +27680,6 @@ __metadata: languageName: node linkType: hard -"global-modules@npm:^2.0.0": - version: 2.0.0 - resolution: "global-modules@npm:2.0.0" - dependencies: - global-prefix: "npm:^3.0.0" - checksum: 10c0/43b770fe24aa6028f4b9770ea583a47f39750be15cf6e2578f851e4ccc9e4fa674b8541928c0b09c21461ca0763f0d36e4068cec86c914b07fd6e388e66ba5b9 - languageName: node - linkType: hard - "global-prefix@npm:^0.1.4": version: 0.1.5 resolution: "global-prefix@npm:0.1.5" @@ -29111,17 +27692,6 @@ __metadata: languageName: node linkType: hard -"global-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "global-prefix@npm:3.0.0" - dependencies: - ini: "npm:^1.3.5" - kind-of: "npm:^6.0.2" - which: "npm:^1.3.1" - checksum: 10c0/510f489fb68d1cc7060f276541709a0ee6d41356ef852de48f7906c648ac223082a1cc8fce86725ca6c0e032bcdc1189ae77b4744a624b29c34a9d0ece498269 - languageName: node - linkType: hard - "globals@npm:^11.1.0": version: 11.12.0 resolution: "globals@npm:11.12.0" @@ -29155,7 +27725,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.1, globby@npm:^11.0.2, globby@npm:^11.0.3, globby@npm:^11.0.4, globby@npm:^11.1.0": +"globby@npm:^11.0.1, globby@npm:^11.0.2, globby@npm:^11.0.3, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -29169,19 +27739,6 @@ __metadata: languageName: node linkType: hard -"globby@npm:^13.1.1": - version: 13.2.2 - resolution: "globby@npm:13.2.2" - dependencies: - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.3.0" - ignore: "npm:^5.2.4" - merge2: "npm:^1.4.1" - slash: "npm:^4.0.0" - checksum: 10c0/a8d7cc7cbe5e1b2d0f81d467bbc5bc2eac35f74eaded3a6c85fc26d7acc8e6de22d396159db8a2fc340b8a342e74cac58de8f4aee74146d3d146921a76062664 - languageName: node - linkType: hard - "globrex@npm:^0.1.2": version: 0.1.2 resolution: "globrex@npm:0.1.2" @@ -29269,25 +27826,6 @@ __metadata: languageName: node linkType: hard -"got@npm:^12.1.0": - version: 12.6.1 - resolution: "got@npm:12.6.1" - dependencies: - "@sindresorhus/is": "npm:^5.2.0" - "@szmarczak/http-timer": "npm:^5.0.1" - cacheable-lookup: "npm:^7.0.0" - cacheable-request: "npm:^10.2.8" - decompress-response: "npm:^6.0.0" - form-data-encoder: "npm:^2.1.2" - get-stream: "npm:^6.0.1" - http2-wrapper: "npm:^2.1.10" - lowercase-keys: "npm:^3.0.0" - p-cancelable: "npm:^3.0.0" - responselike: "npm:^3.0.0" - checksum: 10c0/2fe97fcbd7a9ffc7c2d0ecf59aca0a0562e73a7749cadada9770eeb18efbdca3086262625fb65590594edc220a1eca58fab0d26b0c93c2f9a008234da71ca66b - languageName: node - linkType: hard - "got@npm:^9.6.0": version: 9.6.0 resolution: "got@npm:9.6.0" @@ -29307,13 +27845,6 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:4.2.10": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 10c0/4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 - languageName: node - linkType: hard - "graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.3, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -29631,15 +28162,6 @@ __metadata: languageName: node linkType: hard -"gzip-size@npm:^6.0.0": - version: 6.0.0 - resolution: "gzip-size@npm:6.0.0" - dependencies: - duplexer: "npm:^0.1.2" - checksum: 10c0/4ccb924626c82125897a997d1c84f2377846a6ef57fbee38f7c0e6b41387fba4d00422274440747b58008b5d60114bac2349c2908e9aba55188345281af40a3f - languageName: node - linkType: hard - "hamt_plus@npm:1.0.2": version: 1.0.2 resolution: "hamt_plus@npm:1.0.2" @@ -29647,13 +28169,6 @@ __metadata: languageName: node linkType: hard -"handle-thing@npm:^2.0.0": - version: 2.0.1 - resolution: "handle-thing@npm:2.0.1" - checksum: 10c0/7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 - languageName: node - linkType: hard - "handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": version: 4.7.8 resolution: "handlebars@npm:4.7.8" @@ -29810,13 +28325,6 @@ __metadata: languageName: node linkType: hard -"has-yarn@npm:^3.0.0": - version: 3.0.0 - resolution: "has-yarn@npm:3.0.0" - checksum: 10c0/38c76618cb764e4a98ea114a3938e0bed6ceafb6bacab2ffb32e7c7d1e18b5e09cd03387d507ee87072388e1f20b1f80947fee62c41fc450edfbbdc02a665787 - languageName: node - linkType: hard - "has@npm:^1.0.0": version: 1.0.4 resolution: "has@npm:1.0.4" @@ -29932,22 +28440,6 @@ __metadata: languageName: node linkType: hard -"hast-util-from-parse5@npm:^8.0.0": - version: 8.0.1 - resolution: "hast-util-from-parse5@npm:8.0.1" - dependencies: - "@types/hast": "npm:^3.0.0" - "@types/unist": "npm:^3.0.0" - devlop: "npm:^1.0.0" - hastscript: "npm:^8.0.0" - property-information: "npm:^6.0.0" - vfile: "npm:^6.0.0" - vfile-location: "npm:^5.0.0" - web-namespaces: "npm:^2.0.0" - checksum: 10c0/4a30bb885cff1f0e023c429ae3ece73fe4b03386f07234bf23f5555ca087c2573ff4e551035b417ed7615bde559f394cdaf1db2b91c3b7f0575f3563cd238969 - languageName: node - linkType: hard - "hast-util-has-property@npm:^2.0.0": version: 2.0.1 resolution: "hast-util-has-property@npm:2.0.1" @@ -30021,15 +28513,6 @@ __metadata: languageName: node linkType: hard -"hast-util-parse-selector@npm:^4.0.0": - version: 4.0.0 - resolution: "hast-util-parse-selector@npm:4.0.0" - dependencies: - "@types/hast": "npm:^3.0.0" - checksum: 10c0/5e98168cb44470dc274aabf1a28317e4feb09b1eaf7a48bbaa8c1de1b43a89cd195cb1284e535698e658e3ec26ad91bc5e52c9563c36feb75abbc68aaf68fb9f - languageName: node - linkType: hard - "hast-util-phrasing@npm:^2.0.0": version: 2.0.2 resolution: "hast-util-phrasing@npm:2.0.2" @@ -30095,27 +28578,6 @@ __metadata: languageName: node linkType: hard -"hast-util-raw@npm:^9.0.0": - version: 9.0.4 - resolution: "hast-util-raw@npm:9.0.4" - dependencies: - "@types/hast": "npm:^3.0.0" - "@types/unist": "npm:^3.0.0" - "@ungap/structured-clone": "npm:^1.0.0" - hast-util-from-parse5: "npm:^8.0.0" - hast-util-to-parse5: "npm:^8.0.0" - html-void-elements: "npm:^3.0.0" - mdast-util-to-hast: "npm:^13.0.0" - parse5: "npm:^7.0.0" - unist-util-position: "npm:^5.0.0" - unist-util-visit: "npm:^5.0.0" - vfile: "npm:^6.0.0" - web-namespaces: "npm:^2.0.0" - zwitch: "npm:^2.0.0" - checksum: 10c0/03d0fe7ba8bd75c9ce81f829650b19b78917bbe31db70d36bf6f136842496c3474e3bb1841f2d30dafe1f6b561a89a524185492b9a93d40b131000743c0d7998 - languageName: node - linkType: hard - "hast-util-sanitize@npm:^4.0.0": version: 4.1.0 resolution: "hast-util-sanitize@npm:4.1.0" @@ -30148,30 +28610,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-estree@npm:^3.0.0": - version: 3.1.0 - resolution: "hast-util-to-estree@npm:3.1.0" - dependencies: - "@types/estree": "npm:^1.0.0" - "@types/estree-jsx": "npm:^1.0.0" - "@types/hast": "npm:^3.0.0" - comma-separated-tokens: "npm:^2.0.0" - devlop: "npm:^1.0.0" - estree-util-attach-comments: "npm:^3.0.0" - estree-util-is-identifier-name: "npm:^3.0.0" - hast-util-whitespace: "npm:^3.0.0" - mdast-util-mdx-expression: "npm:^2.0.0" - mdast-util-mdx-jsx: "npm:^3.0.0" - mdast-util-mdxjs-esm: "npm:^2.0.0" - property-information: "npm:^6.0.0" - space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^0.4.0" - unist-util-position: "npm:^5.0.0" - zwitch: "npm:^2.0.0" - checksum: 10c0/9003a8bac26a4580d5fc9f2a271d17330dd653266425e9f5539feecd2f7538868d6630a18f70698b8b804bf14c306418a3f4ab3119bb4692aca78b0c08b1291e - languageName: node - linkType: hard - "hast-util-to-html@npm:^8.0.0": version: 8.0.4 resolution: "hast-util-to-html@npm:8.0.4" @@ -30191,29 +28629,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-jsx-runtime@npm:^2.0.0": - version: 2.3.0 - resolution: "hast-util-to-jsx-runtime@npm:2.3.0" - dependencies: - "@types/estree": "npm:^1.0.0" - "@types/hast": "npm:^3.0.0" - "@types/unist": "npm:^3.0.0" - comma-separated-tokens: "npm:^2.0.0" - devlop: "npm:^1.0.0" - estree-util-is-identifier-name: "npm:^3.0.0" - hast-util-whitespace: "npm:^3.0.0" - mdast-util-mdx-expression: "npm:^2.0.0" - mdast-util-mdx-jsx: "npm:^3.0.0" - mdast-util-mdxjs-esm: "npm:^2.0.0" - property-information: "npm:^6.0.0" - space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^1.0.0" - unist-util-position: "npm:^5.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/df7a36dcc792df7667a54438f044b721753d5e09692606d23bf7336bf4651670111fe7728eebbf9f0e4f96ab3346a05bb23037fa1b1d115482b3bc5bde8b6912 - languageName: node - linkType: hard - "hast-util-to-mdast@npm:^8.3.0": version: 8.4.1 resolution: "hast-util-to-mdast@npm:8.4.1" @@ -30251,21 +28666,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-parse5@npm:^8.0.0": - version: 8.0.0 - resolution: "hast-util-to-parse5@npm:8.0.0" - dependencies: - "@types/hast": "npm:^3.0.0" - comma-separated-tokens: "npm:^2.0.0" - devlop: "npm:^1.0.0" - property-information: "npm:^6.0.0" - space-separated-tokens: "npm:^2.0.0" - web-namespaces: "npm:^2.0.0" - zwitch: "npm:^2.0.0" - checksum: 10c0/3c0c7fba026e0c4be4675daf7277f9ff22ae6da801435f1b7104f7740de5422576f1c025023c7b3df1d0a161e13a04c6ab8f98ada96eb50adb287b537849a2bd - languageName: node - linkType: hard - "hast-util-to-string@npm:^3.0.0": version: 3.0.0 resolution: "hast-util-to-string@npm:3.0.0" @@ -30316,19 +28716,6 @@ __metadata: languageName: node linkType: hard -"hastscript@npm:^8.0.0": - version: 8.0.0 - resolution: "hastscript@npm:8.0.0" - dependencies: - "@types/hast": "npm:^3.0.0" - comma-separated-tokens: "npm:^2.0.0" - hast-util-parse-selector: "npm:^4.0.0" - property-information: "npm:^6.0.0" - space-separated-tokens: "npm:^2.0.0" - checksum: 10c0/f0b54bbdd710854b71c0f044612db0fe1b5e4d74fa2001633dc8c535c26033269f04f536f9fd5b03f234de1111808f9e230e9d19493bf919432bb24d541719e0 - languageName: node - linkType: hard - "he@npm:^1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -30484,18 +28871,6 @@ __metadata: languageName: node linkType: hard -"hpack.js@npm:^2.1.6": - version: 2.1.6 - resolution: "hpack.js@npm:2.1.6" - dependencies: - inherits: "npm:^2.0.1" - obuf: "npm:^1.0.0" - readable-stream: "npm:^2.0.1" - wbuf: "npm:^1.1.0" - checksum: 10c0/55b9e824430bab82a19d079cb6e33042d7d0640325678c9917fcc020c61d8a08ca671b6c942c7f0aae9bb6e4b67ffb50734a72f9e21d66407c3138c1983b70f0 - languageName: node - linkType: hard - "html-element-attributes@npm:^1.0.0": version: 1.3.1 resolution: "html-element-attributes@npm:1.3.1" @@ -30512,55 +28887,14 @@ __metadata: languageName: node linkType: hard -"html-entities@npm:^2.3.2": - version: 2.5.2 - resolution: "html-entities@npm:2.5.2" - checksum: 10c0/f20ffb4326606245c439c231de40a7c560607f639bf40ffbfb36b4c70729fd95d7964209045f1a4e62fe17f2364cef3d6e49b02ea09016f207fde51c2211e481 - languageName: node - linkType: hard - -"html-escaper@npm:^2.0.0, html-escaper@npm:^2.0.2": +"html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 languageName: node linkType: hard -"html-minifier-terser@npm:^6.0.2": - version: 6.1.0 - resolution: "html-minifier-terser@npm:6.1.0" - dependencies: - camel-case: "npm:^4.1.2" - clean-css: "npm:^5.2.2" - commander: "npm:^8.3.0" - he: "npm:^1.2.0" - param-case: "npm:^3.0.4" - relateurl: "npm:^0.2.7" - terser: "npm:^5.10.0" - bin: - html-minifier-terser: cli.js - checksum: 10c0/1aa4e4f01cf7149e3ac5ea84fb7a1adab86da40d38d77a6fff42852b5ee3daccb78b615df97264e3a6a5c33e57f0c77f471d607ca1e1debd1dab9b58286f4b5a - languageName: node - linkType: hard - -"html-minifier-terser@npm:^7.2.0": - version: 7.2.0 - resolution: "html-minifier-terser@npm:7.2.0" - dependencies: - camel-case: "npm:^4.1.2" - clean-css: "npm:~5.3.2" - commander: "npm:^10.0.0" - entities: "npm:^4.4.0" - param-case: "npm:^3.0.4" - relateurl: "npm:^0.2.7" - terser: "npm:^5.15.1" - bin: - html-minifier-terser: cli.js - checksum: 10c0/ffc97c17299d9ec30e17269781b816ea2fc411a9206fc9e768be8f2decb1ea1470892809babb23bb4e3ab1f64d606d97e1803bf526ae3af71edc0fd3070b94b9 - languageName: node - linkType: hard - -"html-tags@npm:^3.1.0, html-tags@npm:^3.3.1": +"html-tags@npm:^3.1.0": version: 3.3.1 resolution: "html-tags@npm:3.3.1" checksum: 10c0/680165e12baa51bad7397452d247dbcc5a5c29dac0e6754b1187eee3bf26f514bc1907a431dd2f7eb56207611ae595ee76a0acc8eaa0d931e72c791dd6463d79 @@ -30587,34 +28921,6 @@ __metadata: languageName: node linkType: hard -"html-void-elements@npm:^3.0.0": - version: 3.0.0 - resolution: "html-void-elements@npm:3.0.0" - checksum: 10c0/a8b9ec5db23b7c8053876dad73a0336183e6162bf6d2677376d8b38d654fdc59ba74fdd12f8812688f7db6fad451210c91b300e472afc0909224e0a44c8610d2 - languageName: node - linkType: hard - -"html-webpack-plugin@npm:^5.5.3": - version: 5.6.0 - resolution: "html-webpack-plugin@npm:5.6.0" - dependencies: - "@types/html-minifier-terser": "npm:^6.0.0" - html-minifier-terser: "npm:^6.0.2" - lodash: "npm:^4.17.21" - pretty-error: "npm:^4.0.0" - tapable: "npm:^2.0.0" - peerDependencies: - "@rspack/core": 0.x || 1.x - webpack: ^5.20.0 - peerDependenciesMeta: - "@rspack/core": - optional: true - webpack: - optional: true - checksum: 10c0/50d1a0f90d512463ea8d798985d91a7ccc9d5e461713dedb240125b2ff0671f58135dd9355f7969af341ff4725e73b2defbc0984cfdce930887a48506d970002 - languageName: node - linkType: hard - "html-whitespace-sensitive-tag-names@npm:^3.0.0": version: 3.0.0 resolution: "html-whitespace-sensitive-tag-names@npm:3.0.0" @@ -30643,18 +28949,6 @@ __metadata: languageName: node linkType: hard -"htmlparser2@npm:^6.1.0": - version: 6.1.0 - resolution: "htmlparser2@npm:6.1.0" - dependencies: - domelementtype: "npm:^2.0.1" - domhandler: "npm:^4.0.0" - domutils: "npm:^2.5.2" - entities: "npm:^2.0.0" - checksum: 10c0/3058499c95634f04dc66be8c2e0927cd86799413b2d6989d8ae542ca4dbf5fa948695d02c27d573acf44843af977aec6d9a7bdd0f6faa6b2d99e2a729b2a31b6 - languageName: node - linkType: hard - "htmlparser2@npm:^8.0.1, htmlparser2@npm:^8.0.2": version: 8.0.2 resolution: "htmlparser2@npm:8.0.2" @@ -30674,13 +28968,6 @@ __metadata: languageName: node linkType: hard -"http-deceiver@npm:^1.2.7": - version: 1.2.7 - resolution: "http-deceiver@npm:1.2.7" - checksum: 10c0/8bb9b716f5fc55f54a451da7f49b9c695c3e45498a789634daec26b61e4add7c85613a4a9e53726c39d09de7a163891ecd6eb5809adb64500a840fd86fe81d03 - languageName: node - linkType: hard - "http-errors@npm:2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" @@ -30707,25 +28994,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:~1.6.2": - version: 1.6.3 - resolution: "http-errors@npm:1.6.3" - dependencies: - depd: "npm:~1.1.2" - inherits: "npm:2.0.3" - setprototypeof: "npm:1.1.0" - statuses: "npm:>= 1.4.0 < 2" - checksum: 10c0/17ec4046ee974477778bfdd525936c254b872054703ec2caa4d6f099566b8adade636ae6aeeacb39302c5cd6e28fb407ebd937f500f5010d0b6850750414ff78 - languageName: node - linkType: hard - -"http-parser-js@npm:>=0.5.1": - version: 0.5.8 - resolution: "http-parser-js@npm:0.5.8" - checksum: 10c0/4ed89f812c44f84c4ae5d43dd3a0c47942b875b63be0ed2ccecbe6b0018af867d806495fc6e12474aff868721163699c49246585bddea4f0ecc6d2b02e19faf1 - languageName: node - linkType: hard - "http-proxy-agent@npm:^4.0.1": version: 4.0.1 resolution: "http-proxy-agent@npm:4.0.1" @@ -30768,24 +29036,6 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.3": - version: 2.0.6 - resolution: "http-proxy-middleware@npm:2.0.6" - dependencies: - "@types/http-proxy": "npm:^1.17.8" - http-proxy: "npm:^1.18.1" - is-glob: "npm:^4.0.1" - is-plain-obj: "npm:^3.0.0" - micromatch: "npm:^4.0.2" - peerDependencies: - "@types/express": ^4.17.13 - peerDependenciesMeta: - "@types/express": - optional: true - checksum: 10c0/25a0e550dd1900ee5048a692e0e9b2b6339d06d487a705d90c47e359e9c6561d648cd7862d001d090e651c9efffa1b6e5160fcf1f299b5fa4935f76e9754eb11 - languageName: node - linkType: hard - "http-proxy@npm:^1.18.1": version: 1.18.1 resolution: "http-proxy@npm:1.18.1" @@ -30848,16 +29098,6 @@ __metadata: languageName: node linkType: hard -"http2-wrapper@npm:^2.1.10": - version: 2.2.1 - resolution: "http2-wrapper@npm:2.2.1" - dependencies: - quick-lru: "npm:^5.1.1" - resolve-alpn: "npm:^1.2.0" - checksum: 10c0/7207201d3c6e53e72e510c9b8912e4f3e468d3ecc0cf3bf52682f2aac9cd99358b896d1da4467380adc151cf97c412bedc59dc13dae90c523f42053a7449eedb - languageName: node - linkType: hard - "https-browserify@npm:^1.0.0": version: 1.0.0 resolution: "https-browserify@npm:1.0.0" @@ -31057,17 +29297,6 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^1.0.2": - version: 1.1.1 - resolution: "image-size@npm:1.1.1" - dependencies: - queue: "npm:6.0.2" - bin: - image-size: bin/image-size.js - checksum: 10c0/2660470096d12be82195f7e80fe03274689fbd14184afb78eaf66ade7cd06352518325814f88af4bde4b26647889fe49e573129f6e7ba8f5ff5b85cc7f559000 - languageName: node - linkType: hard - "imask@npm:^7.6.1": version: 7.6.1 resolution: "imask@npm:7.6.1" @@ -31084,13 +29313,6 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.7": - version: 9.0.21 - resolution: "immer@npm:9.0.21" - checksum: 10c0/03ea3ed5d4d72e8bd428df4a38ad7e483ea8308e9a113d3b42e0ea2cc0cc38340eb0a6aca69592abbbf047c685dbda04e3d34bf2ff438ab57339ed0a34cc0a05 - languageName: node - linkType: hard - "immutable@npm:~3.7.6": version: 3.7.6 resolution: "immutable@npm:3.7.6" @@ -31134,7 +29356,7 @@ __metadata: languageName: node linkType: hard -"import-lazy@npm:^4.0.0, import-lazy@npm:~4.0.0": +"import-lazy@npm:~4.0.0": version: 4.0.0 resolution: "import-lazy@npm:4.0.0" checksum: 10c0/a3520313e2c31f25c0b06aa66d167f329832b68a4f957d7c9daf6e0fa41822b6e84948191648b9b9d8ca82f94740cdf15eecf2401a5b42cd1c33fd84f2225cca @@ -31181,13 +29403,6 @@ __metadata: languageName: node linkType: hard -"infima@npm:0.2.0-alpha.43": - version: 0.2.0-alpha.43 - resolution: "infima@npm:0.2.0-alpha.43" - checksum: 10c0/d248958713a97e1c9f73ace27ceff726ba86a9b534efb0ebdec3e72b785d8edb36db922e38ce09bbeb98a17b657e61357f22edc3a58f02ad51b7ae2ebd96e4e4 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -31242,13 +29457,6 @@ __metadata: languageName: node linkType: hard -"inline-style-parser@npm:0.2.3": - version: 0.2.3 - resolution: "inline-style-parser@npm:0.2.3" - checksum: 10c0/21b46d39a39c8aeaa738346650469388e8a412dd276ab75aa3d85b1883311e89c86a1fdbb8c2f1958f4c979bae74067f6ba0385455b125faf4fa77e1dbb94799 - languageName: node - linkType: hard - "inline-style-prefixer@npm:^7.0.1": version: 7.0.1 resolution: "inline-style-prefixer@npm:7.0.1" @@ -31390,6 +29598,13 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 10c0/8cedd57f07bbc22501516fbfc70447f0c6812871d471096fad9ea603516eacc2137b633633daf432c029712df0baefd793686388ddf5737e3ea15074b877f7ed + languageName: node + linkType: hard + "internmap@npm:^1.0.0": version: 1.0.1 resolution: "internmap@npm:1.0.1" @@ -31461,13 +29676,6 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:^2.0.1": - version: 2.2.0 - resolution: "ipaddr.js@npm:2.2.0" - checksum: 10c0/e4ee875dc1bd92ac9d27e06cfd87cdb63ca786ff9fd7718f1d4f7a8ef27db6e5d516128f52d2c560408cbb75796ac2f83ead669e73507c86282d45f84c5abbb6 - languageName: node - linkType: hard - "is-absolute-url@npm:^3.0.0": version: 3.0.3 resolution: "is-absolute-url@npm:3.0.3" @@ -31631,17 +29839,6 @@ __metadata: languageName: node linkType: hard -"is-ci@npm:^3.0.1": - version: 3.0.1 - resolution: "is-ci@npm:3.0.1" - dependencies: - ci-info: "npm:^3.2.0" - bin: - is-ci: bin.js - checksum: 10c0/0e81caa62f4520d4088a5bef6d6337d773828a88610346c4b1119fb50c842587ed8bef1e5d9a656835a599e7209405b5761ddf2339668f2d0f4e889a92fe6051 - languageName: node - linkType: hard - "is-core-module@npm:^2.1.0, is-core-module@npm:^2.11.0, is-core-module@npm:^2.13.0, is-core-module@npm:^2.13.1, is-core-module@npm:^2.8.1": version: 2.15.0 resolution: "is-core-module@npm:2.15.0" @@ -31887,13 +30084,6 @@ __metadata: languageName: node linkType: hard -"is-npm@npm:^6.0.0": - version: 6.0.0 - resolution: "is-npm@npm:6.0.0" - checksum: 10c0/1f064c66325cba6e494783bee4e635caa2655aad7f853a0e045d086e0bb7d83d2d6cdf1745dc9a7c7c93dacbf816fbee1f8d9179b02d5d01674d4f92541dc0d9 - languageName: node - linkType: hard - "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -31952,13 +30142,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^3.0.0": - version: 3.0.0 - resolution: "is-plain-obj@npm:3.0.0" - checksum: 10c0/8e6483bfb051d42ec9c704c0ede051a821c6b6f9a6c7a3e3b55aa855e00981b0580c8f3b1f5e2e62649b39179b1abfee35d6f8086d999bfaa32c1908d29b07bc - languageName: node - linkType: hard - "is-plain-obj@npm:^4.0.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -32038,13 +30221,6 @@ __metadata: languageName: node linkType: hard -"is-root@npm:^2.1.0": - version: 2.1.0 - resolution: "is-root@npm:2.1.0" - checksum: 10c0/83d3f5b052c3f28fbdbdf0d564bdd34fa14933f5694c78704f85cd1871255bc017fbe3fe2bc2fff2d227c6be5927ad2149b135c0a7c0060e7ac4e610d81a4f01 - languageName: node - linkType: hard - "is-scoped@npm:^2.1.0": version: 2.1.0 resolution: "is-scoped@npm:2.1.0" @@ -32241,13 +30417,6 @@ __metadata: languageName: node linkType: hard -"is-yarn-global@npm:^0.4.0": - version: 0.4.1 - resolution: "is-yarn-global@npm:0.4.1" - checksum: 10c0/8ff66f33454614f8e913ad91cc4de0d88d519a46c1ed41b3f589da79504ed0fcfa304064fe3096dda9360c5f35aa210cb8e978fd36798f3118cb66a4de64d365 - languageName: node - linkType: hard - "isarray@npm:0.0.1": version: 0.0.1 resolution: "isarray@npm:0.0.1" @@ -33075,7 +31244,7 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^29.4.3, jest-worker@npm:^29.7.0": +"jest-worker@npm:^29.7.0": version: 29.7.0 resolution: "jest-worker@npm:29.7.0" dependencies: @@ -33115,7 +31284,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.17.1, jiti@npm:^1.20.0": +"jiti@npm:^1.17.1": version: 1.21.6 resolution: "jiti@npm:1.21.6" bin: @@ -33131,7 +31300,7 @@ __metadata: languageName: node linkType: hard -"joi@npm:^17.11.0, joi@npm:^17.9.2": +"joi@npm:^17.11.0": version: 17.13.3 resolution: "joi@npm:17.13.3" dependencies: @@ -34269,25 +32438,6 @@ __metadata: languageName: node linkType: hard -"latest-version@npm:^7.0.0": - version: 7.0.0 - resolution: "latest-version@npm:7.0.0" - dependencies: - package-json: "npm:^8.1.0" - checksum: 10c0/68045f5e419e005c12e595ae19687dd88317dd0108b83a8773197876622c7e9d164fe43aacca4f434b2cba105c92848b89277f658eabc5d50e81fb743bbcddb1 - languageName: node - linkType: hard - -"launch-editor@npm:^2.6.0": - version: 2.8.1 - resolution: "launch-editor@npm:2.8.1" - dependencies: - picocolors: "npm:^1.0.0" - shell-quote: "npm:^1.8.1" - checksum: 10c0/e18fcda6617a995306602871c7a71ddcfdd82d88a57508ae970be86bfb6685f131cf9ddb8896df4e8e4cde6d0e2d14318d2b41314eaae6abf03ca205948daa27 - languageName: node - linkType: hard - "lazy-universal-dotenv@npm:^4.0.0": version: 4.0.0 resolution: "lazy-universal-dotenv@npm:4.0.0" @@ -34461,13 +32611,6 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^3.2.0": - version: 3.3.1 - resolution: "loader-utils@npm:3.3.1" - checksum: 10c0/f2af4eb185ac5bf7e56e1337b666f90744e9f443861ac521b48f093fb9e8347f191c8960b4388a3365147d218913bc23421234e7788db69f385bacfefa0b4758 - languageName: node - linkType: hard - "local-pkg@npm:^0.5.0": version: 0.5.0 resolution: "local-pkg@npm:0.5.0" @@ -34506,15 +32649,6 @@ __metadata: languageName: node linkType: hard -"locate-path@npm:^7.1.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 - languageName: node - linkType: hard - "lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" @@ -34939,13 +33073,6 @@ __metadata: languageName: node linkType: hard -"lowercase-keys@npm:^3.0.0": - version: 3.0.0 - resolution: "lowercase-keys@npm:3.0.0" - checksum: 10c0/ef62b9fa5690ab0a6e4ef40c94efce68e3ed124f583cc3be38b26ff871da0178a28b9a84ce0c209653bb25ca135520ab87fea7cd411a54ac4899cb2f30501430 - languageName: node - linkType: hard - "lru-cache@npm:7.10.1 - 7.13.1": version: 7.13.1 resolution: "lru-cache@npm:7.13.1" @@ -35253,13 +33380,6 @@ __metadata: languageName: node linkType: hard -"markdown-extensions@npm:^2.0.0": - version: 2.0.0 - resolution: "markdown-extensions@npm:2.0.0" - checksum: 10c0/406139da2aa0d5ebad86195c8e8c02412f873c452b4c087ae7bc767af37956141be449998223bb379eea179b5fd38dfa610602b6f29c22ddab5d51e627a7e41d - languageName: node - linkType: hard - "markdown-it@npm:^14.0.0, markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -35376,22 +33496,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-directive@npm:^3.0.0": - version: 3.0.0 - resolution: "mdast-util-directive@npm:3.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - "@types/unist": "npm:^3.0.0" - devlop: "npm:^1.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - parse-entities: "npm:^4.0.0" - stringify-entities: "npm:^4.0.0" - unist-util-visit-parents: "npm:^6.0.0" - checksum: 10c0/4a71b27f5f0c4ead5293a12d4118d4d832951ac0efdeba4af2dd78f5679f9cabee80feb3619f219a33674c12df3780def1bd3150d7298aaf0ef734f0dfbab999 - languageName: node - linkType: hard - "mdast-util-find-and-replace@npm:^1.1.0": version: 1.1.1 resolution: "mdast-util-find-and-replace@npm:1.1.1" @@ -35415,18 +33519,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-find-and-replace@npm:^3.0.0, mdast-util-find-and-replace@npm:^3.0.1": - version: 3.0.1 - resolution: "mdast-util-find-and-replace@npm:3.0.1" - dependencies: - "@types/mdast": "npm:^4.0.0" - escape-string-regexp: "npm:^5.0.0" - unist-util-is: "npm:^6.0.0" - unist-util-visit-parents: "npm:^6.0.0" - checksum: 10c0/1faca98c4ee10a919f23b8cc6d818e5bb6953216a71dfd35f51066ed5d51ef86e5063b43dcfdc6061cd946e016a9f0d44a1dccadd58452cf4ed14e39377f00cb - languageName: node - linkType: hard - "mdast-util-from-markdown@npm:^0.8.0": version: 0.8.5 resolution: "mdast-util-from-markdown@npm:0.8.5" @@ -35460,26 +33552,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-from-markdown@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-from-markdown@npm:2.0.1" - dependencies: - "@types/mdast": "npm:^4.0.0" - "@types/unist": "npm:^3.0.0" - decode-named-character-reference: "npm:^1.0.0" - devlop: "npm:^1.0.0" - mdast-util-to-string: "npm:^4.0.0" - micromark: "npm:^4.0.0" - micromark-util-decode-numeric-character-reference: "npm:^2.0.0" - micromark-util-decode-string: "npm:^2.0.0" - micromark-util-normalize-identifier: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - unist-util-stringify-position: "npm:^4.0.0" - checksum: 10c0/496596bc6419200ff6258531a0ebcaee576a5c169695f5aa296a79a85f2a221bb9247d565827c709a7c2acfb56ae3c3754bf483d86206617bd299a9658c8121c - languageName: node - linkType: hard - "mdast-util-frontmatter@npm:^0.2.0": version: 0.2.0 resolution: "mdast-util-frontmatter@npm:0.2.0" @@ -35489,20 +33561,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-frontmatter@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-frontmatter@npm:2.0.1" - dependencies: - "@types/mdast": "npm:^4.0.0" - devlop: "npm:^1.0.0" - escape-string-regexp: "npm:^5.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - micromark-extension-frontmatter: "npm:^2.0.0" - checksum: 10c0/d9b0b70dd9c574cc0220d4e05dd8e9d86ac972a6a5af9e0c49c839b31cb750d4313445cfbbdf9264a7fbe3f8c8d920b45358b8500f4286e6b9dc830095b25b9a - languageName: node - linkType: hard - "mdast-util-gfm-autolink-literal@npm:^0.1.0": version: 0.1.3 resolution: "mdast-util-gfm-autolink-literal@npm:0.1.3" @@ -35526,19 +33584,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-autolink-literal@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-gfm-autolink-literal@npm:2.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - ccount: "npm:^2.0.0" - devlop: "npm:^1.0.0" - mdast-util-find-and-replace: "npm:^3.0.0" - micromark-util-character: "npm:^2.0.0" - checksum: 10c0/821ef91db108f05b321c54fdf4436df9d6badb33e18f714d8d52c0e70f988f5b6b118cdd4d607b4cb3bef1718304ce7e9fb25fa580622c3d20d68c1489c64875 - languageName: node - linkType: hard - "mdast-util-gfm-footnote@npm:^1.0.0": version: 1.0.2 resolution: "mdast-util-gfm-footnote@npm:1.0.2" @@ -35550,19 +33595,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-footnote@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-gfm-footnote@npm:2.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - devlop: "npm:^1.1.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - micromark-util-normalize-identifier: "npm:^2.0.0" - checksum: 10c0/c673b22bea24740235e74cfd66765b41a2fa540334f7043fa934b94938b06b7d3c93f2d3b33671910c5492b922c0cc98be833be3b04cfed540e0679650a6d2de - languageName: node - linkType: hard - "mdast-util-gfm-strikethrough@npm:^0.2.0": version: 0.2.3 resolution: "mdast-util-gfm-strikethrough@npm:0.2.3" @@ -35582,17 +33614,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-strikethrough@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/b053e93d62c7545019bd914271ea9e5667ad3b3b57d16dbf68e56fea39a7e19b4a345e781312714eb3d43fdd069ff7ee22a3ca7f6149dfa774554f19ce3ac056 - languageName: node - linkType: hard - "mdast-util-gfm-table@npm:^0.1.0": version: 0.1.6 resolution: "mdast-util-gfm-table@npm:0.1.6" @@ -35615,19 +33636,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-table@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-gfm-table@npm:2.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - devlop: "npm:^1.0.0" - markdown-table: "npm:^3.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/128af47c503a53bd1c79f20642561e54a510ad5e2db1e418d28fefaf1294ab839e6c838e341aef5d7e404f9170b9ca3d1d89605f234efafde93ee51174a6e31e - languageName: node - linkType: hard - "mdast-util-gfm-task-list-item@npm:^0.1.0": version: 0.1.6 resolution: "mdast-util-gfm-task-list-item@npm:0.1.6" @@ -35647,18 +33655,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm-task-list-item@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - devlop: "npm:^1.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/258d725288482b636c0a376c296431390c14b4f29588675297cb6580a8598ed311fc73ebc312acfca12cc8546f07a3a285a53a3b082712e2cbf5c190d677d834 - languageName: node - linkType: hard - "mdast-util-gfm@npm:^0.1.0": version: 0.1.2 resolution: "mdast-util-gfm@npm:0.1.2" @@ -35687,21 +33683,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-gfm@npm:^3.0.0": - version: 3.0.0 - resolution: "mdast-util-gfm@npm:3.0.0" - dependencies: - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-gfm-autolink-literal: "npm:^2.0.0" - mdast-util-gfm-footnote: "npm:^2.0.0" - mdast-util-gfm-strikethrough: "npm:^2.0.0" - mdast-util-gfm-table: "npm:^2.0.0" - mdast-util-gfm-task-list-item: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/91596fe9bf3e4a0c546d0c57f88106c17956d9afbe88ceb08308e4da2388aff64489d649ddad599caecfdf755fc3ae4c9b82c219b85281bc0586b67599881fca - languageName: node - linkType: hard - "mdast-util-mdx-expression@npm:^1.0.0": version: 1.3.2 resolution: "mdast-util-mdx-expression@npm:1.3.2" @@ -35715,20 +33696,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-mdx-expression@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-mdx-expression@npm:2.0.0" - dependencies: - "@types/estree-jsx": "npm:^1.0.0" - "@types/hast": "npm:^3.0.0" - "@types/mdast": "npm:^4.0.0" - devlop: "npm:^1.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/512848cbc44b9dc7cffc1bb3f95f7e67f0d6562870e56a67d25647f475d411e136b915ba417c8069fb36eac1839d0209fb05fb323d377f35626a82fcb0879363 - languageName: node - linkType: hard - "mdast-util-mdx-jsx@npm:^2.0.0": version: 2.1.4 resolution: "mdast-util-mdx-jsx@npm:2.1.4" @@ -35749,27 +33716,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-mdx-jsx@npm:^3.0.0": - version: 3.1.2 - resolution: "mdast-util-mdx-jsx@npm:3.1.2" - dependencies: - "@types/estree-jsx": "npm:^1.0.0" - "@types/hast": "npm:^3.0.0" - "@types/mdast": "npm:^4.0.0" - "@types/unist": "npm:^3.0.0" - ccount: "npm:^2.0.0" - devlop: "npm:^1.1.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - parse-entities: "npm:^4.0.0" - stringify-entities: "npm:^4.0.0" - unist-util-remove-position: "npm:^5.0.0" - unist-util-stringify-position: "npm:^4.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/855b60c3db9bde2fe142bd366597f7bd5892fc288428ba054e26ffcffc07bfe5648c0792d614ba6e08b1eab9784ffc3c1267cf29dfc6db92b419d68b5bcd487d - languageName: node - linkType: hard - "mdast-util-mdx@npm:^2.0.0": version: 2.0.1 resolution: "mdast-util-mdx@npm:2.0.1" @@ -35783,19 +33729,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-mdx@npm:^3.0.0": - version: 3.0.0 - resolution: "mdast-util-mdx@npm:3.0.0" - dependencies: - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-mdx-expression: "npm:^2.0.0" - mdast-util-mdx-jsx: "npm:^3.0.0" - mdast-util-mdxjs-esm: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/4faea13f77d6bc9aa64ee41a5e4779110b73444a17fda363df6ebe880ecfa58b321155b71f8801c3faa6d70d6222a32a00cbd6dbf5fad8db417f4688bc9c74e1 - languageName: node - linkType: hard - "mdast-util-mdxjs-esm@npm:^1.0.0": version: 1.3.1 resolution: "mdast-util-mdxjs-esm@npm:1.3.1" @@ -35809,20 +33742,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-mdxjs-esm@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-mdxjs-esm@npm:2.0.1" - dependencies: - "@types/estree-jsx": "npm:^1.0.0" - "@types/hast": "npm:^3.0.0" - "@types/mdast": "npm:^4.0.0" - devlop: "npm:^1.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - checksum: 10c0/5bda92fc154141705af2b804a534d891f28dac6273186edf1a4c5e3f045d5b01dbcac7400d27aaf91b7e76e8dce007c7b2fdf136c11ea78206ad00bdf9db46bc - languageName: node - linkType: hard - "mdast-util-phrasing@npm:^3.0.0": version: 3.0.1 resolution: "mdast-util-phrasing@npm:3.0.1" @@ -35833,16 +33752,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-phrasing@npm:^4.0.0": - version: 4.1.0 - resolution: "mdast-util-phrasing@npm:4.1.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - unist-util-is: "npm:^6.0.0" - checksum: 10c0/bf6c31d51349aa3d74603d5e5a312f59f3f65662ed16c58017169a5fb0f84ca98578f626c5ee9e4aa3e0a81c996db8717096705521bddb4a0185f98c12c9b42f - languageName: node - linkType: hard - "mdast-util-to-hast@npm:^11.1.1": version: 11.3.0 resolution: "mdast-util-to-hast@npm:11.3.0" @@ -35876,23 +33785,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-hast@npm:^13.0.0": - version: 13.2.0 - resolution: "mdast-util-to-hast@npm:13.2.0" - dependencies: - "@types/hast": "npm:^3.0.0" - "@types/mdast": "npm:^4.0.0" - "@ungap/structured-clone": "npm:^1.0.0" - devlop: "npm:^1.0.0" - micromark-util-sanitize-uri: "npm:^2.0.0" - trim-lines: "npm:^3.0.0" - unist-util-position: "npm:^5.0.0" - unist-util-visit: "npm:^5.0.0" - vfile: "npm:^6.0.0" - checksum: 10c0/9ee58def9287df8350cbb6f83ced90f9c088d72d4153780ad37854f87144cadc6f27b20347073b285173b1649b0723ddf0b9c78158608a804dcacb6bda6e1816 - languageName: node - linkType: hard - "mdast-util-to-markdown@npm:^0.6.0, mdast-util-to-markdown@npm:^0.6.1, mdast-util-to-markdown@npm:~0.6.0": version: 0.6.5 resolution: "mdast-util-to-markdown@npm:0.6.5" @@ -35923,22 +33815,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-markdown@npm:^2.0.0": - version: 2.1.0 - resolution: "mdast-util-to-markdown@npm:2.1.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - "@types/unist": "npm:^3.0.0" - longest-streak: "npm:^3.0.0" - mdast-util-phrasing: "npm:^4.0.0" - mdast-util-to-string: "npm:^4.0.0" - micromark-util-decode-string: "npm:^2.0.0" - unist-util-visit: "npm:^5.0.0" - zwitch: "npm:^2.0.0" - checksum: 10c0/8bd37a9627a438ef6418d6642661904d0cc03c5c732b8b018a8e238ef5cc82fe8aef1940b19c6f563245e58b9659f35e527209bd3fe145f3c723ba14d18fc3e6 - languageName: node - linkType: hard - "mdast-util-to-string@npm:^1.0.0": version: 1.1.0 resolution: "mdast-util-to-string@npm:1.1.0" @@ -35962,15 +33838,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-string@npm:^4.0.0": - version: 4.0.0 - resolution: "mdast-util-to-string@npm:4.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - checksum: 10c0/2d3c1af29bf3fe9c20f552ee9685af308002488f3b04b12fa66652c9718f66f41a32f8362aa2d770c3ff464c034860b41715902ada2306bb0a055146cef064d7 - languageName: node - linkType: hard - "mdn-data@npm:2.0.14": version: 2.0.14 resolution: "mdn-data@npm:2.0.14" @@ -36057,7 +33924,7 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.1.2, memfs@npm:^3.4.1, memfs@npm:^3.4.3": +"memfs@npm:^3.4.1": version: 3.5.3 resolution: "memfs@npm:3.5.3" dependencies: @@ -36185,45 +34052,6 @@ __metadata: languageName: node linkType: hard -"micromark-core-commonmark@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-core-commonmark@npm:2.0.1" - dependencies: - decode-named-character-reference: "npm:^1.0.0" - devlop: "npm:^1.0.0" - micromark-factory-destination: "npm:^2.0.0" - micromark-factory-label: "npm:^2.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-factory-title: "npm:^2.0.0" - micromark-factory-whitespace: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-chunked: "npm:^2.0.0" - micromark-util-classify-character: "npm:^2.0.0" - micromark-util-html-tag-name: "npm:^2.0.0" - micromark-util-normalize-identifier: "npm:^2.0.0" - micromark-util-resolve-all: "npm:^2.0.0" - micromark-util-subtokenize: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/a0b280b1b6132f600518e72cb29a4dd1b2175b85f5ed5b25d2c5695e42b876b045971370daacbcfc6b4ce8cf7acbf78dd3a0284528fb422b450144f4b3bebe19 - languageName: node - linkType: hard - -"micromark-extension-directive@npm:^3.0.0": - version: 3.0.1 - resolution: "micromark-extension-directive@npm:3.0.1" - dependencies: - devlop: "npm:^1.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-factory-whitespace: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - parse-entities: "npm:^4.0.0" - checksum: 10c0/9d226fba0ce18f326d2b28cf2b981c78f6c0c7c2f85e810bf4b12a788dfa4b694386589b081da165227da573ff547238f39c5258d09954b055f167bba1af4983 - languageName: node - linkType: hard - "micromark-extension-frontmatter@npm:^0.2.0": version: 0.2.2 resolution: "micromark-extension-frontmatter@npm:0.2.2" @@ -36233,18 +34061,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-frontmatter@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-extension-frontmatter@npm:2.0.0" - dependencies: - fault: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/7d0d876e598917a67146d29f536d6fbbf9d1b2401a77e2f64a3f80f934a63ff26fa94b01759c9185c24b2a91e4e6abf908fa7aa246f00a7778a6b37a17464300 - languageName: node - linkType: hard - "micromark-extension-gfm-autolink-literal@npm:^1.0.0": version: 1.0.5 resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" @@ -36257,18 +34073,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-autolink-literal@npm:^2.0.0": - version: 2.1.0 - resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" - dependencies: - micromark-util-character: "npm:^2.0.0" - micromark-util-sanitize-uri: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe - languageName: node - linkType: hard - "micromark-extension-gfm-autolink-literal@npm:~0.5.0": version: 0.5.7 resolution: "micromark-extension-gfm-autolink-literal@npm:0.5.7" @@ -36294,22 +34098,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-footnote@npm:^2.0.0": - version: 2.1.0 - resolution: "micromark-extension-gfm-footnote@npm:2.1.0" - dependencies: - devlop: "npm:^1.0.0" - micromark-core-commonmark: "npm:^2.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-normalize-identifier: "npm:^2.0.0" - micromark-util-sanitize-uri: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc - languageName: node - linkType: hard - "micromark-extension-gfm-strikethrough@npm:^1.0.0": version: 1.0.7 resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7" @@ -36324,20 +34112,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-strikethrough@npm:^2.0.0": - version: 2.1.0 - resolution: "micromark-extension-gfm-strikethrough@npm:2.1.0" - dependencies: - devlop: "npm:^1.0.0" - micromark-util-chunked: "npm:^2.0.0" - micromark-util-classify-character: "npm:^2.0.0" - micromark-util-resolve-all: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/ef4f248b865bdda71303b494671b7487808a340b25552b11ca6814dff3fcfaab9be8d294643060bbdb50f79313e4a686ab18b99cbe4d3ee8a4170fcd134234fb - languageName: node - linkType: hard - "micromark-extension-gfm-strikethrough@npm:~0.6.5": version: 0.6.5 resolution: "micromark-extension-gfm-strikethrough@npm:0.6.5" @@ -36360,19 +34134,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-table@npm:^2.0.0": - version: 2.1.0 - resolution: "micromark-extension-gfm-table@npm:2.1.0" - dependencies: - devlop: "npm:^1.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/c1b564ab68576406046d825b9574f5b4dbedbb5c44bede49b5babc4db92f015d9057dd79d8e0530f2fecc8970a695c40ac2e5e1d4435ccf3ef161038d0d1463b - languageName: node - linkType: hard - "micromark-extension-gfm-table@npm:~0.4.0": version: 0.4.3 resolution: "micromark-extension-gfm-table@npm:0.4.3" @@ -36391,15 +34152,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-tagfilter@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-extension-gfm-tagfilter@npm:2.0.0" - dependencies: - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/995558843fff137ae4e46aecb878d8a4691cdf23527dcf1e2f0157d66786be9f7bea0109c52a8ef70e68e3f930af811828ba912239438e31a9cfb9981f44d34d - languageName: node - linkType: hard - "micromark-extension-gfm-tagfilter@npm:~0.3.0": version: 0.3.0 resolution: "micromark-extension-gfm-tagfilter@npm:0.3.0" @@ -36420,19 +34172,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm-task-list-item@npm:^2.0.0": - version: 2.1.0 - resolution: "micromark-extension-gfm-task-list-item@npm:2.1.0" - dependencies: - devlop: "npm:^1.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/78aa537d929e9309f076ba41e5edc99f78d6decd754b6734519ccbbfca8abd52e1c62df68d41a6ae64d2a3fc1646cea955893c79680b0b4385ced4c52296181f - languageName: node - linkType: hard - "micromark-extension-gfm-task-list-item@npm:~0.3.0": version: 0.3.3 resolution: "micromark-extension-gfm-task-list-item@npm:0.3.3" @@ -36472,22 +34211,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-gfm@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-gfm@npm:3.0.0" - dependencies: - micromark-extension-gfm-autolink-literal: "npm:^2.0.0" - micromark-extension-gfm-footnote: "npm:^2.0.0" - micromark-extension-gfm-strikethrough: "npm:^2.0.0" - micromark-extension-gfm-table: "npm:^2.0.0" - micromark-extension-gfm-tagfilter: "npm:^2.0.0" - micromark-extension-gfm-task-list-item: "npm:^2.0.0" - micromark-util-combine-extensions: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/970e28df6ebdd7c7249f52a0dda56e0566fbfa9ae56c8eeeb2445d77b6b89d44096880cd57a1c01e7821b1f4e31009109fbaca4e89731bff7b83b8519690e5d9 - languageName: node - linkType: hard - "micromark-extension-mdx-expression@npm:^1.0.0": version: 1.0.8 resolution: "micromark-extension-mdx-expression@npm:1.0.8" @@ -36504,22 +34227,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-mdx-expression@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-mdx-expression@npm:3.0.0" - dependencies: - "@types/estree": "npm:^1.0.0" - devlop: "npm:^1.0.0" - micromark-factory-mdx-expression: "npm:^2.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-events-to-acorn: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/fa799c594d8ff9ecbbd28e226959c4928590cfcddb60a926d9d859d00fc7acd25684b6f78dbe6a7f0830879a402b4a3628efd40bb9df1f5846e6d2b7332715f7 - languageName: node - linkType: hard - "micromark-extension-mdx-jsx@npm:^1.0.0": version: 1.0.5 resolution: "micromark-extension-mdx-jsx@npm:1.0.5" @@ -36538,24 +34245,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-mdx-jsx@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-mdx-jsx@npm:3.0.0" - dependencies: - "@types/acorn": "npm:^4.0.0" - "@types/estree": "npm:^1.0.0" - devlop: "npm:^1.0.0" - estree-util-is-identifier-name: "npm:^3.0.0" - micromark-factory-mdx-expression: "npm:^2.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/18a81c8def7f3a2088dc435bba19e649c19f679464b1a01e2c680f9518820e70fb0974b8403c790aee8f44205833a280b56ba157fe5a5b2903b476c5de5ba353 - languageName: node - linkType: hard - "micromark-extension-mdx-md@npm:^1.0.0": version: 1.0.1 resolution: "micromark-extension-mdx-md@npm:1.0.1" @@ -36565,15 +34254,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-mdx-md@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-extension-mdx-md@npm:2.0.0" - dependencies: - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/bae91c61273de0e5ba80a980c03470e6cd9d7924aa936f46fbda15d780704d9386e945b99eda200e087b96254fbb4271a9545d5ce02676cd6ae67886a8bf82df - languageName: node - linkType: hard - "micromark-extension-mdxjs-esm@npm:^1.0.0": version: 1.0.5 resolution: "micromark-extension-mdxjs-esm@npm:1.0.5" @@ -36591,23 +34271,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-mdxjs-esm@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-mdxjs-esm@npm:3.0.0" - dependencies: - "@types/estree": "npm:^1.0.0" - devlop: "npm:^1.0.0" - micromark-core-commonmark: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-events-to-acorn: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - unist-util-position-from-estree: "npm:^2.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/13e3f726495a960650cdedcba39198ace5bdc953ccb12c14d71fc9ed9bb88e40cc3ba9231e973f6984da3b3573e7ddb23ce409f7c16f52a8d57b608bf46c748d - languageName: node - linkType: hard - "micromark-extension-mdxjs@npm:^1.0.0": version: 1.0.1 resolution: "micromark-extension-mdxjs@npm:1.0.1" @@ -36624,22 +34287,6 @@ __metadata: languageName: node linkType: hard -"micromark-extension-mdxjs@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-mdxjs@npm:3.0.0" - dependencies: - acorn: "npm:^8.0.0" - acorn-jsx: "npm:^5.0.0" - micromark-extension-mdx-expression: "npm:^3.0.0" - micromark-extension-mdx-jsx: "npm:^3.0.0" - micromark-extension-mdx-md: "npm:^2.0.0" - micromark-extension-mdxjs-esm: "npm:^3.0.0" - micromark-util-combine-extensions: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/fd84f036ddad0aabbc12e7f1b3e9dcfe31573bbc413c5ae903779ef0366d7a4c08193547e7ba75718c9f45654e45f52e575cfc2f23a5f89205a8a70d9a506aea - languageName: node - linkType: hard - "micromark-factory-destination@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-destination@npm:1.1.0" @@ -36651,17 +34298,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-destination@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-factory-destination@npm:2.0.0" - dependencies: - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/b73492f687d41a6a379159c2f3acbf813042346bcea523d9041d0cc6124e6715f0779dbb2a0b3422719e9764c3b09f9707880aa159557e3cb4aeb03b9d274915 - languageName: node - linkType: hard - "micromark-factory-label@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-label@npm:1.1.0" @@ -36674,18 +34310,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-label@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-factory-label@npm:2.0.0" - dependencies: - devlop: "npm:^1.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/8ffad00487a7891941b1d1f51d53a33c7a659dcf48617edb7a4008dad7aff67ec316baa16d55ca98ae3d75ce1d81628dbf72fedc7c6f108f740dec0d5d21c8ee - languageName: node - linkType: hard - "micromark-factory-mdx-expression@npm:^1.0.0": version: 1.0.9 resolution: "micromark-factory-mdx-expression@npm:1.0.9" @@ -36702,22 +34326,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-mdx-expression@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-factory-mdx-expression@npm:2.0.1" - dependencies: - "@types/estree": "npm:^1.0.0" - devlop: "npm:^1.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-events-to-acorn: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - unist-util-position-from-estree: "npm:^2.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/d9cf475a73a7fbfa09aba0d057e033d57e45b7adff78692be9efb4405c4a1717ece4594a632f92a4302e4f8f2ae96355785b616e3f5b2fe8599ec24cfdeee12d - languageName: node - linkType: hard - "micromark-factory-space@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-space@npm:1.1.0" @@ -36728,16 +34336,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-space@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-factory-space@npm:2.0.0" - dependencies: - micromark-util-character: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/103ca954dade963d4ff1d2f27d397833fe855ddc72590205022832ef68b775acdea67949000cee221708e376530b1de78c745267b0bf8366740840783eb37122 - languageName: node - linkType: hard - "micromark-factory-title@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-title@npm:1.1.0" @@ -36750,18 +34348,6 @@ __metadata: languageName: node linkType: hard -"micromark-factory-title@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-factory-title@npm:2.0.0" - dependencies: - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/2b2188e7a011b1b001faf8c860286d246d5c3485ef8819270c60a5808f4c7613e49d4e481dbdff62600ef7acdba0f5100be2d125cbd2a15e236c26b3668a8ebd - languageName: node - linkType: hard - "micromark-factory-whitespace@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-whitespace@npm:1.1.0" @@ -36774,19 +34360,7 @@ __metadata: languageName: node linkType: hard -"micromark-factory-whitespace@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-factory-whitespace@npm:2.0.0" - dependencies: - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/4e91baab0cc71873095134bd0e225d01d9786cde352701402d71b72d317973954754e8f9f1849901f165530e6421202209f4d97c460a27bb0808ec5a3fc3148c - languageName: node - linkType: hard - -"micromark-util-character@npm:^1.0.0, micromark-util-character@npm:^1.1.0": +"micromark-util-character@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-character@npm:1.2.0" dependencies: @@ -36796,16 +34370,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-character@npm:^2.0.0": - version: 2.1.0 - resolution: "micromark-util-character@npm:2.1.0" - dependencies: - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/fc37a76aaa5a5138191ba2bef1ac50c36b3bcb476522e98b1a42304ab4ec76f5b036a746ddf795d3de3e7004b2c09f21dd1bad42d161f39b8cfc0acd067e6373 - languageName: node - linkType: hard - "micromark-util-chunked@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-chunked@npm:1.1.0" @@ -36815,15 +34379,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-chunked@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-chunked@npm:2.0.0" - dependencies: - micromark-util-symbol: "npm:^2.0.0" - checksum: 10c0/043b5f2abc8c13a1e2e4c378ead191d1a47ed9e0cd6d0fa5a0a430b2df9e17ada9d5de5a20688a000bbc5932507e746144acec60a9589d9a79fa60918e029203 - languageName: node - linkType: hard - "micromark-util-classify-character@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-classify-character@npm:1.1.0" @@ -36835,17 +34390,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-classify-character@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-classify-character@npm:2.0.0" - dependencies: - micromark-util-character: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/2bf5fa5050faa9b69f6c7e51dbaaf02329ab70fabad8229984381b356afbbf69db90f4617bec36d814a7d285fb7cad8e3c4e38d1daf4387dc9e240aa7f9a292a - languageName: node - linkType: hard - "micromark-util-combine-extensions@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-combine-extensions@npm:1.1.0" @@ -36856,16 +34400,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-combine-extensions@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-combine-extensions@npm:2.0.0" - dependencies: - micromark-util-chunked: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/cd4c8d1a85255527facb419ff3b3cc3d7b7f27005c5ef5fa7ef2c4d0e57a9129534fc292a188ec2d467c2c458642d369c5f894bc8a9e142aed6696cc7989d3ea - languageName: node - linkType: hard - "micromark-util-decode-numeric-character-reference@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" @@ -36875,15 +34409,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-decode-numeric-character-reference@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.1" - dependencies: - micromark-util-symbol: "npm:^2.0.0" - checksum: 10c0/3f6d684ee8f317c67806e19b3e761956256cb936a2e0533aad6d49ac5604c6536b2041769c6febdd387ab7175b7b7e551851bf2c1f78da943e7a3671ca7635ac - languageName: node - linkType: hard - "micromark-util-decode-string@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-decode-string@npm:1.1.0" @@ -36896,18 +34421,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-decode-string@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-decode-string@npm:2.0.0" - dependencies: - decode-named-character-reference: "npm:^1.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-decode-numeric-character-reference: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - checksum: 10c0/f5413bebb21bdb686cfa1bcfa7e9c93093a523d1b42443ead303b062d2d680a94e5e8424549f57b8ba9d786a758e5a26a97f56068991bbdbca5d1885b3aa7227 - languageName: node - linkType: hard - "micromark-util-encode@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-encode@npm:1.1.0" @@ -36915,13 +34428,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-encode@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-encode@npm:2.0.0" - checksum: 10c0/ebdaafff23100bbf4c74e63b4b1612a9ddf94cd7211d6a076bc6fb0bc32c1b48d6fb615aa0953e607c62c97d849f97f1042260d3eb135259d63d372f401bbbb2 - languageName: node - linkType: hard - "micromark-util-events-to-acorn@npm:^1.0.0": version: 1.2.3 resolution: "micromark-util-events-to-acorn@npm:1.2.3" @@ -36938,22 +34444,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-events-to-acorn@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-util-events-to-acorn@npm:2.0.2" - dependencies: - "@types/acorn": "npm:^4.0.0" - "@types/estree": "npm:^1.0.0" - "@types/unist": "npm:^3.0.0" - devlop: "npm:^1.0.0" - estree-util-visit: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/2bd2660a49efddb625e6adcabdc3384ae4c50c7a04270737270f4aab53d09e8253e6d2607cd947c4c77f8a9900278915babb240e61fd143dc5bab51d9fd50709 - languageName: node - linkType: hard - "micromark-util-html-tag-name@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-html-tag-name@npm:1.2.0" @@ -36961,13 +34451,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-html-tag-name@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-html-tag-name@npm:2.0.0" - checksum: 10c0/988aa26367449bd345b627ae32cf605076daabe2dc1db71b578a8a511a47123e14af466bcd6dcbdacec60142f07bc2723ec5f7a0eed0f5319ce83b5e04825429 - languageName: node - linkType: hard - "micromark-util-normalize-identifier@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-normalize-identifier@npm:1.1.0" @@ -36977,15 +34460,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-normalize-identifier@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-normalize-identifier@npm:2.0.0" - dependencies: - micromark-util-symbol: "npm:^2.0.0" - checksum: 10c0/93bf8789b8449538f22cf82ac9b196363a5f3b2f26efd98aef87c4c1b1f8c05be3ef6391ff38316ff9b03c1a6fd077342567598019ddd12b9bd923dacc556333 - languageName: node - linkType: hard - "micromark-util-resolve-all@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-resolve-all@npm:1.1.0" @@ -36995,15 +34469,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-resolve-all@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-resolve-all@npm:2.0.0" - dependencies: - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/3b912e88453dcefe728a9080c8934a75ac4732056d6576ceecbcaf97f42c5d6fa2df66db8abdc8427eb167c5ffddefe26713728cfe500bc0e314ed260d6e2746 - languageName: node - linkType: hard - "micromark-util-sanitize-uri@npm:^1.0.0, micromark-util-sanitize-uri@npm:^1.1.0": version: 1.2.0 resolution: "micromark-util-sanitize-uri@npm:1.2.0" @@ -37015,17 +34480,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-sanitize-uri@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-sanitize-uri@npm:2.0.0" - dependencies: - micromark-util-character: "npm:^2.0.0" - micromark-util-encode: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - checksum: 10c0/74763ca1c927dd520d3ab8fd9856a19740acf76fc091f0a1f5d4e99c8cd5f1b81c5a0be3efb564941a071fb6d85fd951103f2760eb6cff77b5ab3abe08341309 - languageName: node - linkType: hard - "micromark-util-subtokenize@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-subtokenize@npm:1.1.0" @@ -37038,32 +34492,13 @@ __metadata: languageName: node linkType: hard -"micromark-util-subtokenize@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-subtokenize@npm:2.0.1" - dependencies: - devlop: "npm:^1.0.0" - micromark-util-chunked: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/000cefde827db129f4ed92b8fbdeb4866c5f9c93068c0115485564b0426abcb9058080aa257df9035e12ca7fa92259d66623ea750b9eb3bcdd8325d3fb6fc237 - languageName: node - linkType: hard - -"micromark-util-symbol@npm:^1.0.0, micromark-util-symbol@npm:^1.0.1": +"micromark-util-symbol@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-symbol@npm:1.1.0" checksum: 10c0/10ceaed33a90e6bfd3a5d57053dbb53f437d4809cc11430b5a09479c0ba601577059be9286df4a7eae6e350a60a2575dc9fa9d9872b5b8d058c875e075c33803 languageName: node linkType: hard -"micromark-util-symbol@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-symbol@npm:2.0.0" - checksum: 10c0/4e76186c185ce4cefb9cea8584213d9ffacd77099d1da30c0beb09fa21f46f66f6de4c84c781d7e34ff763fe3a06b530e132fa9004882afab9e825238d0aa8b3 - languageName: node - linkType: hard - "micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": version: 1.1.0 resolution: "micromark-util-types@npm:1.1.0" @@ -37071,13 +34506,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-types@npm:^2.0.0": - version: 2.0.0 - resolution: "micromark-util-types@npm:2.0.0" - checksum: 10c0/d74e913b9b61268e0d6939f4209e3abe9dada640d1ee782419b04fd153711112cfaaa3c4d5f37225c9aee1e23c3bb91a1f5223e1e33ba92d33e83956a53e61de - languageName: node - linkType: hard - "micromark@npm:^2.11.3, micromark@npm:~2.11.0, micromark@npm:~2.11.3": version: 2.11.4 resolution: "micromark@npm:2.11.4" @@ -37113,31 +34541,6 @@ __metadata: languageName: node linkType: hard -"micromark@npm:^4.0.0": - version: 4.0.0 - resolution: "micromark@npm:4.0.0" - dependencies: - "@types/debug": "npm:^4.0.0" - debug: "npm:^4.0.0" - decode-named-character-reference: "npm:^1.0.0" - devlop: "npm:^1.0.0" - micromark-core-commonmark: "npm:^2.0.0" - micromark-factory-space: "npm:^2.0.0" - micromark-util-character: "npm:^2.0.0" - micromark-util-chunked: "npm:^2.0.0" - micromark-util-combine-extensions: "npm:^2.0.0" - micromark-util-decode-numeric-character-reference: "npm:^2.0.0" - micromark-util-encode: "npm:^2.0.0" - micromark-util-normalize-identifier: "npm:^2.0.0" - micromark-util-resolve-all: "npm:^2.0.0" - micromark-util-sanitize-uri: "npm:^2.0.0" - micromark-util-subtokenize: "npm:^2.0.0" - micromark-util-symbol: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - checksum: 10c0/7e91c8d19ff27bc52964100853f1b3b32bb5b2ece57470a34ba1b2f09f4e2a183d90106c4ae585c9f2046969ee088576fed79b2f7061cba60d16652ccc2c64fd - languageName: node - linkType: hard - "micromatch@npm:^4.0.0, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": version: 4.0.7 resolution: "micromatch@npm:4.0.7" @@ -37181,13 +34584,6 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:~1.33.0": - version: 1.33.0 - resolution: "mime-db@npm:1.33.0" - checksum: 10c0/79172ce5468c8503b49dddfdddc18d3f5fe2599f9b5fe1bc321a8cbee14c96730fc6db22f907b23701b05b2936f865795f62ec3a78a7f3c8cb2450bb68c6763e - languageName: node - linkType: hard - "mime-format@npm:2.0.1": version: 2.0.1 resolution: "mime-format@npm:2.0.1" @@ -37197,16 +34593,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:2.1.18": - version: 2.1.18 - resolution: "mime-types@npm:2.1.18" - dependencies: - mime-db: "npm:~1.33.0" - checksum: 10c0/a96a8d12f4bb98bc7bfac6a8ccbd045f40368fc1030d9366050c3613825d3715d1c1f393e10a75a885d2cdc1a26cd6d5e11f3a2a0d5c4d361f00242139430a0f - languageName: node - linkType: hard - -"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.25, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.25, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -37270,13 +34657,6 @@ __metadata: languageName: node linkType: hard -"mimic-response@npm:^4.0.0": - version: 4.0.0 - resolution: "mimic-response@npm:4.0.0" - checksum: 10c0/761d788d2668ae9292c489605ffd4fad220f442fbae6832adce5ebad086d691e906a6d5240c290293c7a11e99fbdbbef04abbbed498bf8699a4ee0f31315e3fb - languageName: node - linkType: hard - "min-indent@npm:^1.0.0, min-indent@npm:^1.0.1": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -37284,18 +34664,6 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:^2.7.6": - version: 2.9.0 - resolution: "mini-css-extract-plugin@npm:2.9.0" - dependencies: - schema-utils: "npm:^4.0.0" - tapable: "npm:^2.2.1" - peerDependencies: - webpack: ^5.0.0 - checksum: 10c0/46e20747ea250420db8a82801b9779299ce3cd5ec4d6dd75e00904c39cc80f0f01decaa534b8cb9658d7d3b656b919cb2cc84b1ba7e2394d2d6548578a5c2901 - languageName: node - linkType: hard - "minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" @@ -37310,15 +34678,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.1.2, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - "minimatch@npm:4.2.3": version: 4.2.3 resolution: "minimatch@npm:4.2.3" @@ -37346,6 +34705,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + "minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -37812,18 +35180,6 @@ __metadata: languageName: node linkType: hard -"multicast-dns@npm:^7.2.5": - version: 7.2.5 - resolution: "multicast-dns@npm:7.2.5" - dependencies: - dns-packet: "npm:^5.2.2" - thunky: "npm:^1.0.2" - bin: - multicast-dns: cli.js - checksum: 10c0/5120171d4bdb1577764c5afa96e413353bff530d1b37081cb29cccc747f989eb1baf40574fe8e27060fc1aef72b59c042f72b9b208413de33bcf411343c69057 - languageName: node - linkType: hard - "multimatch@npm:^5.0.0": version: 5.0.0 resolution: "multimatch@npm:5.0.0" @@ -38237,18 +35593,6 @@ __metadata: languageName: node linkType: hard -"node-emoji@npm:^2.1.0": - version: 2.1.3 - resolution: "node-emoji@npm:2.1.3" - dependencies: - "@sindresorhus/is": "npm:^4.6.0" - char-regex: "npm:^1.0.2" - emojilib: "npm:^2.4.0" - skin-tone: "npm:^2.0.0" - checksum: 10c0/e688333373563aa8308df16111eee2b5837b53a51fb63bf8b7fbea2896327c5d24c9984eb0c8ca6ac155d4d9c194dcf1840d271033c1b588c7c45a3b65339ef7 - languageName: node - linkType: hard - "node-fetch-native@npm:^1.6.3": version: 1.6.4 resolution: "node-fetch-native@npm:1.6.4" @@ -38284,7 +35628,7 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1, node-forge@npm:^1.3.1": +"node-forge@npm:^1.3.1": version: 1.3.1 resolution: "node-forge@npm:1.3.1" checksum: 10c0/e882819b251a4321f9fc1d67c85d1501d3004b4ee889af822fd07f64de3d1a8e272ff00b689570af0465d65d6bf5074df9c76e900e0aff23e60b847f2a46fbe8 @@ -38390,40 +35734,6 @@ __metadata: languageName: node linkType: hard -"node-polyfill-webpack-plugin@npm:^1.1.2": - version: 1.1.4 - resolution: "node-polyfill-webpack-plugin@npm:1.1.4" - dependencies: - assert: "npm:^2.0.0" - browserify-zlib: "npm:^0.2.0" - buffer: "npm:^6.0.3" - console-browserify: "npm:^1.2.0" - constants-browserify: "npm:^1.0.0" - crypto-browserify: "npm:^3.12.0" - domain-browser: "npm:^4.19.0" - events: "npm:^3.3.0" - filter-obj: "npm:^2.0.2" - https-browserify: "npm:^1.0.0" - os-browserify: "npm:^0.3.0" - path-browserify: "npm:^1.0.1" - process: "npm:^0.11.10" - punycode: "npm:^2.1.1" - querystring-es3: "npm:^0.2.1" - readable-stream: "npm:^3.6.0" - stream-browserify: "npm:^3.0.0" - stream-http: "npm:^3.2.0" - string_decoder: "npm:^1.3.0" - timers-browserify: "npm:^2.0.12" - tty-browserify: "npm:^0.0.1" - url: "npm:^0.11.0" - util: "npm:^0.12.4" - vm-browserify: "npm:^1.1.2" - peerDependencies: - webpack: ">=5" - checksum: 10c0/7536ede8c9254aa16e97bc16a81b736931d5d1b8345267dac46b5ea1bf276ee95d45e8e8fbce7b7d9eb0d7acbc5be881342096d52d9f6a8236c637299dbe6ad7 - languageName: node - linkType: hard - "node-preload@npm:^0.2.1": version: 0.2.1 resolution: "node-preload@npm:0.2.1" @@ -38520,13 +35830,6 @@ __metadata: languageName: node linkType: hard -"normalize-range@npm:^0.1.2": - version: 0.1.2 - resolution: "normalize-range@npm:0.1.2" - checksum: 10c0/bf39b73a63e0a42ad1a48c2bd1bda5a07ede64a7e2567307a407674e595bcff0fa0d57e8e5f1e7fa5e91000797c7615e13613227aaaa4d6d6e87f5bd5cc95de6 - languageName: node - linkType: hard - "normalize-url@npm:^4.1.0": version: 4.5.1 resolution: "normalize-url@npm:4.5.1" @@ -38541,13 +35844,6 @@ __metadata: languageName: node linkType: hard -"normalize-url@npm:^8.0.0": - version: 8.0.1 - resolution: "normalize-url@npm:8.0.1" - checksum: 10c0/eb439231c4b84430f187530e6fdac605c5048ef4ec556447a10c00a91fc69b52d8d8298d9d608e68d3e0f7dc2d812d3455edf425e0f215993667c3183bcab1ef - languageName: node - linkType: hard - "npm-bundled@npm:^1.1.1": version: 1.1.2 resolution: "npm-bundled@npm:1.1.2" @@ -38772,13 +36068,6 @@ __metadata: languageName: node linkType: hard -"nprogress@npm:^0.2.0": - version: 0.2.0 - resolution: "nprogress@npm:0.2.0" - checksum: 10c0/eab9a923a1ad1eed71a455ecfbc358442dd9bcd71b9fa3fa1c67eddf5159360b182c218f76fca320c97541a1b45e19ced04e6dcb044a662244c5419f8ae9e821 - languageName: node - linkType: hard - "nth-check@npm:^2.0.0, nth-check@npm:^2.0.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" @@ -39012,7 +36301,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.1.0, object.assign@npm:^4.1.4, object.assign@npm:^4.1.5": +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.5": version: 4.1.5 resolution: "object.assign@npm:4.1.5" dependencies: @@ -39085,7 +36374,7 @@ __metadata: languageName: node linkType: hard -"obuf@npm:^1.0.0, obuf@npm:^1.1.2, obuf@npm:~1.1.2": +"obuf@npm:~1.1.2": version: 1.1.2 resolution: "obuf@npm:1.1.2" checksum: 10c0/520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 @@ -39152,7 +36441,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.4, open@npm:^8.0.9, open@npm:^8.4.0": +"open@npm:^8.0.4, open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -39201,7 +36490,7 @@ __metadata: languageName: node linkType: hard -"opener@npm:^1.5.1, opener@npm:^1.5.2": +"opener@npm:^1.5.1": version: 1.5.2 resolution: "opener@npm:1.5.2" bin: @@ -39293,7 +36582,7 @@ __metadata: languageName: node linkType: hard -"os-browserify@npm:^0.3.0, os-browserify@npm:~0.3.0": +"os-browserify@npm:~0.3.0": version: 0.3.0 resolution: "os-browserify@npm:0.3.0" checksum: 10c0/6ff32cb1efe2bc6930ad0fd4c50e30c38010aee909eba8d65be60af55efd6cbb48f0287e3649b4e3f3a63dce5a667b23c187c4293a75e557f0d5489d735bcf52 @@ -39385,13 +36674,6 @@ __metadata: languageName: node linkType: hard -"p-cancelable@npm:^3.0.0": - version: 3.0.0 - resolution: "p-cancelable@npm:3.0.0" - checksum: 10c0/948fd4f8e87b956d9afc2c6c7392de9113dac817cb1cecf4143f7a3d4c57ab5673614a80be3aba91ceec5e4b69fd8c869852d7e8048bc3d9273c4c36ce14b9aa - languageName: node - linkType: hard - "p-finally@npm:^1.0.0": version: 1.0.0 resolution: "p-finally@npm:1.0.0" @@ -39417,15 +36699,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad - languageName: node - linkType: hard - "p-limit@npm:^5.0.0": version: 5.0.0 resolution: "p-limit@npm:5.0.0" @@ -39462,15 +36735,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 - languageName: node - linkType: hard - "p-map@npm:^3.0.0": version: 3.0.0 resolution: "p-map@npm:3.0.0" @@ -39499,7 +36763,7 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:4, p-retry@npm:^4.5.0": +"p-retry@npm:4": version: 4.6.2 resolution: "p-retry@npm:4.6.2" dependencies: @@ -39556,18 +36820,6 @@ __metadata: languageName: node linkType: hard -"package-json@npm:^8.1.0": - version: 8.1.1 - resolution: "package-json@npm:8.1.1" - dependencies: - got: "npm:^12.1.0" - registry-auth-token: "npm:^5.0.1" - registry-url: "npm:^6.0.0" - semver: "npm:^7.3.7" - checksum: 10c0/83b057878bca229033aefad4ef51569b484e63a65831ddf164dc31f0486817e17ffcb58c819c7af3ef3396042297096b3ffc04e107fd66f8f48756f6d2071c8f - languageName: node - linkType: hard - "pacote@npm:^11.1.11, pacote@npm:^11.2.6, pacote@npm:^11.3.5": version: 11.3.5 resolution: "pacote@npm:11.3.5" @@ -39791,13 +37043,6 @@ __metadata: languageName: node linkType: hard -"parse-numeric-range@npm:^1.3.0": - version: 1.3.0 - resolution: "parse-numeric-range@npm:1.3.0" - checksum: 10c0/53465afaa92111e86697281b684aa4574427360889cc23a1c215488c06b72441febdbf09f47ab0bef9a0c701e059629f3eebd2fe6fb241a254ad7a7a642aebe8 - languageName: node - linkType: hard - "parse-passwd@npm:^1.0.0": version: 1.0.0 resolution: "parse-passwd@npm:1.0.0" @@ -39857,7 +37102,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 @@ -40008,13 +37253,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a - languageName: node - linkType: hard - "path-is-absolute@npm:^1.0.0, path-is-absolute@npm:^1.0.1": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -40022,13 +37260,6 @@ __metadata: languageName: node linkType: hard -"path-is-inside@npm:1.0.2": - version: 1.0.2 - resolution: "path-is-inside@npm:1.0.2" - checksum: 10c0/7fdd4b41672c70461cce734fc222b33e7b447fa489c7c4377c95e7e6852d83d69741f307d88ec0cc3b385b41cb4accc6efac3c7c511cd18512e95424f5fa980c - languageName: node - linkType: hard - "path-key@npm:^2.0.0": version: 2.0.1 resolution: "path-key@npm:2.0.1" @@ -40104,13 +37335,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:2.2.1": - version: 2.2.1 - resolution: "path-to-regexp@npm:2.2.1" - checksum: 10c0/f4b51090a73dad5ce0720f13ce8528ac77914bc927d72cc4ba05ab32770ad3a8d2e431962734b688b9ed863d4098d858da6ff4746037e4e24259cbd3b2c32b79 - languageName: node - linkType: hard - "path-to-regexp@npm:3.2.0": version: 3.2.0 resolution: "path-to-regexp@npm:3.2.0" @@ -40364,6 +37588,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.1.0": + version: 1.1.0 + resolution: "picocolors@npm:1.1.0" + checksum: 10c0/86946f6032148801ef09c051c6fb13b5cf942eaf147e30ea79edb91dd32d700934edebe782a1078ff859fb2b816792e97ef4dab03d7f0b804f6b01a0df35e023 + languageName: node + linkType: hard + "picomatch@npm:3.0.1": version: 3.0.1 resolution: "picomatch@npm:3.0.1" @@ -40459,15 +37690,6 @@ __metadata: languageName: node linkType: hard -"pkg-dir@npm:^7.0.0": - version: 7.0.0 - resolution: "pkg-dir@npm:7.0.0" - dependencies: - find-up: "npm:^6.3.0" - checksum: 10c0/1afb23d2efb1ec9d8b2c4a0c37bf146822ad2774f074cb05b853be5dca1b40815c5960dd126df30ab8908349262a266f31b771e877235870a3b8fd313beebec5 - languageName: node - linkType: hard - "pkg-types@npm:^1.0.3, pkg-types@npm:^1.1.1": version: 1.1.3 resolution: "pkg-types@npm:1.1.3" @@ -40479,15 +37701,6 @@ __metadata: languageName: node linkType: hard -"pkg-up@npm:^3.1.0": - version: 3.1.0 - resolution: "pkg-up@npm:3.1.0" - dependencies: - find-up: "npm:^3.0.0" - checksum: 10c0/ecb60e1f8e1f611c0bdf1a0b6a474d6dfb51185567dc6f29cdef37c8d480ecba5362e006606bb290519bbb6f49526c403fabea93c3090c20368d98bb90c999ab - languageName: node - linkType: hard - "planer@npm:^1.2.0": version: 1.2.0 resolution: "planer@npm:1.2.0" @@ -40577,91 +37790,6 @@ __metadata: languageName: node linkType: hard -"postcss-calc@npm:^9.0.1": - version: 9.0.1 - resolution: "postcss-calc@npm:9.0.1" - dependencies: - postcss-selector-parser: "npm:^6.0.11" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.2.2 - checksum: 10c0/e0df07337162dbcaac5d6e030c7fd289e21da8766a9daca5d6b2b3c8094bb524ae5d74c70048ea7fe5fe4960ce048c60ac97922d917c3bbff34f58e9d2b0eb0e - languageName: node - linkType: hard - -"postcss-colormin@npm:^6.1.0": - version: 6.1.0 - resolution: "postcss-colormin@npm:6.1.0" - dependencies: - browserslist: "npm:^4.23.0" - caniuse-api: "npm:^3.0.0" - colord: "npm:^2.9.3" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/0802963fa0d8f2fe408b2e088117670f5303c69a58c135f0ecf0e5ceff69e95e87111b22c4e29c9adb2f69aa8d3bc175f4e8e8708eeb99c9ffc36c17064de427 - languageName: node - linkType: hard - -"postcss-convert-values@npm:^6.1.0": - version: 6.1.0 - resolution: "postcss-convert-values@npm:6.1.0" - dependencies: - browserslist: "npm:^4.23.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/a80066965cb58fe8fcaf79f306b32c83fc678e1f0678e43f4db3e9fee06eed6db92cf30631ad348a17492769d44757400493c91a33ee865ee8dedea9234a11f5 - languageName: node - linkType: hard - -"postcss-discard-comments@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-discard-comments@npm:6.0.2" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/338a1fcba7e2314d956e5e5b9bd1e12e6541991bf85ac72aed6e229a029bf60edb31f11576b677623576169aa7d9c75e1be259ac7b50d0b735b841b5518f9da9 - languageName: node - linkType: hard - -"postcss-discard-duplicates@npm:^6.0.3": - version: 6.0.3 - resolution: "postcss-discard-duplicates@npm:6.0.3" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/24d2f00e54668f2837eb38a64b1751d7a4a73b2752f9749e61eb728f1fae837984bc2b339f7f5207aff5f66f72551253489114b59b9ba21782072677a81d7d1b - languageName: node - linkType: hard - -"postcss-discard-empty@npm:^6.0.3": - version: 6.0.3 - resolution: "postcss-discard-empty@npm:6.0.3" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/1af08bb29f18eda41edf3602b257d89a4cf0a16f79fc773cfebd4a37251f8dbd9b77ac18efe55d0677d000b43a8adf2ef9328d31961c810e9433a38494a1fa65 - languageName: node - linkType: hard - -"postcss-discard-overridden@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-discard-overridden@npm:6.0.2" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/fda70ef3cd4cb508369c5bbbae44d7760c40ec9f2e65df1cd1b6e0314317fb1d25ae7f64987ca84e66889c1e9d1862487a6ce391c159dfe04d536597bfc5030d - languageName: node - linkType: hard - -"postcss-discard-unused@npm:^6.0.5": - version: 6.0.5 - resolution: "postcss-discard-unused@npm:6.0.5" - dependencies: - postcss-selector-parser: "npm:^6.0.16" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/fca82f17395a7fcc78eab4e03dfb05958beb240c10cacb3836b832c6ea99f5259980c70890a9b7d8b67adf8071b61f3fcf1b432c7a116397aaf67909366da5cc - languageName: node - linkType: hard - "postcss-load-config@npm:^6.0.1": version: 6.0.1 resolution: "postcss-load-config@npm:6.0.1" @@ -40685,106 +37813,6 @@ __metadata: languageName: node linkType: hard -"postcss-loader@npm:^7.3.3": - version: 7.3.4 - resolution: "postcss-loader@npm:7.3.4" - dependencies: - cosmiconfig: "npm:^8.3.5" - jiti: "npm:^1.20.0" - semver: "npm:^7.5.4" - peerDependencies: - postcss: ^7.0.0 || ^8.0.1 - webpack: ^5.0.0 - checksum: 10c0/1bf7614aeea9ad1f8ee6be3a5451576c059391688ea67f825aedc2674056369597faeae4e4a81fe10843884c9904a71403d9a54197e1f560e8fbb9e61f2a2680 - languageName: node - linkType: hard - -"postcss-merge-idents@npm:^6.0.3": - version: 6.0.3 - resolution: "postcss-merge-idents@npm:6.0.3" - dependencies: - cssnano-utils: "npm:^4.0.2" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/fdb51d971df33218bd5fdd9619e5a4d854e23affcea51f96bf4391260cb8d0bec937854582fa9a19bde1fa1b2a43fa5a2f179da23a3adeb8e8d292a4749a8ed7 - languageName: node - linkType: hard - -"postcss-merge-longhand@npm:^6.0.5": - version: 6.0.5 - resolution: "postcss-merge-longhand@npm:6.0.5" - dependencies: - postcss-value-parser: "npm:^4.2.0" - stylehacks: "npm:^6.1.1" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/5a223a7f698c05ab42e9997108a7ff27ea1e0c33a11a353d65a04fc89c3b5b750b9e749550d76b6406329117a055adfc79dde7fee48dca5c8e167a2854ae3fea - languageName: node - linkType: hard - -"postcss-merge-rules@npm:^6.1.1": - version: 6.1.1 - resolution: "postcss-merge-rules@npm:6.1.1" - dependencies: - browserslist: "npm:^4.23.0" - caniuse-api: "npm:^3.0.0" - cssnano-utils: "npm:^4.0.2" - postcss-selector-parser: "npm:^6.0.16" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/6d8952dbb19b1e59bf5affe0871fa1be6515103466857cff5af879d6cf619659f8642ec7a931cabb7cdbd393d8c1e91748bf70bee70fa3edea010d4e25786d04 - languageName: node - linkType: hard - -"postcss-minify-font-values@npm:^6.1.0": - version: 6.1.0 - resolution: "postcss-minify-font-values@npm:6.1.0" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/0d6567170c22a7db42096b5eac298f041614890fbe01759a9fa5ccda432f2bb09efd399d92c11bf6675ae13ccd259db4602fad3c358317dee421df5f7ab0a003 - languageName: node - linkType: hard - -"postcss-minify-gradients@npm:^6.0.3": - version: 6.0.3 - resolution: "postcss-minify-gradients@npm:6.0.3" - dependencies: - colord: "npm:^2.9.3" - cssnano-utils: "npm:^4.0.2" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/7fcbcec94fe5455b89fe1b424a451198e60e0407c894bbacdc062d9fdef2f8571b483b5c3bb17f22d2f1249431251b2de22e1e4e8b0614d10624f8ee6e71afd2 - languageName: node - linkType: hard - -"postcss-minify-params@npm:^6.1.0": - version: 6.1.0 - resolution: "postcss-minify-params@npm:6.1.0" - dependencies: - browserslist: "npm:^4.23.0" - cssnano-utils: "npm:^4.0.2" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/e5c38c3e5fb42e2ca165764f983716e57d854a63a477f7389ccc94cd2ab8123707006613bd7f29acc6eafd296fff513aa6d869c98ac52590f886d641cb21a59e - languageName: node - linkType: hard - -"postcss-minify-selectors@npm:^6.0.4": - version: 6.0.4 - resolution: "postcss-minify-selectors@npm:6.0.4" - dependencies: - postcss-selector-parser: "npm:^6.0.16" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/695ec2e1e3a7812b0cabe1105d0ed491760be3d8e9433914fb5af1fc30a84e6dc24089cd31b7e300de620b8e7adf806526c1acf8dd14077a7d1d2820c60a327c - languageName: node - linkType: hard - "postcss-modules-extract-imports@npm:^3.1.0": version: 3.1.0 resolution: "postcss-modules-extract-imports@npm:3.1.0" @@ -40829,191 +37857,13 @@ __metadata: languageName: node linkType: hard -"postcss-normalize-charset@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-charset@npm:6.0.2" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/af32a3b4cf94163d728b8aa935b2494c9f69fbc96a33b35f67ae15dbdef7fcc8732569df97cbaaf20ca6c0103c39adad0cfce2ba07ffed283796787f6c36f410 - languageName: node - linkType: hard - -"postcss-normalize-display-values@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-display-values@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/782761850c7e697fdb6c3ff53076de716a71b60f9e835efb2f7ef238de347c88b5d55f0d43cf5c608e1ee58de65360e3d9fccd5f20774bba08ded7c87d8a5651 - languageName: node - linkType: hard - -"postcss-normalize-positions@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-positions@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/9fdd42a47226bbda5f68774f3c4c3a90eb4fa708aef5a997c6a52fe6cac06585c9774038fe3bc1aa86a203c29223b8d8db6ebe7580c1aa293154f2b48db0b038 - languageName: node - linkType: hard - -"postcss-normalize-repeat-style@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-repeat-style@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/9133ccbdf1286920c1cd0d01c1c5fa0bd3251b717f2f3e47d691dcc44978ac1dc419d20d9ae5428bd48ee542059e66b823ba699356f5968ccced5606c7c7ca34 - languageName: node - linkType: hard - -"postcss-normalize-string@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-string@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/fecc2d52c4029b24fecf2ca2fb45df5dbdf9f35012194ad4ea80bc7be3252cdcb21a0976400902320595aa6178f2cc625cc804c6b6740aef6efa42105973a205 - languageName: node - linkType: hard - -"postcss-normalize-timing-functions@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-timing-functions@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/a22af0b3374704e59ae70bbbcc66b7029137e284f04e30a2ad548818d1540d6c1ed748dd8f689b9b6df5c1064085a00ad07b6f7e25ffaad49d4e661b616cdeae - languageName: node - linkType: hard - -"postcss-normalize-unicode@npm:^6.1.0": - version: 6.1.0 - resolution: "postcss-normalize-unicode@npm:6.1.0" - dependencies: - browserslist: "npm:^4.23.0" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/ff5746670d94dd97b49a0955c3c71ff516fb4f54bbae257f877d179bacc44a62e50a0fd6e7ddf959f2ca35c335de4266b0c275d880bb57ad7827189339ab1582 - languageName: node - linkType: hard - -"postcss-normalize-url@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-url@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/4718f1c0657788d2c560b340ee8e0a4eb3eb053eba6fbbf489e9a6e739b4c5f9ce1957f54bd03497c50a1f39962bf6ab9ff6ba4976b69dd160f6afd1670d69b7 - languageName: node - linkType: hard - -"postcss-normalize-whitespace@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-normalize-whitespace@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/d5275a88e29a894aeb83a2a833e816d2456dbf3f39961628df596ce205dcc4895186a023812ff691945e0804241ccc53e520d16591b5812288474b474bbaf652 - languageName: node - linkType: hard - -"postcss-ordered-values@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-ordered-values@npm:6.0.2" - dependencies: - cssnano-utils: "npm:^4.0.2" - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/aece23a289228aa804217a85f8da198d22b9123f02ca1310b81834af380d6fbe115e4300683599b4a2ab7f1c6a1dbd6789724c47c38e2b0a3774f2ea4b4f0963 - languageName: node - linkType: hard - -"postcss-reduce-idents@npm:^6.0.3": - version: 6.0.3 - resolution: "postcss-reduce-idents@npm:6.0.3" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/d9f9209e52ebb3d1d7feefc0be24fc74792e064e0fdec99554f050c6b882c61073d5d40986c545061b30e5ead881615e92c965dc765d8d83b2dec10d6a664e1f - languageName: node - linkType: hard - -"postcss-reduce-initial@npm:^6.1.0": - version: 6.1.0 - resolution: "postcss-reduce-initial@npm:6.1.0" - dependencies: - browserslist: "npm:^4.23.0" - caniuse-api: "npm:^3.0.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/a8f28cf51ce9a1b9423cce1a01c1d7cbee90125930ec36435a0073e73aef402d90affe2fd3600c964b679cf738869fda447b95a9acce74414e9d67d5c6ba8646 - languageName: node - linkType: hard - -"postcss-reduce-transforms@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-reduce-transforms@npm:6.0.2" - dependencies: - postcss-value-parser: "npm:^4.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/755ef27b3d083f586ac831f0c611a66e76f504d27e2100dc7674f6b86afad597901b4520cb889fe58ca70e852aa7fd0c0acb69a63d39dfe6a95860b472394e7c - languageName: node - linkType: hard - -"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.16, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": - version: 6.1.1 - resolution: "postcss-selector-parser@npm:6.1.1" +"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": + version: 6.1.2 + resolution: "postcss-selector-parser@npm:6.1.2" dependencies: cssesc: "npm:^3.0.0" util-deprecate: "npm:^1.0.2" - checksum: 10c0/5608765e033fee35d448e1f607ffbaa750eb86901824a8bc4a911ea8bc137cb82f29239330787427c5d3695afd90d8721e190f211dbbf733e25033d8b3100763 - languageName: node - linkType: hard - -"postcss-sort-media-queries@npm:^5.2.0": - version: 5.2.0 - resolution: "postcss-sort-media-queries@npm:5.2.0" - dependencies: - sort-css-media-queries: "npm:2.2.0" - peerDependencies: - postcss: ^8.4.23 - checksum: 10c0/5e7f265a21999bdbf6592f7e15b3e889dd93bc9b15fe048958e8f85603ac276e69ef50305e8b41b10f4eea68917c9c25c7956fa9c3ba7f8577c1149416d35c4e - languageName: node - linkType: hard - -"postcss-svgo@npm:^6.0.3": - version: 6.0.3 - resolution: "postcss-svgo@npm:6.0.3" - dependencies: - postcss-value-parser: "npm:^4.2.0" - svgo: "npm:^3.2.0" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/994b15a88cbb411f32cfa98957faa5623c76f2d75fede51f5f47238f06b367ebe59c204fecbdaf21ccb9e727239a4b290087e04c502392658a0c881ddfbd61f2 - languageName: node - linkType: hard - -"postcss-unique-selectors@npm:^6.0.4": - version: 6.0.4 - resolution: "postcss-unique-selectors@npm:6.0.4" - dependencies: - postcss-selector-parser: "npm:^6.0.16" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/bfb99d8a7c675c93f2e65c9d9d563477bfd46fdce9e2727d42d57982b31ccbaaf944e8034bfbefe48b3119e77fba7eb1b181c19b91cb3a5448058fa66a7c9ae9 + checksum: 10c0/523196a6bd8cf660bdf537ad95abd79e546d54180f9afb165a4ab3e651ac705d0f8b8ce6b3164fb9e3279ce482c5f751a69eb2d3a1e8eb0fd5e82294fb3ef13e languageName: node linkType: hard @@ -41024,15 +37874,6 @@ __metadata: languageName: node linkType: hard -"postcss-zindex@npm:^6.0.2": - version: 6.0.2 - resolution: "postcss-zindex@npm:6.0.2" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/346291703e1f2dd954144d2bb251713dad6ae10e8aa05c3873dee2fc7a30d72da7866bec060abd932b9b839bc1495f73d813dde5312750a69d7ad33c435ce7ea - languageName: node - linkType: hard - "postcss@npm:8.4.31": version: 8.4.31 resolution: "postcss@npm:8.4.31" @@ -41044,7 +37885,18 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.21, postcss@npm:^8.4.24, postcss@npm:^8.4.26, postcss@npm:^8.4.33, postcss@npm:^8.4.38, postcss@npm:^8.4.40": +"postcss@npm:^8.4.33": + version: 8.4.47 + resolution: "postcss@npm:8.4.47" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.1.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/929f68b5081b7202709456532cee2a145c1843d391508c5a09de2517e8c4791638f71dd63b1898dba6712f8839d7a6da046c72a5e44c162e908f5911f57b5f44 + languageName: node + linkType: hard + +"postcss@npm:^8.4.40": version: 8.4.41 resolution: "postcss@npm:8.4.41" dependencies: @@ -41246,16 +38098,6 @@ __metadata: languageName: node linkType: hard -"pretty-error@npm:^4.0.0": - version: 4.0.0 - resolution: "pretty-error@npm:4.0.0" - dependencies: - lodash: "npm:^4.17.20" - renderkid: "npm:^3.0.0" - checksum: 10c0/dc292c087e2857b2e7592784ab31e37a40f3fa918caa11eba51f9fb2853e1d4d6e820b219917e35f5721d833cfd20fdf4f26ae931a90fd1ad0cae2125c345138 - languageName: node - linkType: hard - "pretty-format@npm:^27.0.2": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" @@ -41297,13 +38139,6 @@ __metadata: languageName: node linkType: hard -"pretty-time@npm:^1.1.0": - version: 1.1.0 - resolution: "pretty-time@npm:1.1.0" - checksum: 10c0/ba9d7af19cd43838fb2b147654990949575e400dc2cc24bf71ec4a6c4033a38ba8172b1014b597680c6d4d3c075e94648b2c13a7206c5f0c90b711c7388726f3 - languageName: node - linkType: hard - "pretty@npm:2.0.0": version: 2.0.0 resolution: "pretty@npm:2.0.0" @@ -41336,7 +38171,7 @@ __metadata: languageName: node linkType: hard -"prism-react-renderer@npm:^2.1.0, prism-react-renderer@npm:^2.3.0": +"prism-react-renderer@npm:^2.1.0": version: 2.3.1 resolution: "prism-react-renderer@npm:2.3.1" dependencies: @@ -41348,7 +38183,7 @@ __metadata: languageName: node linkType: hard -"prismjs@npm:^1.23.0, prismjs@npm:^1.29.0": +"prismjs@npm:^1.23.0": version: 1.29.0 resolution: "prismjs@npm:1.29.0" checksum: 10c0/d906c4c4d01b446db549b4f57f72d5d7e6ccaca04ecc670fb85cea4d4b1acc1283e945a9cbc3d81819084a699b382f970e02f9d1378e14af9808d366d9ed7ec6 @@ -41467,7 +38302,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1, prompts@npm:^2.4.0, prompts@npm:^2.4.1, prompts@npm:^2.4.2": +"prompts@npm:^2.0.1, prompts@npm:^2.4.0, prompts@npm:^2.4.1": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -41808,15 +38643,6 @@ __metadata: languageName: node linkType: hard -"pupa@npm:^3.1.0": - version: 3.1.0 - resolution: "pupa@npm:3.1.0" - dependencies: - escape-goat: "npm:^4.0.0" - checksum: 10c0/02afa6e4547a733484206aaa8f8eb3fbfb12d3dd17d7ca4fa1ea390a7da2cb8f381e38868bbf68009c4d372f8f6059f553171b6a712d8f2802c7cd43d513f06c - languageName: node - linkType: hard - "puppeteer-core@npm:^2.1.1": version: 2.1.1 resolution: "puppeteer-core@npm:2.1.1" @@ -41902,7 +38728,7 @@ __metadata: languageName: node linkType: hard -"querystring-es3@npm:^0.2.1, querystring-es3@npm:~0.2.0": +"querystring-es3@npm:~0.2.0": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" checksum: 10c0/476938c1adb45c141f024fccd2ffd919a3746e79ed444d00e670aad68532977b793889648980e7ca7ff5ffc7bfece623118d0fbadcaf217495eeb7059ae51580 @@ -41930,15 +38756,6 @@ __metadata: languageName: node linkType: hard -"queue@npm:6.0.2": - version: 6.0.2 - resolution: "queue@npm:6.0.2" - dependencies: - inherits: "npm:~2.0.3" - checksum: 10c0/cf987476cc72e7d3aaabe23ccefaab1cd757a2b5e0c8d80b67c9575a6b5e1198807ffd4f0948a3f118b149d1111d810ee773473530b77a5c606673cac2c9c996 - languageName: node - linkType: hard - "quick-lru@npm:^5.1.1": version: 5.1.1 resolution: "quick-lru@npm:5.1.1" @@ -41979,14 +38796,7 @@ __metadata: languageName: node linkType: hard -"range-parser@npm:1.2.0": - version: 1.2.0 - resolution: "range-parser@npm:1.2.0" - checksum: 10c0/c7aef4f6588eb974c475649c157f197d07437d8c6c8ff7e36280a141463fb5ab7a45918417334ebd7b665c6b8321cf31c763f7631dd5f5db9372249261b8b02a - languageName: node - linkType: hard - -"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": +"range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 @@ -42092,38 +38902,6 @@ __metadata: languageName: node linkType: hard -"react-dev-utils@npm:^12.0.1": - version: 12.0.1 - resolution: "react-dev-utils@npm:12.0.1" - dependencies: - "@babel/code-frame": "npm:^7.16.0" - address: "npm:^1.1.2" - browserslist: "npm:^4.18.1" - chalk: "npm:^4.1.2" - cross-spawn: "npm:^7.0.3" - detect-port-alt: "npm:^1.1.6" - escape-string-regexp: "npm:^4.0.0" - filesize: "npm:^8.0.6" - find-up: "npm:^5.0.0" - fork-ts-checker-webpack-plugin: "npm:^6.5.0" - global-modules: "npm:^2.0.0" - globby: "npm:^11.0.4" - gzip-size: "npm:^6.0.0" - immer: "npm:^9.0.7" - is-root: "npm:^2.1.0" - loader-utils: "npm:^3.2.0" - open: "npm:^8.4.0" - pkg-up: "npm:^3.1.0" - prompts: "npm:^2.4.2" - react-error-overlay: "npm:^6.0.11" - recursive-readdir: "npm:^2.2.2" - shell-quote: "npm:^1.7.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - checksum: 10c0/94bc4ee5014290ca47a025e53ab2205c5dc0299670724d46a0b1bacbdd48904827b5ae410842d0a3a92481509097ae032e4a9dc7ca70db437c726eaba6411e82 - languageName: node - linkType: hard - "react-devtools-inline@npm:4.4.0": version: 4.4.0 resolution: "react-devtools-inline@npm:4.4.0" @@ -42222,33 +39000,13 @@ __metadata: languageName: node linkType: hard -"react-error-overlay@npm:^6.0.11": - version: 6.0.11 - resolution: "react-error-overlay@npm:6.0.11" - checksum: 10c0/8fc93942976e0c704274aec87dbc8e21f62a2cc78d1c93f9bcfff9f7494b00c60f7a2f0bd48d832bcd3190627c0255a1df907373f61f820371373a65ec4b2d64 - languageName: node - linkType: hard - -"react-fast-compare@npm:^3.2.0, react-fast-compare@npm:^3.2.2": +"react-fast-compare@npm:^3.2.0": version: 3.2.2 resolution: "react-fast-compare@npm:3.2.2" checksum: 10c0/0bbd2f3eb41ab2ff7380daaa55105db698d965c396df73e6874831dbafec8c4b5b08ba36ff09df01526caa3c61595247e3269558c284e37646241cba2b90a367 languageName: node linkType: hard -"react-helmet-async@npm:*": - version: 2.0.5 - resolution: "react-helmet-async@npm:2.0.5" - dependencies: - invariant: "npm:^2.2.4" - react-fast-compare: "npm:^3.2.2" - shallowequal: "npm:^1.1.0" - peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/f390ea8bf13c2681850e5f8eb5b73d8613f407c245a5fd23e9db9b2cc14a3700dd1ce992d3966632886d1d613083294c2aeee009193f49dfa7d145d9f13ea2b0 - languageName: node - linkType: hard - "react-helmet-async@npm:^1.3.0": version: 1.3.0 resolution: "react-helmet-async@npm:1.3.0" @@ -42364,45 +39122,6 @@ __metadata: languageName: node linkType: hard -"react-json-view-lite@npm:^1.2.0": - version: 1.4.0 - resolution: "react-json-view-lite@npm:1.4.0" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10c0/80dd21b14f9dcd93b2f473084aaa934594834a98ae2ed5725c98fae34486226d2eaa69a0bc4233f89b7bab4825e2d393efd6f7d39d59aa37a5bb44a61785f7e5 - languageName: node - linkType: hard - -"react-lifecycles-compat@npm:^3.0.4": - version: 3.0.4 - resolution: "react-lifecycles-compat@npm:3.0.4" - checksum: 10c0/1d0df3c85af79df720524780f00c064d53a9dd1899d785eddb7264b378026979acbddb58a4b7e06e7d0d12aa1494fd5754562ee55d32907b15601068dae82c27 - languageName: node - linkType: hard - -"react-loadable-ssr-addon-v5-slorber@npm:^1.0.1": - version: 1.0.1 - resolution: "react-loadable-ssr-addon-v5-slorber@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.10.3" - peerDependencies: - react-loadable: "*" - webpack: ">=4.41.1 || 5.x" - checksum: 10c0/7b0645f66adec56646f985ba8094c66a1c0a4627d96ad80eea32431d773ef1f79aa47d3247a8f21db3b064a0c6091653c5b5d3483b7046722eb64e55bffe635c - languageName: node - linkType: hard - -"react-loadable@npm:@docusaurus/react-loadable@6.0.0": - version: 6.0.0 - resolution: "@docusaurus/react-loadable@npm:6.0.0" - dependencies: - "@types/react": "npm:*" - peerDependencies: - react: "*" - checksum: 10c0/6b145d1a8d2e7342ceef58dd154aa990322f72a6cb98955ab8ce8e3f0dc7f0c5d00f9c2e4efa8d356c5effed72a130b5588857332b11faba0398f5429b484b04 - languageName: node - linkType: hard - "react-loading-skeleton@npm:^3.3.1": version: 3.4.0 resolution: "react-loading-skeleton@npm:3.4.0" @@ -42612,19 +39331,7 @@ __metadata: languageName: node linkType: hard -"react-router-config@npm:^5.1.1": - version: 5.1.1 - resolution: "react-router-config@npm:5.1.1" - dependencies: - "@babel/runtime": "npm:^7.1.2" - peerDependencies: - react: ">=15" - react-router: ">=5" - checksum: 10c0/1f8f4e55ca68b7b012293e663eb0ee4d670a3df929b78928f713ef98cd9d62c7f5c30a098d6668e64bbb11c7d6bb24e9e6b9c985a8b82465a1858dc7ba663f2b - languageName: node - linkType: hard - -"react-router-dom@npm:^5.2.0, react-router-dom@npm:^5.3.4": +"react-router-dom@npm:^5.2.0": version: 5.3.4 resolution: "react-router-dom@npm:5.3.4" dependencies: @@ -42666,7 +39373,7 @@ __metadata: languageName: node linkType: hard -"react-router@npm:5.3.4, react-router@npm:^5.3.4": +"react-router@npm:5.3.4": version: 5.3.4 resolution: "react-router@npm:5.3.4" dependencies: @@ -42907,7 +39614,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.0.6, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.0.6, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -42974,13 +39681,6 @@ __metadata: languageName: node linkType: hard -"reading-time@npm:^1.5.0": - version: 1.5.0 - resolution: "reading-time@npm:1.5.0" - checksum: 10c0/0f730852fd4fb99e5f78c5b0cf36ab8c3fa15db96f87d9563843f6fd07a47864273ade539ebb184b785b728cde81a70283aa2d9b80cba5ca03b81868be03cabc - languageName: node - linkType: hard - "readline-sync@npm:^1.4.9": version: 1.4.10 resolution: "readline-sync@npm:1.4.10" @@ -43026,15 +39726,6 @@ __metadata: languageName: node linkType: hard -"recursive-readdir@npm:^2.2.2": - version: 2.2.3 - resolution: "recursive-readdir@npm:2.2.3" - dependencies: - minimatch: "npm:^3.0.5" - checksum: 10c0/d0238f137b03af9cd645e1e0b40ae78b6cda13846e3ca57f626fcb58a66c79ae018a10e926b13b3a460f1285acc946a4e512ea8daa2e35df4b76a105709930d1 - languageName: node - linkType: hard - "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -43241,15 +39932,6 @@ __metadata: languageName: node linkType: hard -"registry-auth-token@npm:^5.0.1": - version: 5.0.2 - resolution: "registry-auth-token@npm:5.0.2" - dependencies: - "@pnpm/npm-conf": "npm:^2.1.0" - checksum: 10c0/20fc2225681cc54ae7304b31ebad5a708063b1949593f02dfe5fb402bc1fc28890cecec6497ea396ba86d6cca8a8480715926dfef8cf1f2f11e6f6cc0a1b4bde - languageName: node - linkType: hard - "registry-url@npm:^5.0.0": version: 5.1.0 resolution: "registry-url@npm:5.1.0" @@ -43259,15 +39941,6 @@ __metadata: languageName: node linkType: hard -"registry-url@npm:^6.0.0": - version: 6.0.1 - resolution: "registry-url@npm:6.0.1" - dependencies: - rc: "npm:1.2.8" - checksum: 10c0/66e2221c8113fc35ee9d23fe58cb516fc8d556a189fb8d6f1011a02efccc846c4c9b5075b4027b99a5d5c9ad1345ac37f297bea3c0ca30d607ec8084bf561b90 - languageName: node - linkType: hard - "regjsgen@npm:^0.2.0": version: 0.2.0 resolution: "regjsgen@npm:0.2.0" @@ -43378,17 +40051,6 @@ __metadata: languageName: node linkType: hard -"rehype-raw@npm:^7.0.0": - version: 7.0.0 - resolution: "rehype-raw@npm:7.0.0" - dependencies: - "@types/hast": "npm:^3.0.0" - hast-util-raw: "npm:^9.0.0" - vfile: "npm:^6.0.0" - checksum: 10c0/1435b4b6640a5bc3abe3b2133885c4dbff5ef2190ef9cfe09d6a63f74dd7d7ffd0cede70603278560ccf1acbfb9da9faae4b68065a28bc5aa88ad18e40f32d52 - languageName: node - linkType: hard - "rehype-remark@npm:^9.1.2": version: 9.1.2 resolution: "rehype-remark@npm:9.1.2" @@ -43425,13 +40087,6 @@ __metadata: languageName: node linkType: hard -"relateurl@npm:^0.2.7": - version: 0.2.7 - resolution: "relateurl@npm:0.2.7" - checksum: 10c0/c248b4e3b32474f116a804b537fa6343d731b80056fb506dffd91e737eef4cac6be47a65aae39b522b0db9d0b1011d1a12e288d82a109ecd94a5299d82f6573a - languageName: node - linkType: hard - "relay-runtime@npm:12.0.0": version: 12.0.0 resolution: "relay-runtime@npm:12.0.0" @@ -43465,31 +40120,6 @@ __metadata: languageName: node linkType: hard -"remark-directive@npm:^3.0.0": - version: 3.0.0 - resolution: "remark-directive@npm:3.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - mdast-util-directive: "npm:^3.0.0" - micromark-extension-directive: "npm:^3.0.0" - unified: "npm:^11.0.0" - checksum: 10c0/eeec4d70501c5bce55b2528fa0c8f1e2a5c713c9f72a7d4678dd3868c425620ec409a719bb2656663296bc476c63f5d7bcacd5a9059146bfc89d40e4ce13a7f6 - languageName: node - linkType: hard - -"remark-emoji@npm:^4.0.0": - version: 4.0.1 - resolution: "remark-emoji@npm:4.0.1" - dependencies: - "@types/mdast": "npm:^4.0.2" - emoticon: "npm:^4.0.1" - mdast-util-find-and-replace: "npm:^3.0.1" - node-emoji: "npm:^2.1.0" - unified: "npm:^11.0.4" - checksum: 10c0/27f88892215f3efe8f25c43f226a82d70144a1ae5906d36f6e09390b893b2d5524d5949bd8ca6a02be0e3cb5cba908b35c4221f4e07f34e93d13d6ff9347dbb8 - languageName: node - linkType: hard - "remark-external-links@npm:^8.0.0": version: 8.0.0 resolution: "remark-external-links@npm:8.0.0" @@ -43513,18 +40143,6 @@ __metadata: languageName: node linkType: hard -"remark-frontmatter@npm:^5.0.0": - version: 5.0.0 - resolution: "remark-frontmatter@npm:5.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - mdast-util-frontmatter: "npm:^2.0.0" - micromark-extension-frontmatter: "npm:^2.0.0" - unified: "npm:^11.0.0" - checksum: 10c0/102325d5edbcf30eaf74de8a0a6e03096cc2370dfef19080fd2dd208f368fbb2323388751ac9931a1aa38a4f2828fa4bad6c52dc5249dcadcd34861693b52bf9 - languageName: node - linkType: hard - "remark-gfm@npm:^1.0.0": version: 1.0.0 resolution: "remark-gfm@npm:1.0.0" @@ -43547,20 +40165,6 @@ __metadata: languageName: node linkType: hard -"remark-gfm@npm:^4.0.0": - version: 4.0.0 - resolution: "remark-gfm@npm:4.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - mdast-util-gfm: "npm:^3.0.0" - micromark-extension-gfm: "npm:^3.0.0" - remark-parse: "npm:^11.0.0" - remark-stringify: "npm:^11.0.0" - unified: "npm:^11.0.0" - checksum: 10c0/db0aa85ab718d475c2596e27c95be9255d3b0fc730a4eda9af076b919f7dd812f7be3ac020611a8dbe5253fd29671d7b12750b56e529fdc32dfebad6dbf77403 - languageName: node - linkType: hard - "remark-mdx@npm:^2.0.0": version: 2.3.0 resolution: "remark-mdx@npm:2.3.0" @@ -43571,16 +40175,6 @@ __metadata: languageName: node linkType: hard -"remark-mdx@npm:^3.0.0": - version: 3.0.1 - resolution: "remark-mdx@npm:3.0.1" - dependencies: - mdast-util-mdx: "npm:^3.0.0" - micromark-extension-mdxjs: "npm:^3.0.0" - checksum: 10c0/9e16cd5ff3b30620bd25351a2dd1701627fa5555785b35ee5fe07bd1e6793a9c825cc1f6af9e54a44351f74879f8b5ea2bce8e5a21379aeab58935e76a4d69ce - languageName: node - linkType: hard - "remark-parse@npm:^10.0.0, remark-parse@npm:^10.0.1": version: 10.0.2 resolution: "remark-parse@npm:10.0.2" @@ -43592,18 +40186,6 @@ __metadata: languageName: node linkType: hard -"remark-parse@npm:^11.0.0": - version: 11.0.0 - resolution: "remark-parse@npm:11.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - mdast-util-from-markdown: "npm:^2.0.0" - micromark-util-types: "npm:^2.0.0" - unified: "npm:^11.0.0" - checksum: 10c0/6eed15ddb8680eca93e04fcb2d1b8db65a743dcc0023f5007265dda558b09db595a087f622062ccad2630953cd5cddc1055ce491d25a81f3317c858348a8dd38 - languageName: node - linkType: hard - "remark-parse@npm:^9.0.0": version: 9.0.0 resolution: "remark-parse@npm:9.0.0" @@ -43625,19 +40207,6 @@ __metadata: languageName: node linkType: hard -"remark-rehype@npm:^11.0.0": - version: 11.1.0 - resolution: "remark-rehype@npm:11.1.0" - dependencies: - "@types/hast": "npm:^3.0.0" - "@types/mdast": "npm:^4.0.0" - mdast-util-to-hast: "npm:^13.0.0" - unified: "npm:^11.0.0" - vfile: "npm:^6.0.0" - checksum: 10c0/7a9534847ea70e78cf09227a4302af7e491f625fd092351a1b1ee27a2de0a369ac4acf069682e8a8ec0a55847b3e83f0be76b2028aa90e98e69e21420b9794c3 - languageName: node - linkType: hard - "remark-slug@npm:^6.0.0": version: 6.1.0 resolution: "remark-slug@npm:6.1.0" @@ -43660,17 +40229,6 @@ __metadata: languageName: node linkType: hard -"remark-stringify@npm:^11.0.0": - version: 11.0.0 - resolution: "remark-stringify@npm:11.0.0" - dependencies: - "@types/mdast": "npm:^4.0.0" - mdast-util-to-markdown: "npm:^2.0.0" - unified: "npm:^11.0.0" - checksum: 10c0/0cdb37ce1217578f6f847c7ec9f50cbab35df5b9e3903d543e74b405404e67c07defcb23cd260a567b41b769400f6de03c2c3d9cd6ae7a6707d5c8d89ead489f - languageName: node - linkType: hard - "remark-stringify@npm:^9.0.1": version: 9.0.1 resolution: "remark-stringify@npm:9.0.1" @@ -43708,19 +40266,6 @@ __metadata: languageName: node linkType: hard -"renderkid@npm:^3.0.0": - version: 3.0.0 - resolution: "renderkid@npm:3.0.0" - dependencies: - css-select: "npm:^4.1.3" - dom-converter: "npm:^0.2.0" - htmlparser2: "npm:^6.1.0" - lodash: "npm:^4.17.21" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/24a9fae4cc50e731d059742d1b3eec163dc9e3872b12010d120c3fcbd622765d9cda41f79a1bbb4bf63c1d3442f18a08f6e1642cb5d7ebf092a0ce3f7a3bd143 - languageName: node - linkType: hard - "repeat-string@npm:^1.0.0, repeat-string@npm:^1.6.1": version: 1.6.1 resolution: "repeat-string@npm:1.6.1" @@ -43797,13 +40342,6 @@ __metadata: languageName: node linkType: hard -"require-like@npm:>= 0.1.1": - version: 0.1.2 - resolution: "require-like@npm:0.1.2" - checksum: 10c0/9035ff6c4000a56ede6fc51dd5c56541fafa5a7dddc9b1c3a5f9148d95ee21c603c9bf5c6e37b19fc7de13d9294260842d8590b2ffd6c7c773e78603d1af8050 - languageName: node - linkType: hard - "require-main-filename@npm:^2.0.0": version: 2.0.0 resolution: "require-main-filename@npm:2.0.0" @@ -43832,7 +40370,7 @@ __metadata: languageName: node linkType: hard -"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": +"resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 @@ -43997,15 +40535,6 @@ __metadata: languageName: node linkType: hard -"responselike@npm:^3.0.0": - version: 3.0.0 - resolution: "responselike@npm:3.0.0" - dependencies: - lowercase-keys: "npm:^3.0.0" - checksum: 10c0/8af27153f7e47aa2c07a5f2d538cb1e5872995f0e9ff77def858ecce5c3fe677d42b824a62cde502e56d275ab832b0a8bd350d5cd6b467ac0425214ac12ae658 - languageName: node - linkType: hard - "restore-cursor@npm:^3.1.0": version: 3.1.0 resolution: "restore-cursor@npm:3.1.0" @@ -44109,6 +40638,13 @@ __metadata: languageName: node linkType: hard +"robust-predicates@npm:^3.0.2": + version: 3.0.2 + resolution: "robust-predicates@npm:3.0.2" + checksum: 10c0/4ecd53649f1c2d49529c85518f2fa69ffb2f7a4453f7fd19c042421c7b4d76c3efb48bc1c740c8f7049346d7cb58cf08ee0c9adaae595cc23564d360adb1fde4 + languageName: node + linkType: hard + "rollup-plugin-inject@npm:^3.0.0": version: 3.0.2 resolution: "rollup-plugin-inject@npm:3.0.2" @@ -44252,27 +40788,6 @@ __metadata: languageName: node linkType: hard -"rtl-detect@npm:^1.0.4": - version: 1.1.2 - resolution: "rtl-detect@npm:1.1.2" - checksum: 10c0/1b92888aafca1593314f837e83fdf02eb208faae3e713ab87c176804728efd3b1980d53b64f65f1fa593348087e852c5cd729b7b9372950f6e9b7be489afc0ca - languageName: node - linkType: hard - -"rtlcss@npm:^4.1.0": - version: 4.2.0 - resolution: "rtlcss@npm:4.2.0" - dependencies: - escalade: "npm:^3.1.1" - picocolors: "npm:^1.0.0" - postcss: "npm:^8.4.21" - strip-json-comments: "npm:^3.1.1" - bin: - rtlcss: bin/rtlcss.js - checksum: 10c0/8d1512c36f426bc4f133bc14ab06f11f3f7880a88491ddab81733551465f72adace688653f13fbb6d343961c08503ede5b204bf224e8adf8941a045d5756f537 - languageName: node - linkType: hard - "run-async@npm:^2.0.0, run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -44333,7 +40848,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -44365,13 +40880,6 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.2.4": - version: 1.4.1 - resolution: "sax@npm:1.4.1" - checksum: 10c0/6bf86318a254c5d898ede6bd3ded15daf68ae08a5495a2739564eb265cd13bcc64a07ab466fb204f67ce472bb534eb8612dac587435515169593f4fffa11de7c - languageName: node - linkType: hard - "saxes@npm:^6.0.0": version: 6.0.0 resolution: "saxes@npm:6.0.0" @@ -44390,17 +40898,6 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:2.7.0": - version: 2.7.0 - resolution: "schema-utils@npm:2.7.0" - dependencies: - "@types/json-schema": "npm:^7.0.4" - ajv: "npm:^6.12.2" - ajv-keywords: "npm:^3.4.1" - checksum: 10c0/723c3c856a0313a89aa81c5fb2c93d4b11225f5cdd442665fddd55d3c285ae72e079f5286a3a9a1a973affe888f6c33554a2cf47b79b24cd8de2f1f756a6fb1b - languageName: node - linkType: hard - "schema-utils@npm:^2.7.0": version: 2.7.1 resolution: "schema-utils@npm:2.7.1" @@ -44423,18 +40920,6 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0, schema-utils@npm:^4.0.1": - version: 4.2.0 - resolution: "schema-utils@npm:4.2.0" - dependencies: - "@types/json-schema": "npm:^7.0.9" - ajv: "npm:^8.9.0" - ajv-formats: "npm:^2.1.1" - ajv-keywords: "npm:^5.1.0" - checksum: 10c0/8dab7e7800316387fd8569870b4b668cfcecf95ac551e369ea799bbcbfb63fb0365366d4b59f64822c9f7904d8c5afcfaf5a6124a4b08783e558cd25f299a6b4 - languageName: node - linkType: hard - "scoped-regex@npm:^2.0.0": version: 2.1.0 resolution: "scoped-regex@npm:2.1.0" @@ -44489,23 +40974,6 @@ __metadata: languageName: node linkType: hard -"select-hose@npm:^2.0.0": - version: 2.0.0 - resolution: "select-hose@npm:2.0.0" - checksum: 10c0/01cc52edd29feddaf379efb4328aededa633f0ac43c64b11a8abd075ff34f05b0d280882c4fbcbdf1a0658202c9cd2ea8d5985174dcf9a2dac7e3a4996fa9b67 - languageName: node - linkType: hard - -"selfsigned@npm:^2.1.1": - version: 2.4.1 - resolution: "selfsigned@npm:2.4.1" - dependencies: - "@types/node-forge": "npm:^1.3.0" - node-forge: "npm:^1" - checksum: 10c0/521829ec36ea042f7e9963bf1da2ed040a815cf774422544b112ec53b7edc0bc50a0f8cc2ae7aa6cc19afa967c641fd96a15de0fc650c68651e41277d2e1df09 - languageName: node - linkType: hard - "semver-diff@npm:^3.1.1": version: 3.1.1 resolution: "semver-diff@npm:3.1.1" @@ -44515,15 +40983,6 @@ __metadata: languageName: node linkType: hard -"semver-diff@npm:^4.0.0": - version: 4.0.0 - resolution: "semver-diff@npm:4.0.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/3ed1bb22f39b4b6e98785bb066e821eabb9445d3b23e092866c50e7df8b9bd3eda617b242f81db4159586e0e39b0deb908dd160a24f783bd6f52095b22cd68ea - languageName: node - linkType: hard - "semver-regex@npm:^4.0.5": version: 4.0.5 resolution: "semver-regex@npm:4.0.5" @@ -44630,7 +41089,7 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^6.0.0, serialize-javascript@npm:^6.0.1": +"serialize-javascript@npm:^6.0.1": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" dependencies: @@ -44639,37 +41098,6 @@ __metadata: languageName: node linkType: hard -"serve-handler@npm:^6.1.5": - version: 6.1.5 - resolution: "serve-handler@npm:6.1.5" - dependencies: - bytes: "npm:3.0.0" - content-disposition: "npm:0.5.2" - fast-url-parser: "npm:1.1.3" - mime-types: "npm:2.1.18" - minimatch: "npm:3.1.2" - path-is-inside: "npm:1.0.2" - path-to-regexp: "npm:2.2.1" - range-parser: "npm:1.2.0" - checksum: 10c0/6fd393ae37a0305107e634ca545322b00605322189fe70d8f1a4a90a101c4e354768c610efe5a7ef1af3820cec5c33d97467c88151f35a3cb41d8ff2075ef802 - languageName: node - linkType: hard - -"serve-index@npm:^1.9.1": - version: 1.9.1 - resolution: "serve-index@npm:1.9.1" - dependencies: - accepts: "npm:~1.3.4" - batch: "npm:0.6.1" - debug: "npm:2.6.9" - escape-html: "npm:~1.0.3" - http-errors: "npm:~1.6.2" - mime-types: "npm:~2.1.17" - parseurl: "npm:~1.3.2" - checksum: 10c0/a666471a24196f74371edf2c3c7bcdd82adbac52f600804508754b5296c3567588bf694258b19e0cb23a567acfa20d9721bfdaed3286007b81f9741ada8a3a9c - languageName: node - linkType: hard - "serve-static@npm:1.15.0": version: 1.15.0 resolution: "serve-static@npm:1.15.0" @@ -44739,20 +41167,13 @@ __metadata: languageName: node linkType: hard -"setimmediate@npm:^1.0.4, setimmediate@npm:^1.0.5": +"setimmediate@npm:^1.0.5": version: 1.0.5 resolution: "setimmediate@npm:1.0.5" checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 languageName: node linkType: hard -"setprototypeof@npm:1.1.0": - version: 1.1.0 - resolution: "setprototypeof@npm:1.1.0" - checksum: 10c0/a77b20876689c6a89c3b42f0c3596a9cae02f90fc902570cbd97198e9e8240382086c9303ad043e88cee10f61eae19f1004e51d885395a1e9bf49f9ebed12872 - languageName: node - linkType: hard - "setprototypeof@npm:1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -44962,7 +41383,7 @@ __metadata: languageName: node linkType: hard -"sirv@npm:^2.0.3, sirv@npm:^2.0.4": +"sirv@npm:^2.0.4": version: 2.0.4 resolution: "sirv@npm:2.0.4" dependencies: @@ -44980,29 +41401,6 @@ __metadata: languageName: node linkType: hard -"sitemap@npm:^7.1.1": - version: 7.1.2 - resolution: "sitemap@npm:7.1.2" - dependencies: - "@types/node": "npm:^17.0.5" - "@types/sax": "npm:^1.2.1" - arg: "npm:^5.0.0" - sax: "npm:^1.2.4" - bin: - sitemap: dist/cli.js - checksum: 10c0/01dd1268c0d4b89f8ef082bcb9ef18d0182d00d1622e9c54743474918169491e5360538f9a01a769262e0fe23d6e3822a90680eff0f076cf87b68d459014a34c - languageName: node - linkType: hard - -"skin-tone@npm:^2.0.0": - version: 2.0.0 - resolution: "skin-tone@npm:2.0.0" - dependencies: - unicode-emoji-modifier-base: "npm:^1.0.0" - checksum: 10c0/82d4c2527864f9cbd6cb7f3c4abb31e2224752234d5013b881d3e34e9ab543545b05206df5a17d14b515459fcb265ce409f9cfe443903176b0360cd20e4e4ba5 - languageName: node - linkType: hard - "slash@npm:3.0.0, slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -45024,13 +41422,6 @@ __metadata: languageName: node linkType: hard -"slash@npm:^4.0.0": - version: 4.0.0 - resolution: "slash@npm:4.0.0" - checksum: 10c0/b522ca75d80d107fd30d29df0549a7b2537c83c4c4ecd12cd7d4ea6c8aaca2ab17ada002e7a1d78a9d736a0261509f26ea5b489082ee443a3a810586ef8eff18 - languageName: node - linkType: hard - "slash@npm:^5.0.0": version: 5.1.0 resolution: "slash@npm:5.1.0" @@ -45077,17 +41468,6 @@ __metadata: languageName: node linkType: hard -"sockjs@npm:^0.3.24": - version: 0.3.24 - resolution: "sockjs@npm:0.3.24" - dependencies: - faye-websocket: "npm:^0.11.3" - uuid: "npm:^8.3.2" - websocket-driver: "npm:^0.7.4" - checksum: 10c0/aa102c7d921bf430215754511c81ea7248f2dcdf268fbdb18e4d8183493a86b8793b164c636c52f474a886f747447c962741df2373888823271efdb9d2594f33 - languageName: node - linkType: hard - "socks-proxy-agent@npm:^6.0.0": version: 6.2.1 resolution: "socks-proxy-agent@npm:6.2.1" @@ -45131,13 +41511,6 @@ __metadata: languageName: node linkType: hard -"sort-css-media-queries@npm:2.2.0": - version: 2.2.0 - resolution: "sort-css-media-queries@npm:2.2.0" - checksum: 10c0/7478308c7ca93409f959ab993d41de2f0515ed5f51b671908ecb777aae0d63be97b454d59d80e14ee4874884618a2e825d4ae7ccb225779276904dd175f4e766 - languageName: node - linkType: hard - "sort-keys-length@npm:^1.0.0": version: 1.0.1 resolution: "sort-keys-length@npm:1.0.1" @@ -45172,6 +41545,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -45241,7 +41621,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0, source-map@npm:~0.6.1": +"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -45336,33 +41716,6 @@ __metadata: languageName: node linkType: hard -"spdy-transport@npm:^3.0.0": - version: 3.0.0 - resolution: "spdy-transport@npm:3.0.0" - dependencies: - debug: "npm:^4.1.0" - detect-node: "npm:^2.0.4" - hpack.js: "npm:^2.1.6" - obuf: "npm:^1.1.2" - readable-stream: "npm:^3.0.6" - wbuf: "npm:^1.7.3" - checksum: 10c0/eaf7440fa90724fffc813c386d4a8a7427d967d6e46d7c51d8f8a533d1a6911b9823ea9218703debbae755337e85f110185d7a00ae22ec5c847077b908ce71bb - languageName: node - linkType: hard - -"spdy@npm:^4.0.2": - version: 4.0.2 - resolution: "spdy@npm:4.0.2" - dependencies: - debug: "npm:^4.1.0" - handle-thing: "npm:^2.0.0" - http-deceiver: "npm:^1.2.7" - select-hose: "npm:^2.0.0" - spdy-transport: "npm:^3.0.0" - checksum: 10c0/983509c0be9d06fd00bb9dff713c5b5d35d3ffd720db869acdd5ad7aa6fc0e02c2318b58f75328957d8ff772acdf1f7d19382b6047df342044ff3e2d6805ccdf - languageName: node - linkType: hard - "split-on-first@npm:^1.0.0": version: 1.1.0 resolution: "split-on-first@npm:1.1.0" @@ -45400,13 +41753,6 @@ __metadata: languageName: node linkType: hard -"srcset@npm:^4.0.0": - version: 4.0.0 - resolution: "srcset@npm:4.0.0" - checksum: 10c0/0685c3bd2423b33831734fb71560cd8784f024895e70ee2ac2c392e30047c27ffd9481e001950fb0503f4906bc3fe963145935604edad77944d09c9800990660 - languageName: node - linkType: hard - "sshpk@npm:^1.7.0": version: 1.18.0 resolution: "sshpk@npm:1.18.0" @@ -45541,14 +41887,14 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2": +"statuses@npm:>= 1.5.0 < 2": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: 10c0/e433900956357b3efd79b1c547da4d291799ac836960c016d10a98f6a810b1b5c0dcc13b5a7aa609a58239b5190e1ea176ad9221c2157d2fd1c747393e6b2940 languageName: node linkType: hard -"std-env@npm:^3.0.1, std-env@npm:^3.5.0": +"std-env@npm:^3.5.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" checksum: 10c0/60edf2d130a4feb7002974af3d5a5f3343558d1ccf8d9b9934d225c638606884db4a20d2fe6440a09605bca282af6b042ae8070a10490c0800d69e82e478f41e @@ -45674,7 +42020,7 @@ __metadata: languageName: node linkType: hard -"stream-http@npm:^3.0.0, stream-http@npm:^3.2.0": +"stream-http@npm:^3.0.0": version: 3.2.0 resolution: "stream-http@npm:3.2.0" dependencies: @@ -45931,7 +42277,7 @@ __metadata: languageName: node linkType: hard -"stringify-object@npm:3.3.0, stringify-object@npm:^3.3.0": +"stringify-object@npm:3.3.0": version: 3.3.0 resolution: "stringify-object@npm:3.3.0" dependencies: @@ -46153,7 +42499,7 @@ __metadata: languageName: node linkType: hard -"style-to-object@npm:^0.4.0, style-to-object@npm:^0.4.1": +"style-to-object@npm:^0.4.1": version: 0.4.4 resolution: "style-to-object@npm:0.4.4" dependencies: @@ -46162,15 +42508,6 @@ __metadata: languageName: node linkType: hard -"style-to-object@npm:^1.0.0": - version: 1.0.6 - resolution: "style-to-object@npm:1.0.6" - dependencies: - inline-style-parser: "npm:0.2.3" - checksum: 10c0/be5e8e3f0e35c0338de4112b9d861db576a52ebbd97f2501f1fb2c900d05c8fc42c5114407fa3a7f8b39301146cd8ca03a661bf52212394125a9629d5b771aba - languageName: node - linkType: hard - "style-value-types@npm:5.0.0": version: 5.0.0 resolution: "style-value-types@npm:5.0.0" @@ -46197,18 +42534,6 @@ __metadata: languageName: node linkType: hard -"stylehacks@npm:^6.1.1": - version: 6.1.1 - resolution: "stylehacks@npm:6.1.1" - dependencies: - browserslist: "npm:^4.23.0" - postcss-selector-parser: "npm:^6.0.16" - peerDependencies: - postcss: ^8.4.31 - checksum: 10c0/2dd2bccfd8311ff71492e63a7b8b86c3d7b1fff55d4ba5a2357aff97743e633d351cdc2f5ae3c0057637d00dab4ef5fc5b218a1b370e4585a41df22b5a5128be - languageName: node - linkType: hard - "stylis@npm:4.2.0": version: 4.2.0 resolution: "stylis@npm:4.2.0" @@ -46370,7 +42695,7 @@ __metadata: languageName: node linkType: hard -"svgo@npm:^3.0.2, svgo@npm:^3.2.0": +"svgo@npm:^3.0.2": version: 3.3.2 resolution: "svgo@npm:3.3.2" dependencies: @@ -46450,14 +42775,7 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^1.0.0": - version: 1.1.3 - resolution: "tapable@npm:1.1.3" - checksum: 10c0/c9f0265e55e45821ec672b9b9ee8a35d95bf3ea6b352199f8606a2799018e89cfe4433c554d424b31fc67c4be26b05d4f36dc3c607def416fdb2514cd63dba50 - languageName: node - linkType: hard - -"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": +"tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.2.1 resolution: "tapable@npm:2.2.1" checksum: 10c0/bc40e6efe1e554d075469cedaba69a30eeb373552aaf41caeaaa45bf56ffacc2674261b106245bd566b35d8f3329b52d838e851ee0a852120acae26e622925c9 @@ -46569,7 +42887,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.7, terser-webpack-plugin@npm:^5.3.9": +"terser-webpack-plugin@npm:^5.3.7": version: 5.3.10 resolution: "terser-webpack-plugin@npm:5.3.10" dependencies: @@ -46591,7 +42909,7 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.10.0, terser@npm:^5.15.1, terser@npm:^5.26.0": +"terser@npm:^5.26.0": version: 5.31.5 resolution: "terser@npm:5.31.5" dependencies: @@ -46690,13 +43008,6 @@ __metadata: languageName: node linkType: hard -"thunky@npm:^1.0.2": - version: 1.1.0 - resolution: "thunky@npm:1.1.0" - checksum: 10c0/369764f39de1ce1de2ba2fa922db4a3f92e9c7f33bcc9a713241bc1f4a5238b484c17e0d36d1d533c625efb00e9e82c3e45f80b47586945557b45abb890156d2 - languageName: node - linkType: hard - "timers-browserify@npm:^1.0.1": version: 1.4.2 resolution: "timers-browserify@npm:1.4.2" @@ -46706,15 +43017,6 @@ __metadata: languageName: node linkType: hard -"timers-browserify@npm:^2.0.12": - version: 2.0.12 - resolution: "timers-browserify@npm:2.0.12" - dependencies: - setimmediate: "npm:^1.0.4" - checksum: 10c0/98e84db1a685bc8827c117a8bc62aac811ad56a995d07938fc7ed8cdc5bf3777bfe2d4e5da868847194e771aac3749a20f6cdd22091300fe889a76fe214a4641 - languageName: node - linkType: hard - "timers-ext@npm:^0.1.7": version: 0.1.8 resolution: "timers-ext@npm:0.1.8" @@ -47297,7 +43599,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.5.2, tslib@npm:^2.6.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:~2.6.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.5.2, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:~2.6.0": version: 2.6.3 resolution: "tslib@npm:2.6.3" checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a @@ -47393,7 +43695,7 @@ __metadata: languageName: node linkType: hard -"tty-browserify@npm:0.0.1, tty-browserify@npm:^0.0.1": +"tty-browserify@npm:0.0.1": version: 0.0.1 resolution: "tty-browserify@npm:0.0.1" checksum: 10c0/5e34883388eb5f556234dae75b08e069b9e62de12bd6d87687f7817f5569430a6dfef550b51dbc961715ae0cd0eb5a059e6e3fc34dc127ea164aa0f9b5bb033d @@ -47458,6 +43760,9 @@ __metadata: version: 0.0.0-use.local resolution: "twenty-front@workspace:packages/twenty-front" dependencies: + "@nivo/calendar": "npm:^0.87.0" + "@nivo/core": "npm:^0.87.0" + "@nivo/line": "npm:^0.87.0" "@xyflow/react": "npm:^12.0.4" transliteration: "npm:^2.3.5" languageName: unknown @@ -47536,6 +43841,9 @@ __metadata: version: 0.0.0-use.local resolution: "twenty-website@workspace:packages/twenty-website" dependencies: + "@docsearch/react": "npm:^3.6.2" + "@nivo/calendar": "npm:^0.87.0" + gray-matter: "npm:^4.0.3" next-runtime-env: "npm:^3.2.2" postgres: "npm:^3.4.3" languageName: unknown @@ -47545,6 +43853,7 @@ __metadata: version: 0.0.0-use.local resolution: "twenty-zapier@workspace:packages/twenty-zapier" dependencies: + dotenv: "npm:^16.4.5" jest: "npm:29.7.0" rimraf: "npm:^3.0.2" zapier-platform-cli: "npm:^15.4.1" @@ -47570,10 +43879,6 @@ __metadata: "@codesandbox/sandpack-react": "npm:^2.13.5" "@crxjs/vite-plugin": "npm:^1.0.14" "@dagrejs/dagre": "npm:^1.1.2" - "@docusaurus/core": "npm:^3.1.0" - "@docusaurus/module-type-aliases": "npm:^3.1.0" - "@docusaurus/preset-classic": "npm:^3.1.0" - "@docusaurus/tsconfig": "npm:3.1.0" "@emotion/react": "npm:^11.11.1" "@emotion/styled": "npm:^11.11.0" "@envelop/on-resolve": "npm:^4.1.0" @@ -47608,8 +43913,6 @@ __metadata: "@nestjs/testing": "npm:^9.0.0" "@nestjs/typeorm": "npm:^10.0.0" "@next/eslint-plugin-next": "npm:^14.1.4" - "@nivo/calendar": "npm:^0.84.0" - "@nivo/core": "npm:^0.84.0" "@nx/eslint": "npm:18.3.3" "@nx/eslint-plugin": "npm:18.3.3" "@nx/jest": "npm:18.3.3" @@ -47693,6 +43996,7 @@ __metadata: "@types/passport-google-oauth20": "npm:^2.0.11" "@types/passport-jwt": "npm:^3.0.8" "@types/passport-microsoft": "npm:^1.0.3" + "@types/pluralize": "npm:^0.0.33" "@types/react": "npm:^18.2.39" "@types/react-datepicker": "npm:^6.2.0" "@types/react-dom": "npm:^18.2.15" @@ -47724,6 +44028,7 @@ __metadata: concurrently: "npm:^8.2.2" cross-env: "npm:^7.0.3" cross-var: "npm:^1.1.0" + css-loader: "npm:^7.1.2" danger: "npm:^11.3.0" danger-plugin-todos: "npm:^1.3.1" dataloader: "npm:^2.2.2" @@ -47731,7 +44036,6 @@ __metadata: date-fns-tz: "npm:^2.0.0" debounce: "npm:^2.0.0" deep-equal: "npm:^2.2.2" - docusaurus-node-polyfills: "npm:^1.0.0" dompurify: "npm:^3.0.11" dotenv-cli: "npm:^7.2.1" drizzle-kit: "npm:^0.20.14" @@ -48298,13 +44602,6 @@ __metadata: languageName: node linkType: hard -"unicode-emoji-modifier-base@npm:^1.0.0": - version: 1.0.0 - resolution: "unicode-emoji-modifier-base@npm:1.0.0" - checksum: 10c0/b37623fcf0162186debd20f116483e035a2d5b905b932a2c472459d9143d446ebcbefb2a494e2fe4fa7434355396e2a95ec3fc1f0c29a3bc8f2c827220e79c66 - languageName: node - linkType: hard - "unicode-match-property-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-match-property-ecmascript@npm:2.0.0" @@ -48344,21 +44641,6 @@ __metadata: languageName: node linkType: hard -"unified@npm:^11.0.0, unified@npm:^11.0.3, unified@npm:^11.0.4": - version: 11.0.5 - resolution: "unified@npm:11.0.5" - dependencies: - "@types/unist": "npm:^3.0.0" - bail: "npm:^2.0.0" - devlop: "npm:^1.0.0" - extend: "npm:^3.0.0" - is-plain-obj: "npm:^4.0.0" - trough: "npm:^2.0.0" - vfile: "npm:^6.0.0" - checksum: 10c0/53c8e685f56d11d9d458a43e0e74328a4d6386af51c8ac37a3dcabec74ce5026da21250590d4aff6733ccd7dc203116aae2b0769abc18cdf9639a54ae528dfc9 - languageName: node - linkType: hard - "unified@npm:^9.2.1": version: 9.2.2 resolution: "unified@npm:9.2.2" @@ -48445,15 +44727,6 @@ __metadata: languageName: node linkType: hard -"unique-string@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-string@npm:3.0.0" - dependencies: - crypto-random-string: "npm:^4.0.0" - checksum: 10c0/b35ea034b161b2a573666ec16c93076b4b6106b8b16c2415808d747ab3a0566b5db0c4be231d4b11cfbc16d7fd915c9d8a45884bff0e2db11b799775b2e1e017 - languageName: node - linkType: hard - "unist-builder@npm:^3.0.0": version: 3.0.1 resolution: "unist-builder@npm:3.0.1" @@ -48554,15 +44827,6 @@ __metadata: languageName: node linkType: hard -"unist-util-position-from-estree@npm:^2.0.0": - version: 2.0.0 - resolution: "unist-util-position-from-estree@npm:2.0.0" - dependencies: - "@types/unist": "npm:^3.0.0" - checksum: 10c0/39127bf5f0594e0a76d9241dec4f7aa26323517120ce1edd5ed91c8c1b9df7d6fb18af556e4b6250f1c7368825720ed892e2b6923be5cdc08a9bb16536dc37b3 - languageName: node - linkType: hard - "unist-util-position@npm:^4.0.0": version: 4.0.4 resolution: "unist-util-position@npm:4.0.4" @@ -48572,15 +44836,6 @@ __metadata: languageName: node linkType: hard -"unist-util-position@npm:^5.0.0": - version: 5.0.0 - resolution: "unist-util-position@npm:5.0.0" - dependencies: - "@types/unist": "npm:^3.0.0" - checksum: 10c0/dde3b31e314c98f12b4dc6402f9722b2bf35e96a4f2d463233dd90d7cde2d4928074a7a11eff0a5eb1f4e200f27fc1557e0a64a7e8e4da6558542f251b1b7400 - languageName: node - linkType: hard - "unist-util-remove-position@npm:^4.0.0": version: 4.0.2 resolution: "unist-util-remove-position@npm:4.0.2" @@ -48591,16 +44846,6 @@ __metadata: languageName: node linkType: hard -"unist-util-remove-position@npm:^5.0.0": - version: 5.0.0 - resolution: "unist-util-remove-position@npm:5.0.0" - dependencies: - "@types/unist": "npm:^3.0.0" - unist-util-visit: "npm:^5.0.0" - checksum: 10c0/e8c76da4399446b3da2d1c84a97c607b37d03d1d92561e14838cbe4fdcb485bfc06c06cfadbb808ccb72105a80643976d0660d1fe222ca372203075be9d71105 - languageName: node - linkType: hard - "unist-util-select@npm:^4.0.0, unist-util-select@npm:^4.0.1": version: 4.0.3 resolution: "unist-util-select@npm:4.0.3" @@ -48631,15 +44876,6 @@ __metadata: languageName: node linkType: hard -"unist-util-stringify-position@npm:^4.0.0": - version: 4.0.0 - resolution: "unist-util-stringify-position@npm:4.0.0" - dependencies: - "@types/unist": "npm:^3.0.0" - checksum: 10c0/dfe1dbe79ba31f589108cb35e523f14029b6675d741a79dea7e5f3d098785045d556d5650ec6a8338af11e9e78d2a30df12b1ee86529cded1098da3f17ee999e - languageName: node - linkType: hard - "unist-util-visit-parents@npm:^3.0.0": version: 3.1.1 resolution: "unist-util-visit-parents@npm:3.1.1" @@ -48846,28 +45082,6 @@ __metadata: languageName: node linkType: hard -"update-notifier@npm:^6.0.2": - version: 6.0.2 - resolution: "update-notifier@npm:6.0.2" - dependencies: - boxen: "npm:^7.0.0" - chalk: "npm:^5.0.1" - configstore: "npm:^6.0.0" - has-yarn: "npm:^3.0.0" - import-lazy: "npm:^4.0.0" - is-ci: "npm:^3.0.1" - is-installed-globally: "npm:^0.4.0" - is-npm: "npm:^6.0.0" - is-yarn-global: "npm:^0.4.0" - latest-version: "npm:^7.0.0" - pupa: "npm:^3.1.0" - semver: "npm:^7.3.7" - semver-diff: "npm:^4.0.0" - xdg-basedir: "npm:^5.1.0" - checksum: 10c0/ad3980073312df904133a6e6c554a7f9d0832ed6275e55f5a546313fe77a0f20f23a7b1b4aeb409e20a78afb06f4d3b2b28b332d9cfb55745b5d1ea155810bcc - languageName: node - linkType: hard - "upper-case-first@npm:^2.0.2": version: 2.0.2 resolution: "upper-case-first@npm:2.0.2" @@ -48909,23 +45123,6 @@ __metadata: languageName: node linkType: hard -"url-loader@npm:^4.1.1": - version: 4.1.1 - resolution: "url-loader@npm:4.1.1" - dependencies: - loader-utils: "npm:^2.0.0" - mime-types: "npm:^2.1.27" - schema-utils: "npm:^3.0.0" - peerDependencies: - file-loader: "*" - webpack: ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - file-loader: - optional: true - checksum: 10c0/71b6300e02ce26c70625eae1a2297c0737635038c62691bb3007ac33e85c0130efc74bfb444baf5c6b3bad5953491159d31d66498967d1417865d0c7e7cd1a64 - languageName: node - linkType: hard - "url-parse-lax@npm:^3.0.0": version: 3.0.0 resolution: "url-parse-lax@npm:3.0.0" @@ -48952,7 +45149,7 @@ __metadata: languageName: node linkType: hard -"url@npm:^0.11.0, url@npm:~0.11.0": +"url@npm:~0.11.0": version: 0.11.4 resolution: "url@npm:0.11.4" dependencies: @@ -49129,13 +45326,6 @@ __metadata: languageName: node linkType: hard -"utila@npm:~0.4": - version: 0.4.0 - resolution: "utila@npm:0.4.0" - checksum: 10c0/2791604e09ca4f77ae314df83e80d1805f867eb5c7e13e7413caee01273c278cf2c9a3670d8d25c889a877f7b149d892fe61b0181a81654b425e9622ab23d42e - languageName: node - linkType: hard - "utility-types@npm:^3.10.0": version: 3.11.0 resolution: "utility-types@npm:3.11.0" @@ -49349,16 +45539,6 @@ __metadata: languageName: node linkType: hard -"vfile-location@npm:^5.0.0": - version: 5.0.3 - resolution: "vfile-location@npm:5.0.3" - dependencies: - "@types/unist": "npm:^3.0.0" - vfile: "npm:^6.0.0" - checksum: 10c0/1711f67802a5bc175ea69750d59863343ed43d1b1bb25c0a9063e4c70595e673e53e2ed5cdbb6dcdc370059b31605144d95e8c061b9361bcc2b036b8f63a4966 - languageName: node - linkType: hard - "vfile-matter@npm:^3.0.1": version: 3.0.1 resolution: "vfile-matter@npm:3.0.1" @@ -49390,16 +45570,6 @@ __metadata: languageName: node linkType: hard -"vfile-message@npm:^4.0.0": - version: 4.0.2 - resolution: "vfile-message@npm:4.0.2" - dependencies: - "@types/unist": "npm:^3.0.0" - unist-util-stringify-position: "npm:^4.0.0" - checksum: 10c0/07671d239a075f888b78f318bc1d54de02799db4e9dce322474e67c35d75ac4a5ac0aaf37b18801d91c9f8152974ea39678aa72d7198758b07f3ba04fb7d7514 - languageName: node - linkType: hard - "vfile@npm:^4.0.0": version: 4.2.1 resolution: "vfile@npm:4.2.1" @@ -49424,17 +45594,6 @@ __metadata: languageName: node linkType: hard -"vfile@npm:^6.0.0, vfile@npm:^6.0.1": - version: 6.0.2 - resolution: "vfile@npm:6.0.2" - dependencies: - "@types/unist": "npm:^3.0.0" - unist-util-stringify-position: "npm:^4.0.0" - vfile-message: "npm:^4.0.0" - checksum: 10c0/96b7e060b332ff1b05462053bd9b0f39062c00c5eabb78fc75603cc808d5f77c4379857fffca3e30a28e0aad2d51c065dfcd4a43fbe15b1fc9c2aaa9ac1be8e1 - languageName: node - linkType: hard - "vinyl-file@npm:^3.0.0": version: 3.0.0 resolution: "vinyl-file@npm:3.0.0" @@ -49682,7 +45841,7 @@ __metadata: languageName: node linkType: hard -"vm-browserify@npm:^1.0.0, vm-browserify@npm:^1.1.2": +"vm-browserify@npm:^1.0.0": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" checksum: 10c0/0cc1af6e0d880deb58bc974921320c187f9e0a94f25570fca6b1bd64e798ce454ab87dfd797551b1b0cc1849307421aae0193cedf5f06bdb5680476780ee344b @@ -49860,7 +46019,7 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.2.0, watchpack@npm:^2.4.0, watchpack@npm:^2.4.1": +"watchpack@npm:^2.2.0, watchpack@npm:^2.4.0": version: 2.4.1 resolution: "watchpack@npm:2.4.1" dependencies: @@ -49870,15 +46029,6 @@ __metadata: languageName: node linkType: hard -"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": - version: 1.7.3 - resolution: "wbuf@npm:1.7.3" - dependencies: - minimalistic-assert: "npm:^1.0.0" - checksum: 10c0/56edcc5ef2b3d30913ba8f1f5cccc364d180670b24d5f3f8849c1e6fb514e5c7e3a87548ae61227a82859eba6269c11393ae24ce12a2ea1ecb9b465718ddced7 - languageName: node - linkType: hard - "wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" @@ -49943,101 +46093,6 @@ __metadata: languageName: node linkType: hard -"webpack-bundle-analyzer@npm:^4.9.0": - version: 4.10.2 - resolution: "webpack-bundle-analyzer@npm:4.10.2" - dependencies: - "@discoveryjs/json-ext": "npm:0.5.7" - acorn: "npm:^8.0.4" - acorn-walk: "npm:^8.0.0" - commander: "npm:^7.2.0" - debounce: "npm:^1.2.1" - escape-string-regexp: "npm:^4.0.0" - gzip-size: "npm:^6.0.0" - html-escaper: "npm:^2.0.2" - opener: "npm:^1.5.2" - picocolors: "npm:^1.0.0" - sirv: "npm:^2.0.3" - ws: "npm:^7.3.1" - bin: - webpack-bundle-analyzer: lib/bin/analyzer.js - checksum: 10c0/00603040e244ead15b2d92981f0559fa14216381349412a30070a7358eb3994cd61a8221d34a3b3fb8202dc3d1c5ee1fbbe94c5c52da536e5b410aa1cf279a48 - languageName: node - linkType: hard - -"webpack-dev-middleware@npm:^5.3.4": - version: 5.3.4 - resolution: "webpack-dev-middleware@npm:5.3.4" - dependencies: - colorette: "npm:^2.0.10" - memfs: "npm:^3.4.3" - mime-types: "npm:^2.1.31" - range-parser: "npm:^1.2.1" - schema-utils: "npm:^4.0.0" - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 10c0/257df7d6bc5494d1d3cb66bba70fbdf5a6e0423e39b6420f7631aeb52435afbfbff8410a62146dcdf3d2f945c62e03193aae2ac1194a2f7d5a2523b9d194e9e1 - languageName: node - linkType: hard - -"webpack-dev-server@npm:^4.15.1": - version: 4.15.2 - resolution: "webpack-dev-server@npm:4.15.2" - dependencies: - "@types/bonjour": "npm:^3.5.9" - "@types/connect-history-api-fallback": "npm:^1.3.5" - "@types/express": "npm:^4.17.13" - "@types/serve-index": "npm:^1.9.1" - "@types/serve-static": "npm:^1.13.10" - "@types/sockjs": "npm:^0.3.33" - "@types/ws": "npm:^8.5.5" - ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.0.11" - chokidar: "npm:^3.5.3" - colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" - connect-history-api-fallback: "npm:^2.0.0" - default-gateway: "npm:^6.0.3" - express: "npm:^4.17.3" - graceful-fs: "npm:^4.2.6" - html-entities: "npm:^2.3.2" - http-proxy-middleware: "npm:^2.0.3" - ipaddr.js: "npm:^2.0.1" - launch-editor: "npm:^2.6.0" - open: "npm:^8.0.9" - p-retry: "npm:^4.5.0" - rimraf: "npm:^3.0.2" - schema-utils: "npm:^4.0.0" - selfsigned: "npm:^2.1.1" - serve-index: "npm:^1.9.1" - sockjs: "npm:^0.3.24" - spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^5.3.4" - ws: "npm:^8.13.0" - peerDependencies: - webpack: ^4.37.0 || ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - bin: - webpack-dev-server: bin/webpack-dev-server.js - checksum: 10c0/625bd5b79360afcf98782c8b1fd710b180bb0e96d96b989defff550c546890010ceea82ffbecb2a0a23f7f018bc72f2dee7b3070f7b448fb0110df6657fb2904 - languageName: node - linkType: hard - -"webpack-merge@npm:^5.9.0": - version: 5.10.0 - resolution: "webpack-merge@npm:5.10.0" - dependencies: - clone-deep: "npm:^4.0.1" - flat: "npm:^5.0.2" - wildcard: "npm:^2.0.0" - checksum: 10c0/b607c84cabaf74689f965420051a55a08722d897bdd6c29cb0b2263b451c090f962d41ecf8c9bf56b0ab3de56e65476ace0a8ecda4f4a4663684243d90e0512b - languageName: node - linkType: hard - "webpack-node-externals@npm:3.0.0": version: 3.0.0 resolution: "webpack-node-externals@npm:3.0.0" @@ -50133,75 +46188,6 @@ __metadata: languageName: node linkType: hard -"webpack@npm:^5.88.1": - version: 5.93.0 - resolution: "webpack@npm:5.93.0" - dependencies: - "@types/eslint-scope": "npm:^3.7.3" - "@types/estree": "npm:^1.0.5" - "@webassemblyjs/ast": "npm:^1.12.1" - "@webassemblyjs/wasm-edit": "npm:^1.12.1" - "@webassemblyjs/wasm-parser": "npm:^1.12.1" - acorn: "npm:^8.7.1" - acorn-import-attributes: "npm:^1.9.5" - browserslist: "npm:^4.21.10" - chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.0" - es-module-lexer: "npm:^1.2.1" - eslint-scope: "npm:5.1.1" - events: "npm:^3.2.0" - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.11" - json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" - mime-types: "npm:^2.1.27" - neo-async: "npm:^2.6.2" - schema-utils: "npm:^3.2.0" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.2.3" - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 10c0/f0c72f1325ff57a4cc461bb978e6e1296f2a7d45c9765965271aa686ccdd448512956f4d7fdcf8c164d073af046c5a0aba17ce85ea98e33e5e2bfbfe13aa5808 - languageName: node - linkType: hard - -"webpackbar@npm:^5.0.2": - version: 5.0.2 - resolution: "webpackbar@npm:5.0.2" - dependencies: - chalk: "npm:^4.1.0" - consola: "npm:^2.15.3" - pretty-time: "npm:^1.1.0" - std-env: "npm:^3.0.1" - peerDependencies: - webpack: 3 || 4 || 5 - checksum: 10c0/336568a6ed1c1ad743c8d20a5cab5875a7ebe1e96181f49ae0a1a897f1a59d1661d837574a25d8ba9dfa4f2f705bd46ca0cd037ff60286ff70fb8d9db2b0c123 - languageName: node - linkType: hard - -"websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": - version: 0.7.4 - resolution: "websocket-driver@npm:0.7.4" - dependencies: - http-parser-js: "npm:>=0.5.1" - safe-buffer: "npm:>=5.1.0" - websocket-extensions: "npm:>=0.1.1" - checksum: 10c0/5f09547912b27bdc57bac17b7b6527d8993aa4ac8a2d10588bb74aebaf785fdcf64fea034aae0c359b7adff2044dd66f3d03866e4685571f81b13e548f9021f1 - languageName: node - linkType: hard - -"websocket-extensions@npm:>=0.1.1": - version: 0.1.4 - resolution: "websocket-extensions@npm:0.1.4" - checksum: 10c0/bbc8c233388a0eb8a40786ee2e30d35935cacbfe26ab188b3e020987e85d519c2009fe07cfc37b7f718b85afdba7e54654c9153e6697301f72561bfe429177e0 - languageName: node - linkType: hard - "whatwg-encoding@npm:^2.0.0": version: 2.0.0 resolution: "whatwg-encoding@npm:2.0.0" @@ -50341,7 +46327,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.12, which@npm:^1.2.9, which@npm:^1.3.1": +"which@npm:^1.2.12, which@npm:^1.2.9": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -50415,22 +46401,6 @@ __metadata: languageName: node linkType: hard -"widest-line@npm:^4.0.1": - version: 4.0.1 - resolution: "widest-line@npm:4.0.1" - dependencies: - string-width: "npm:^5.0.1" - checksum: 10c0/7da9525ba45eaf3e4ed1a20f3dcb9b85bd9443962450694dae950f4bdd752839747bbc14713522b0b93080007de8e8af677a61a8c2114aa553ad52bde72d0f9c - languageName: node - linkType: hard - -"wildcard@npm:^2.0.0": - version: 2.0.1 - resolution: "wildcard@npm:2.0.1" - checksum: 10c0/08f70cd97dd9a20aea280847a1fe8148e17cae7d231640e41eb26d2388697cbe65b67fd9e68715251c39b080c5ae4f76d71a9a69fa101d897273efdfb1b58bf7 - languageName: node - linkType: hard - "windows-release@npm:^4.0.0": version: 4.0.0 resolution: "windows-release@npm:4.0.0" @@ -50483,7 +46453,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^8.0.1, wrap-ansi@npm:^8.1.0": +"wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" dependencies: @@ -50564,7 +46534,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^5.2.0 || ^6.0.0 || ^7.0.0, ws@npm:^7.3.1": +"ws@npm:^5.2.0 || ^6.0.0 || ^7.0.0": version: 7.5.10 resolution: "ws@npm:7.5.10" peerDependencies: @@ -50617,13 +46587,6 @@ __metadata: languageName: node linkType: hard -"xdg-basedir@npm:^5.0.1, xdg-basedir@npm:^5.1.0": - version: 5.1.0 - resolution: "xdg-basedir@npm:5.1.0" - checksum: 10c0/c88efabc71ffd996ba9ad8923a8cc1c7c020a03e2c59f0ffa72e06be9e724ad2a0fccef488757bc6ed3d8849d753dd25082d1035d95cb179e79eae4d034d0b80 - languageName: node - linkType: hard - "xlsx-ugnis@npm:^0.19.3": version: 0.19.3 resolution: "xlsx-ugnis@npm:0.19.3" @@ -50642,17 +46605,6 @@ __metadata: languageName: node linkType: hard -"xml-js@npm:^1.6.11": - version: 1.6.11 - resolution: "xml-js@npm:1.6.11" - dependencies: - sax: "npm:^1.2.4" - bin: - xml-js: ./bin/cli.js - checksum: 10c0/c83631057f10bf90ea785cee434a8a1a0030c7314fe737ad9bf568a281083b565b28b14c9e9ba82f11fc9dc582a3a907904956af60beb725be1c9ad4b030bc5a - languageName: node - linkType: hard - "xml-name-validator@npm:^4.0.0": version: 4.0.0 resolution: "xml-name-validator@npm:4.0.0"