Compare commits
	
		
			1 Commits
		
	
	
		
			AUT-157-AU
			...
			AUT-995
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2460e9f281 | 
							
								
								
									
										262
									
								
								packages/backend/src/apps/wordpress/actions/create-post/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								packages/backend/src/apps/wordpress/actions/create-post/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  | import isEmpty from 'lodash/isEmpty.js'; | ||||||
|  | import omitBy from 'lodash/omitBy.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create post', | ||||||
|  |   key: 'createPost', | ||||||
|  |   description: 'Creates a new post.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Title', | ||||||
|  |       key: 'title', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Content', | ||||||
|  |       key: 'content', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Excerpt', | ||||||
|  |       key: 'excerpt', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Password', | ||||||
|  |       key: 'password', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: 'A password to protect access to the content and excerpt.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Author', | ||||||
|  |       key: 'author', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listUsers', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Featured Media', | ||||||
|  |       key: 'featuredMedia', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listMedia', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Comment Status', | ||||||
|  |       key: 'commentStatus', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Open', value: 'open' }, | ||||||
|  |         { label: 'Closed', value: 'closed' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Ping Status', | ||||||
|  |       key: 'pingStatus', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Open', value: 'open' }, | ||||||
|  |         { label: 'Closed', value: 'closed' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Format', | ||||||
|  |       key: 'format', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Standard', value: 'standard' }, | ||||||
|  |         { label: 'Aside', value: 'aside' }, | ||||||
|  |         { label: 'Chat', value: 'chat' }, | ||||||
|  |         { label: 'Gallery', value: 'gallery' }, | ||||||
|  |         { label: 'Link', value: 'link' }, | ||||||
|  |         { label: 'Image', value: 'image' }, | ||||||
|  |         { label: 'Quote', value: 'quote' }, | ||||||
|  |         { label: 'Status', value: 'status' }, | ||||||
|  |         { label: 'Status', value: 'status' }, | ||||||
|  |         { label: 'Video', value: 'video' }, | ||||||
|  |         { label: 'Audio', value: 'audio' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Sticky', | ||||||
|  |       key: 'sticky', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'False', value: 'false' }, | ||||||
|  |         { label: 'True', value: 'true' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Categories', | ||||||
|  |       key: 'categoryIds', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Category', | ||||||
|  |           key: 'categoryId', | ||||||
|  |           type: 'dropdown', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |           source: { | ||||||
|  |             type: 'query', | ||||||
|  |             name: 'getDynamicData', | ||||||
|  |             arguments: [ | ||||||
|  |               { | ||||||
|  |                 name: 'key', | ||||||
|  |                 value: 'listCategories', | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Tags', | ||||||
|  |       key: 'tagIds', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Tag', | ||||||
|  |           key: 'tagId', | ||||||
|  |           type: 'dropdown', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |           source: { | ||||||
|  |             type: 'query', | ||||||
|  |             name: 'getDynamicData', | ||||||
|  |             arguments: [ | ||||||
|  |               { | ||||||
|  |                 name: 'key', | ||||||
|  |                 value: 'listTags', | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Status', | ||||||
|  |       key: 'status', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listStatuses', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Date', | ||||||
|  |       key: 'date', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: "Post publish date in the site's timezone", | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { | ||||||
|  |       title, | ||||||
|  |       content, | ||||||
|  |       excerpt, | ||||||
|  |       password, | ||||||
|  |       author, | ||||||
|  |       featuredMedia, | ||||||
|  |       commentStatus, | ||||||
|  |       pingStatus, | ||||||
|  |       format, | ||||||
|  |       sticky, | ||||||
|  |       categoryIds, | ||||||
|  |       tagIds, | ||||||
|  |       status, | ||||||
|  |       date, | ||||||
|  |     } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const allCategoryIds = categoryIds | ||||||
|  |       ?.map((categoryId) => categoryId.categoryId) | ||||||
|  |       .filter(Boolean); | ||||||
|  |  | ||||||
|  |     const allTagIds = tagIds?.map((tagId) => tagId.tagId).filter(Boolean); | ||||||
|  |  | ||||||
|  |     let body = { | ||||||
|  |       title, | ||||||
|  |       content, | ||||||
|  |       excerpt, | ||||||
|  |       password, | ||||||
|  |       author, | ||||||
|  |       featured_media: featuredMedia, | ||||||
|  |       comment_status: commentStatus, | ||||||
|  |       ping_status: pingStatus, | ||||||
|  |       format, | ||||||
|  |       sticky, | ||||||
|  |       categories: allCategoryIds, | ||||||
|  |       tags: allTagIds, | ||||||
|  |       status, | ||||||
|  |       date, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     body = omitBy(body, isEmpty); | ||||||
|  |  | ||||||
|  |     const response = await $.http.post('?rest_route=/wp/v2/posts', body); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ raw: response.data }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										3
									
								
								packages/backend/src/apps/wordpress/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/backend/src/apps/wordpress/actions/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | import createPost from './create-post/index.js'; | ||||||
|  |  | ||||||
|  | export default [createPost]; | ||||||
| @@ -1,3 +1,7 @@ | |||||||
|  | import listCategories from './list-categories/index.js'; | ||||||
|  | import listMedia from './list-media/index.js'; | ||||||
| import listStatuses from './list-statuses/index.js'; | import listStatuses from './list-statuses/index.js'; | ||||||
|  | import listTags from './list-tags/index.js'; | ||||||
|  | import listUsers from './list-users/index.js'; | ||||||
|  |  | ||||||
| export default [listStatuses]; | export default [listCategories, listMedia, listStatuses, listTags, listUsers]; | ||||||
|   | |||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List categories', | ||||||
|  |   key: 'listCategories', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const categories = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const params = { | ||||||
|  |       page: 1, | ||||||
|  |       per_page: 100, | ||||||
|  |       order: 'desc', | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let totalPages = 1; | ||||||
|  |     do { | ||||||
|  |       const { data, headers } = await $.http.get( | ||||||
|  |         '?rest_route=/wp/v2/categories', | ||||||
|  |         { | ||||||
|  |           params, | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       params.page = params.page + 1; | ||||||
|  |       totalPages = Number(headers['x-wp-totalpages']); | ||||||
|  |  | ||||||
|  |       if (data) { | ||||||
|  |         for (const category of data) { | ||||||
|  |           categories.data.push({ | ||||||
|  |             value: category.id, | ||||||
|  |             name: category.name, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } while (params.page <= totalPages); | ||||||
|  |  | ||||||
|  |     return categories; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List media', | ||||||
|  |   key: 'listMedia', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const media = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const params = { | ||||||
|  |       page: 1, | ||||||
|  |       per_page: 100, | ||||||
|  |       order: 'desc', | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let totalPages = 1; | ||||||
|  |     do { | ||||||
|  |       const { data, headers } = await $.http.get('?rest_route=/wp/v2/media', { | ||||||
|  |         params, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       params.page = params.page + 1; | ||||||
|  |       totalPages = Number(headers['x-wp-totalpages']); | ||||||
|  |  | ||||||
|  |       if (data) { | ||||||
|  |         for (const medium of data) { | ||||||
|  |           media.data.push({ | ||||||
|  |             value: medium.id, | ||||||
|  |             name: medium.slug, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } while (params.page <= totalPages); | ||||||
|  |  | ||||||
|  |     return media; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List tags', | ||||||
|  |   key: 'listTags', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const tags = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const params = { | ||||||
|  |       page: 1, | ||||||
|  |       per_page: 100, | ||||||
|  |       order: 'desc', | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let totalPages = 1; | ||||||
|  |     do { | ||||||
|  |       const { data, headers } = await $.http.get('?rest_route=/wp/v2/tags', { | ||||||
|  |         params, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       params.page = params.page + 1; | ||||||
|  |       totalPages = Number(headers['x-wp-totalpages']); | ||||||
|  |  | ||||||
|  |       if (data) { | ||||||
|  |         for (const tag of data) { | ||||||
|  |           tags.data.push({ | ||||||
|  |             value: tag.id, | ||||||
|  |             name: tag.name, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } while (params.page <= totalPages); | ||||||
|  |  | ||||||
|  |     return tags; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List users', | ||||||
|  |   key: 'listUsers', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const users = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const params = { | ||||||
|  |       page: 1, | ||||||
|  |       per_page: 100, | ||||||
|  |       order: 'desc', | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let totalPages = 1; | ||||||
|  |     do { | ||||||
|  |       const { data, headers } = await $.http.get('?rest_route=/wp/v2/users', { | ||||||
|  |         params, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       params.page = params.page + 1; | ||||||
|  |       totalPages = Number(headers['x-wp-totalpages']); | ||||||
|  |  | ||||||
|  |       if (data) { | ||||||
|  |         for (const user of data) { | ||||||
|  |           users.data.push({ | ||||||
|  |             value: user.id, | ||||||
|  |             name: user.name, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } while (params.page <= totalPages); | ||||||
|  |  | ||||||
|  |     return users; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -4,6 +4,7 @@ import setBaseUrl from './common/set-base-url.js'; | |||||||
| import auth from './auth/index.js'; | import auth from './auth/index.js'; | ||||||
| import triggers from './triggers/index.js'; | import triggers from './triggers/index.js'; | ||||||
| import dynamicData from './dynamic-data/index.js'; | import dynamicData from './dynamic-data/index.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
|  |  | ||||||
| export default defineApp({ | export default defineApp({ | ||||||
|   name: 'WordPress', |   name: 'WordPress', | ||||||
| @@ -18,4 +19,5 @@ export default defineApp({ | |||||||
|   auth, |   auth, | ||||||
|   triggers, |   triggers, | ||||||
|   dynamicData, |   dynamicData, | ||||||
|  |   actions, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ const appConfig = { | |||||||
|   isDev: appEnv === 'development', |   isDev: appEnv === 'development', | ||||||
|   isTest: appEnv === 'test', |   isTest: appEnv === 'test', | ||||||
|   isProd: appEnv === 'production', |   isProd: appEnv === 'production', | ||||||
|   version: '0.12.0', |   version: '0.11.0', | ||||||
|   postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development', |   postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development', | ||||||
|   postgresSchema: process.env.POSTGRES_SCHEMA || 'public', |   postgresSchema: process.env.POSTGRES_SCHEMA || 'public', | ||||||
|   postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'), |   postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'), | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ describe('GET /api/v1/automatisch/version', () => { | |||||||
|  |  | ||||||
|     const expectedPayload = { |     const expectedPayload = { | ||||||
|       data: { |       data: { | ||||||
|         version: '0.12.0', |         version: '0.11.0', | ||||||
|       }, |       }, | ||||||
|       meta: { |       meta: { | ||||||
|         count: 1, |         count: 1, | ||||||
|   | |||||||
| @@ -33,8 +33,8 @@ class User extends Base { | |||||||
|       fullName: { type: 'string', minLength: 1 }, |       fullName: { type: 'string', minLength: 1 }, | ||||||
|       email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 }, |       email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 }, | ||||||
|       password: { type: 'string' }, |       password: { type: 'string' }, | ||||||
|       resetPasswordToken: { type: ['string', 'null'] }, |       resetPasswordToken: { type: 'string' }, | ||||||
|       resetPasswordTokenSentAt: { type: ['string', 'null'], format: 'date-time' }, |       resetPasswordTokenSentAt: { type: 'string' }, | ||||||
|       trialExpiryDate: { type: 'string' }, |       trialExpiryDate: { type: 'string' }, | ||||||
|       roleId: { type: 'string', format: 'uuid' }, |       roleId: { type: 'string', format: 'uuid' }, | ||||||
|       deletedAt: { type: 'string' }, |       deletedAt: { type: 'string' }, | ||||||
|   | |||||||
| @@ -40,7 +40,6 @@ export const worker = new Worker( | |||||||
|       await user.$relatedQuery('usageData').withSoftDeleted().hardDelete(); |       await user.$relatedQuery('usageData').withSoftDeleted().hardDelete(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     await user.$relatedQuery('accessTokens').withSoftDeleted().hardDelete(); |  | ||||||
|     await user.$query().withSoftDeleted().hardDelete(); |     await user.$query().withSoftDeleted().hardDelete(); | ||||||
|   }, |   }, | ||||||
|   { connection: redisConfig } |   { connection: redisConfig } | ||||||
|   | |||||||
| @@ -518,6 +518,7 @@ export default defineConfig({ | |||||||
|           collapsible: true, |           collapsible: true, | ||||||
|           collapsed: true, |           collapsed: true, | ||||||
|           items: [ |           items: [ | ||||||
|  |             { text: 'Actions', link: '/apps/wordpress/actions' }, | ||||||
|             { text: 'Triggers', link: '/apps/wordpress/triggers' }, |             { text: 'Triggers', link: '/apps/wordpress/triggers' }, | ||||||
|             { text: 'Connection', link: '/apps/wordpress/connection' }, |             { text: 'Connection', link: '/apps/wordpress/connection' }, | ||||||
|           ], |           ], | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								packages/docs/pages/apps/wordpress/actions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/docs/pages/apps/wordpress/actions.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | --- | ||||||
|  | favicon: /favicons/wordpress.svg | ||||||
|  | items: | ||||||
|  |   - name: Create post | ||||||
|  |     desc: Creates a new post. | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | <script setup> | ||||||
|  |   import CustomListing from '../../components/CustomListing.vue' | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <CustomListing /> | ||||||
| @@ -6,12 +6,16 @@ We use `lerna` with `yarn workspaces` to manage the mono repository. We have the | |||||||
| . | . | ||||||
| ├── packages | ├── packages | ||||||
| │   ├── backend | │   ├── backend | ||||||
|  | │   ├── cli | ||||||
| │   ├── docs | │   ├── docs | ||||||
| │   ├── e2e-tests | │   ├── e2e-tests | ||||||
|  | │   ├── types | ||||||
| │   └── web | │   └── web | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| - `backend` - The backend package contains the backend application and all integrations. | - `backend` - The backend package contains the backend application and all integrations. | ||||||
|  | - `cli` - The cli package contains the CLI application of Automatisch. | ||||||
| - `docs` - The docs package contains the documentation website. | - `docs` - The docs package contains the documentation website. | ||||||
| - `e2e-tests` - The e2e-tests package contains the end-to-end tests for the internal usage. | - `e2e-tests` - The e2e-tests package contains the end-to-end tests for the internal usage. | ||||||
|  | - `types` - The types package contains the shared types for both the backend and web packages. | ||||||
| - `web` - The web package contains the frontend application of Automatisch. | - `web` - The web package contains the frontend application of Automatisch. | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ | |||||||
|     "@apollo/client": "^3.6.9", |     "@apollo/client": "^3.6.9", | ||||||
|     "@casl/ability": "^6.5.0", |     "@casl/ability": "^6.5.0", | ||||||
|     "@casl/react": "^3.1.0", |     "@casl/react": "^3.1.0", | ||||||
|     "@dagrejs/dagre": "^1.1.2", |  | ||||||
|     "@emotion/react": "^11.4.1", |     "@emotion/react": "^11.4.1", | ||||||
|     "@emotion/styled": "^11.3.0", |     "@emotion/styled": "^11.3.0", | ||||||
|     "@hookform/resolvers": "^2.8.8", |     "@hookform/resolvers": "^2.8.8", | ||||||
| @@ -33,7 +32,6 @@ | |||||||
|     "react-router-dom": "^6.0.2", |     "react-router-dom": "^6.0.2", | ||||||
|     "react-scripts": "5.0.0", |     "react-scripts": "5.0.0", | ||||||
|     "react-window": "^1.8.9", |     "react-window": "^1.8.9", | ||||||
|     "reactflow": "^11.11.2", |  | ||||||
|     "slate": "^0.94.1", |     "slate": "^0.94.1", | ||||||
|     "slate-history": "^0.93.0", |     "slate-history": "^0.93.0", | ||||||
|     "slate-react": "^0.94.2", |     "slate-react": "^0.94.2", | ||||||
|   | |||||||
| @@ -68,10 +68,7 @@ function AccountDropdownMenu(props) { | |||||||
| AccountDropdownMenu.propTypes = { | AccountDropdownMenu.propTypes = { | ||||||
|   open: PropTypes.bool.isRequired, |   open: PropTypes.bool.isRequired, | ||||||
|   onClose: PropTypes.func.isRequired, |   onClose: PropTypes.func.isRequired, | ||||||
|   anchorEl: PropTypes.oneOfType([ |   anchorEl: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), | ||||||
|     PropTypes.func, |  | ||||||
|     PropTypes.shape({ current: PropTypes.instanceOf(Element) }), |  | ||||||
|   ]), |  | ||||||
|   id: PropTypes.string.isRequired, |   id: PropTypes.string.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ function AdminApplicationSettings(props) { | |||||||
|  |  | ||||||
|   const handleSubmit = async (values) => { |   const handleSubmit = async (values) => { | ||||||
|     try { |     try { | ||||||
|       if (!appConfig?.data) { |       if (!appConfig.data) { | ||||||
|         await createAppConfig({ |         await createAppConfig({ | ||||||
|           variables: { |           variables: { | ||||||
|             input: { key: props.appKey, ...values }, |             input: { key: props.appKey, ...values }, | ||||||
| @@ -69,7 +69,6 @@ function AdminApplicationSettings(props) { | |||||||
|     }), |     }), | ||||||
|     [appConfig?.data], |     [appConfig?.data], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Form |     <Form | ||||||
|       defaultValues={defaultValues} |       defaultValues={defaultValues} | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ import * as URLS from 'config/urls'; | |||||||
| import useFormatMessage from 'hooks/useFormatMessage'; | import useFormatMessage from 'hooks/useFormatMessage'; | ||||||
| import { ConnectionPropType } from 'propTypes/propTypes'; | import { ConnectionPropType } from 'propTypes/propTypes'; | ||||||
| import { useQueryClient } from '@tanstack/react-query'; | import { useQueryClient } from '@tanstack/react-query'; | ||||||
| import Can from 'components/Can'; |  | ||||||
|  |  | ||||||
| function ContextMenu(props) { | function ContextMenu(props) { | ||||||
|   const { |   const { | ||||||
| @@ -45,35 +44,21 @@ function ContextMenu(props) { | |||||||
|       hideBackdrop={false} |       hideBackdrop={false} | ||||||
|       anchorEl={anchorEl} |       anchorEl={anchorEl} | ||||||
|     > |     > | ||||||
|       <Can I="read" a="Flow" passThrough> |  | ||||||
|         {(allowed) => ( |  | ||||||
|       <MenuItem |       <MenuItem | ||||||
|         component={Link} |         component={Link} | ||||||
|         to={URLS.APP_FLOWS_FOR_CONNECTION(appKey, connection.id)} |         to={URLS.APP_FLOWS_FOR_CONNECTION(appKey, connection.id)} | ||||||
|         onClick={createActionHandler({ type: 'viewFlows' })} |         onClick={createActionHandler({ type: 'viewFlows' })} | ||||||
|             disabled={!allowed} |  | ||||||
|       > |       > | ||||||
|         {formatMessage('connection.viewFlows')} |         {formatMessage('connection.viewFlows')} | ||||||
|       </MenuItem> |       </MenuItem> | ||||||
|         )} |  | ||||||
|       </Can> |  | ||||||
|  |  | ||||||
|       <Can I="update" a="Connection" passThrough> |       <MenuItem onClick={createActionHandler({ type: 'test' })}> | ||||||
|         {(allowed) => ( |  | ||||||
|           <MenuItem |  | ||||||
|             onClick={createActionHandler({ type: 'test' })} |  | ||||||
|             disabled={!allowed} |  | ||||||
|           > |  | ||||||
|         {formatMessage('connection.testConnection')} |         {formatMessage('connection.testConnection')} | ||||||
|       </MenuItem> |       </MenuItem> | ||||||
|         )} |  | ||||||
|       </Can> |  | ||||||
|  |  | ||||||
|       <Can I="create" a="Connection" passThrough> |  | ||||||
|         {(allowed) => ( |  | ||||||
|       <MenuItem |       <MenuItem | ||||||
|         component={Link} |         component={Link} | ||||||
|             disabled={!allowed || disableReconnection} |         disabled={disableReconnection} | ||||||
|         to={URLS.APP_RECONNECT_CONNECTION( |         to={URLS.APP_RECONNECT_CONNECTION( | ||||||
|           appKey, |           appKey, | ||||||
|           connection.id, |           connection.id, | ||||||
| @@ -83,19 +68,10 @@ function ContextMenu(props) { | |||||||
|       > |       > | ||||||
|         {formatMessage('connection.reconnect')} |         {formatMessage('connection.reconnect')} | ||||||
|       </MenuItem> |       </MenuItem> | ||||||
|         )} |  | ||||||
|       </Can> |  | ||||||
|  |  | ||||||
|       <Can I="delete" a="Connection" passThrough> |       <MenuItem onClick={createActionHandler({ type: 'delete' })}> | ||||||
|         {(allowed) => ( |  | ||||||
|           <MenuItem |  | ||||||
|             onClick={createActionHandler({ type: 'delete' })} |  | ||||||
|             disabled={!allowed} |  | ||||||
|           > |  | ||||||
|         {formatMessage('connection.delete')} |         {formatMessage('connection.delete')} | ||||||
|       </MenuItem> |       </MenuItem> | ||||||
|         )} |  | ||||||
|       </Can> |  | ||||||
|     </Menu> |     </Menu> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; | |||||||
|  |  | ||||||
| import AppConnectionRow from 'components/AppConnectionRow'; | import AppConnectionRow from 'components/AppConnectionRow'; | ||||||
| import NoResultFound from 'components/NoResultFound'; | import NoResultFound from 'components/NoResultFound'; | ||||||
| import Can from 'components/Can'; |  | ||||||
| import useFormatMessage from 'hooks/useFormatMessage'; | import useFormatMessage from 'hooks/useFormatMessage'; | ||||||
| import * as URLS from 'config/urls'; | import * as URLS from 'config/urls'; | ||||||
| import useAppConnections from 'hooks/useAppConnections'; | import useAppConnections from 'hooks/useAppConnections'; | ||||||
| @@ -17,15 +16,11 @@ function AppConnections(props) { | |||||||
|  |  | ||||||
|   if (!hasConnections) { |   if (!hasConnections) { | ||||||
|     return ( |     return ( | ||||||
|       <Can I="create" a="Connection" passThrough> |  | ||||||
|         {(allowed) => ( |  | ||||||
|       <NoResultFound |       <NoResultFound | ||||||
|  |         to={URLS.APP_ADD_CONNECTION(appKey)} | ||||||
|         text={formatMessage('app.noConnections')} |         text={formatMessage('app.noConnections')} | ||||||
|         data-test="connections-no-results" |         data-test="connections-no-results" | ||||||
|             {...(allowed && { to: URLS.APP_ADD_CONNECTION(appKey) })} |  | ||||||
|       /> |       /> | ||||||
|         )} |  | ||||||
|       </Can> |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import PaginationItem from '@mui/material/PaginationItem'; | |||||||
|  |  | ||||||
| import * as URLS from 'config/urls'; | import * as URLS from 'config/urls'; | ||||||
| import AppFlowRow from 'components/FlowRow'; | import AppFlowRow from 'components/FlowRow'; | ||||||
| import Can from 'components/Can'; |  | ||||||
| import NoResultFound from 'components/NoResultFound'; | import NoResultFound from 'components/NoResultFound'; | ||||||
| import useFormatMessage from 'hooks/useFormatMessage'; | import useFormatMessage from 'hooks/useFormatMessage'; | ||||||
| import useConnectionFlows from 'hooks/useConnectionFlows'; | import useConnectionFlows from 'hooks/useConnectionFlows'; | ||||||
| @@ -37,20 +36,11 @@ function AppFlows(props) { | |||||||
|  |  | ||||||
|   if (!hasFlows) { |   if (!hasFlows) { | ||||||
|     return ( |     return ( | ||||||
|       <Can I="create" a="Flow" passThrough> |  | ||||||
|         {(allowed) => ( |  | ||||||
|       <NoResultFound |       <NoResultFound | ||||||
|  |         to={URLS.CREATE_FLOW_WITH_APP_AND_CONNECTION(appKey, connectionId)} | ||||||
|         text={formatMessage('app.noFlows')} |         text={formatMessage('app.noFlows')} | ||||||
|         data-test="flows-no-results" |         data-test="flows-no-results" | ||||||
|             {...(allowed && { |  | ||||||
|               to: URLS.CREATE_FLOW_WITH_APP_AND_CONNECTION( |  | ||||||
|                 appKey, |  | ||||||
|                 connectionId |  | ||||||
|               ), |  | ||||||
|             })} |  | ||||||
|       /> |       /> | ||||||
|         )} |  | ||||||
|       </Can> |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -165,7 +165,6 @@ function ChooseAppAndEventSubstep(props) { | |||||||
|             value={getOption(appOptions, step.appKey) || null} |             value={getOption(appOptions, step.appKey) || null} | ||||||
|             onChange={onAppChange} |             onChange={onAppChange} | ||||||
|             data-test="choose-app-autocomplete" |             data-test="choose-app-autocomplete" | ||||||
|             componentsProps={{ popper: { className: 'nowheel' } }} |  | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           {step.appKey && ( |           {step.appKey && ( | ||||||
| @@ -228,7 +227,6 @@ function ChooseAppAndEventSubstep(props) { | |||||||
|                 value={getOption(actionOrTriggerOptions, step.key) || null} |                 value={getOption(actionOrTriggerOptions, step.key) || null} | ||||||
|                 onChange={onEventChange} |                 onChange={onEventChange} | ||||||
|                 data-test="choose-event-autocomplete" |                 data-test="choose-event-autocomplete" | ||||||
|                 componentsProps={{ popper: { className: 'nowheel' } }} |  | ||||||
|               /> |               /> | ||||||
|             </Box> |             </Box> | ||||||
|           )} |           )} | ||||||
|   | |||||||
| @@ -240,7 +240,6 @@ function ChooseConnectionSubstep(props) { | |||||||
|             onChange={handleChange} |             onChange={handleChange} | ||||||
|             loading={isAppConnectionsLoading} |             loading={isAppConnectionsLoading} | ||||||
|             data-test="choose-connection-autocomplete" |             data-test="choose-connection-autocomplete" | ||||||
|             componentsProps={{ popper: { className: 'nowheel' } }} |  | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|           <Button |           <Button | ||||||
|   | |||||||
| @@ -32,11 +32,9 @@ function ControlledAutocomplete(props) { | |||||||
|     ...autocompleteProps |     ...autocompleteProps | ||||||
|   } = props; |   } = props; | ||||||
|   let dependsOnValues = []; |   let dependsOnValues = []; | ||||||
|  |  | ||||||
|   if (dependsOn?.length) { |   if (dependsOn?.length) { | ||||||
|     dependsOnValues = watch(dependsOn); |     dependsOnValues = watch(dependsOn); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   React.useEffect(() => { |   React.useEffect(() => { | ||||||
|     const hasDependencies = dependsOnValues.length; |     const hasDependencies = dependsOnValues.length; | ||||||
|     const allDepsSatisfied = dependsOnValues.every(Boolean); |     const allDepsSatisfied = dependsOnValues.every(Boolean); | ||||||
| @@ -46,7 +44,6 @@ function ControlledAutocomplete(props) { | |||||||
|       resetField(name); |       resetField(name); | ||||||
|     } |     } | ||||||
|   }, dependsOnValues); |   }, dependsOnValues); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Controller |     <Controller | ||||||
|       rules={{ required }} |       rules={{ required }} | ||||||
|   | |||||||
| @@ -21,9 +21,7 @@ const CustomOptions = (props) => { | |||||||
|     label, |     label, | ||||||
|     initialTabIndex, |     initialTabIndex, | ||||||
|   } = props; |   } = props; | ||||||
|  |  | ||||||
|   const [activeTabIndex, setActiveTabIndex] = React.useState(undefined); |   const [activeTabIndex, setActiveTabIndex] = React.useState(undefined); | ||||||
|  |  | ||||||
|   React.useEffect( |   React.useEffect( | ||||||
|     function applyInitialActiveTabIndex() { |     function applyInitialActiveTabIndex() { | ||||||
|       setActiveTabIndex((currentActiveTabIndex) => { |       setActiveTabIndex((currentActiveTabIndex) => { | ||||||
| @@ -35,7 +33,6 @@ const CustomOptions = (props) => { | |||||||
|     }, |     }, | ||||||
|     [initialTabIndex], |     [initialTabIndex], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Popper |     <Popper | ||||||
|       open={open} |       open={open} | ||||||
| @@ -50,7 +47,6 @@ const CustomOptions = (props) => { | |||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       ]} |       ]} | ||||||
|       className="nowheel" |  | ||||||
|     > |     > | ||||||
|       <Paper elevation={5} sx={{ width: '100%' }}> |       <Paper elevation={5} sx={{ width: '100%' }}> | ||||||
|         <Tabs |         <Tabs | ||||||
| @@ -79,10 +75,7 @@ const CustomOptions = (props) => { | |||||||
|  |  | ||||||
| CustomOptions.propTypes = { | CustomOptions.propTypes = { | ||||||
|   open: PropTypes.bool.isRequired, |   open: PropTypes.bool.isRequired, | ||||||
|   anchorEl: PropTypes.oneOfType([ |   anchorEl: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired, | ||||||
|     PropTypes.func, |  | ||||||
|     PropTypes.shape({ current: PropTypes.instanceOf(Element) }), |  | ||||||
|   ]), |  | ||||||
|   data: PropTypes.arrayOf( |   data: PropTypes.arrayOf( | ||||||
|     PropTypes.shape({ |     PropTypes.shape({ | ||||||
|       id: PropTypes.string.isRequired, |       id: PropTypes.string.isRequired, | ||||||
|   | |||||||
| @@ -61,7 +61,6 @@ function ControlledCustomAutocomplete(props) { | |||||||
|   const [isSingleChoice, setSingleChoice] = React.useState(undefined); |   const [isSingleChoice, setSingleChoice] = React.useState(undefined); | ||||||
|   const priorStepsWithExecutions = React.useContext(StepExecutionsContext); |   const priorStepsWithExecutions = React.useContext(StepExecutionsContext); | ||||||
|   const editorRef = React.useRef(null); |   const editorRef = React.useRef(null); | ||||||
|   const mountedRef = React.useRef(false); |  | ||||||
|  |  | ||||||
|   const renderElement = React.useCallback( |   const renderElement = React.useCallback( | ||||||
|     (props) => <Element {...props} disabled={disabled} />, |     (props) => <Element {...props} disabled={disabled} />, | ||||||
| @@ -95,15 +94,11 @@ function ControlledCustomAutocomplete(props) { | |||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   React.useEffect(() => { |   React.useEffect(() => { | ||||||
|     if (mountedRef.current) { |  | ||||||
|     const hasDependencies = dependsOnValues.length; |     const hasDependencies = dependsOnValues.length; | ||||||
|     if (hasDependencies) { |     if (hasDependencies) { | ||||||
|       // Reset the field when a dependent has been updated |       // Reset the field when a dependent has been updated | ||||||
|       resetEditor(editor); |       resetEditor(editor); | ||||||
|     } |     } | ||||||
|     } else { |  | ||||||
|       mountedRef.current = true; |  | ||||||
|     } |  | ||||||
|   }, dependsOnValues); |   }, dependsOnValues); | ||||||
|  |  | ||||||
|   React.useEffect( |   React.useEffect( | ||||||
|   | |||||||
| @@ -64,19 +64,11 @@ function DynamicField(props) { | |||||||
|           <Stack |           <Stack | ||||||
|             direction={{ xs: 'column', sm: 'row' }} |             direction={{ xs: 'column', sm: 'row' }} | ||||||
|             spacing={{ xs: 2 }} |             spacing={{ xs: 2 }} | ||||||
|             sx={{ |             sx={{ display: 'flex', flex: 1 }} | ||||||
|               display: 'flex', |  | ||||||
|               flex: 1, |  | ||||||
|               minWidth: 0, |  | ||||||
|             }} |  | ||||||
|           > |           > | ||||||
|             {fields.map((fieldSchema, fieldSchemaIndex) => ( |             {fields.map((fieldSchema, fieldSchemaIndex) => ( | ||||||
|               <Box |               <Box | ||||||
|                 sx={{ |                 sx={{ display: 'flex', flex: '1 0 0px' }} | ||||||
|                   display: 'flex', |  | ||||||
|                   flex: '1 0 0px', |  | ||||||
|                   minWidth: 0, |  | ||||||
|                 }} |  | ||||||
|                 key={`field-${field.__id}-${fieldSchemaIndex}`} |                 key={`field-${field.__id}-${fieldSchemaIndex}`} | ||||||
|               > |               > | ||||||
|                 <InputCreator |                 <InputCreator | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ import Tooltip from '@mui/material/Tooltip'; | |||||||
| import IconButton from '@mui/material/IconButton'; | import IconButton from '@mui/material/IconButton'; | ||||||
| import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; | import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; | ||||||
| import Snackbar from '@mui/material/Snackbar'; | import Snackbar from '@mui/material/Snackbar'; | ||||||
| import { ReactFlowProvider } from 'reactflow'; |  | ||||||
|  |  | ||||||
| import { EditorProvider } from 'contexts/Editor'; | import { EditorProvider } from 'contexts/Editor'; | ||||||
| import EditableTypography from 'components/EditableTypography'; | import EditableTypography from 'components/EditableTypography'; | ||||||
| @@ -21,9 +20,6 @@ import * as URLS from 'config/urls'; | |||||||
| import { TopBar } from './style'; | import { TopBar } from './style'; | ||||||
| import useFlow from 'hooks/useFlow'; | import useFlow from 'hooks/useFlow'; | ||||||
| import { useQueryClient } from '@tanstack/react-query'; | import { useQueryClient } from '@tanstack/react-query'; | ||||||
| import EditorNew from 'components/EditorNew/EditorNew'; |  | ||||||
|  |  | ||||||
| const useNewFlowEditor = process.env.REACT_APP_USE_NEW_FLOW_EDITOR === 'true'; |  | ||||||
|  |  | ||||||
| export default function EditorLayout() { | export default function EditorLayout() { | ||||||
|   const { flowId } = useParams(); |   const { flowId } = useParams(); | ||||||
| @@ -59,7 +55,6 @@ export default function EditorLayout() { | |||||||
|  |  | ||||||
|   const onFlowStatusUpdate = React.useCallback( |   const onFlowStatusUpdate = React.useCallback( | ||||||
|     async (active) => { |     async (active) => { | ||||||
|       try { |  | ||||||
|       await updateFlowStatus({ |       await updateFlowStatus({ | ||||||
|         variables: { |         variables: { | ||||||
|           input: { |           input: { | ||||||
| @@ -77,7 +72,6 @@ export default function EditorLayout() { | |||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       await queryClient.invalidateQueries({ queryKey: ['flows', flowId] }); |       await queryClient.invalidateQueries({ queryKey: ['flows', flowId] }); | ||||||
|       } catch (err) {} |  | ||||||
|     }, |     }, | ||||||
|     [flowId, queryClient], |     [flowId, queryClient], | ||||||
|   ); |   ); | ||||||
| @@ -137,28 +131,15 @@ export default function EditorLayout() { | |||||||
|           </Button> |           </Button> | ||||||
|         </Box> |         </Box> | ||||||
|       </TopBar> |       </TopBar> | ||||||
|  |  | ||||||
|       {useNewFlowEditor ? ( |  | ||||||
|         <Stack direction="column" height="100%" flexGrow={1}> |  | ||||||
|           <Stack direction="column" flexGrow={1}> |  | ||||||
|             <EditorProvider value={{ readOnly: !!flow?.active }}> |  | ||||||
|               <ReactFlowProvider> |  | ||||||
|                 {!flow && !isFlowLoading && 'not found'} |  | ||||||
|                 {flow && <EditorNew flow={flow} />} |  | ||||||
|               </ReactFlowProvider> |  | ||||||
|             </EditorProvider> |  | ||||||
|           </Stack> |  | ||||||
|         </Stack> |  | ||||||
|       ) : ( |  | ||||||
|       <Stack direction="column" height="100%"> |       <Stack direction="column" height="100%"> | ||||||
|         <Container maxWidth="md"> |         <Container maxWidth="md"> | ||||||
|           <EditorProvider value={{ readOnly: !!flow?.active }}> |           <EditorProvider value={{ readOnly: !!flow?.active }}> | ||||||
|             {!flow && !isFlowLoading && 'not found'} |             {!flow && !isFlowLoading && 'not found'} | ||||||
|  |  | ||||||
|             {flow && <Editor flow={flow} />} |             {flow && <Editor flow={flow} />} | ||||||
|           </EditorProvider> |           </EditorProvider> | ||||||
|         </Container> |         </Container> | ||||||
|       </Stack> |       </Stack> | ||||||
|       )} |  | ||||||
|  |  | ||||||
|       <Snackbar |       <Snackbar | ||||||
|         data-test="flow-cannot-edit-info-snackbar" |         data-test="flow-cannot-edit-info-snackbar" | ||||||
|   | |||||||
| @@ -1,57 +0,0 @@ | |||||||
| import { EdgeLabelRenderer, getStraightPath } from 'reactflow'; |  | ||||||
| import IconButton from '@mui/material/IconButton'; |  | ||||||
| import AddIcon from '@mui/icons-material/Add'; |  | ||||||
|  |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { useContext } from 'react'; |  | ||||||
| import { EdgesContext } from '../EditorNew'; |  | ||||||
|  |  | ||||||
| export default function Edge({ |  | ||||||
|   sourceX, |  | ||||||
|   sourceY, |  | ||||||
|   targetX, |  | ||||||
|   targetY, |  | ||||||
|   source, |  | ||||||
|   data: { laidOut }, |  | ||||||
| }) { |  | ||||||
|   const { stepCreationInProgress, flowActive, onAddStep } = |  | ||||||
|     useContext(EdgesContext); |  | ||||||
|  |  | ||||||
|   const [edgePath, labelX, labelY] = getStraightPath({ |  | ||||||
|     sourceX, |  | ||||||
|     sourceY, |  | ||||||
|     targetX, |  | ||||||
|     targetY, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <> |  | ||||||
|       <EdgeLabelRenderer> |  | ||||||
|         <IconButton |  | ||||||
|           onClick={() => onAddStep(source)} |  | ||||||
|           color="primary" |  | ||||||
|           sx={{ |  | ||||||
|             position: 'absolute', |  | ||||||
|             transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, |  | ||||||
|             pointerEvents: 'all', |  | ||||||
|             visibility: laidOut ? 'visible' : 'hidden', |  | ||||||
|           }} |  | ||||||
|           disabled={stepCreationInProgress || flowActive} |  | ||||||
|         > |  | ||||||
|           <AddIcon /> |  | ||||||
|         </IconButton> |  | ||||||
|       </EdgeLabelRenderer> |  | ||||||
|     </> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Edge.propTypes = { |  | ||||||
|   sourceX: PropTypes.number.isRequired, |  | ||||||
|   sourceY: PropTypes.number.isRequired, |  | ||||||
|   targetX: PropTypes.number.isRequired, |  | ||||||
|   targetY: PropTypes.number.isRequired, |  | ||||||
|   source: PropTypes.string.isRequired, |  | ||||||
|   data: PropTypes.shape({ |  | ||||||
|     laidOut: PropTypes.bool, |  | ||||||
|   }).isRequired, |  | ||||||
| }; |  | ||||||
| @@ -1,277 +0,0 @@ | |||||||
| import { useEffect, useCallback, createContext, useRef } from 'react'; |  | ||||||
| import { useMutation } from '@apollo/client'; |  | ||||||
| import { useQueryClient } from '@tanstack/react-query'; |  | ||||||
| import { FlowPropType } from 'propTypes/propTypes'; |  | ||||||
| import ReactFlow, { useNodesState, useEdgesState } from 'reactflow'; |  | ||||||
| import 'reactflow/dist/style.css'; |  | ||||||
| import { UPDATE_STEP } from 'graphql/mutations/update-step'; |  | ||||||
| import { CREATE_STEP } from 'graphql/mutations/create-step'; |  | ||||||
|  |  | ||||||
| import { useAutoLayout } from './useAutoLayout'; |  | ||||||
| import { useScrollBoundaries } from './useScrollBoundaries'; |  | ||||||
| import FlowStepNode from './FlowStepNode/FlowStepNode'; |  | ||||||
| import Edge from './Edge/Edge'; |  | ||||||
| import InvisibleNode from './InvisibleNode/InvisibleNode'; |  | ||||||
| import { EditorWrapper } from './style'; |  | ||||||
| import { |  | ||||||
|   generateEdgeId, |  | ||||||
|   generateInitialEdges, |  | ||||||
|   generateInitialNodes, |  | ||||||
|   updatedCollapsedNodes, |  | ||||||
| } from './utils'; |  | ||||||
| import { EDGE_TYPES, INVISIBLE_NODE_ID, NODE_TYPES } from './constants'; |  | ||||||
|  |  | ||||||
| export const EdgesContext = createContext(); |  | ||||||
| export const NodesContext = createContext(); |  | ||||||
|  |  | ||||||
| const nodeTypes = { |  | ||||||
|   [NODE_TYPES.FLOW_STEP]: FlowStepNode, |  | ||||||
|   [NODE_TYPES.INVISIBLE]: InvisibleNode, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const edgeTypes = { |  | ||||||
|   [EDGE_TYPES.ADD_NODE_EDGE]: Edge, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const EditorNew = ({ flow }) => { |  | ||||||
|   const [updateStep] = useMutation(UPDATE_STEP); |  | ||||||
|   const queryClient = useQueryClient(); |  | ||||||
|   const [createStep, { loading: stepCreationInProgress }] = |  | ||||||
|     useMutation(CREATE_STEP); |  | ||||||
|  |  | ||||||
|   const [nodes, setNodes, onNodesChange] = useNodesState( |  | ||||||
|     generateInitialNodes(flow), |  | ||||||
|   ); |  | ||||||
|   const [edges, setEdges, onEdgesChange] = useEdgesState( |  | ||||||
|     generateInitialEdges(flow), |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   useAutoLayout(); |  | ||||||
|   useScrollBoundaries(); |  | ||||||
|  |  | ||||||
|   const createdStepIdRef = useRef(null); |  | ||||||
|  |  | ||||||
|   const openNextStep = useCallback( |  | ||||||
|     (currentStepId) => { |  | ||||||
|       setNodes((nodes) => { |  | ||||||
|         const currentStepIndex = nodes.findIndex( |  | ||||||
|           (node) => node.id === currentStepId, |  | ||||||
|         ); |  | ||||||
|         if (currentStepIndex >= 0) { |  | ||||||
|           const nextStep = nodes[currentStepIndex + 1]; |  | ||||||
|           return updatedCollapsedNodes(nodes, nextStep.id); |  | ||||||
|         } |  | ||||||
|         return nodes; |  | ||||||
|       }); |  | ||||||
|     }, |  | ||||||
|     [setNodes], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   const onStepClose = useCallback(() => { |  | ||||||
|     setNodes((nodes) => updatedCollapsedNodes(nodes)); |  | ||||||
|   }, [setNodes]); |  | ||||||
|  |  | ||||||
|   const onStepOpen = useCallback( |  | ||||||
|     (stepId) => { |  | ||||||
|       setNodes((nodes) => updatedCollapsedNodes(nodes, stepId)); |  | ||||||
|     }, |  | ||||||
|     [setNodes], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   const onStepChange = useCallback( |  | ||||||
|     async (step) => { |  | ||||||
|       const mutationInput = { |  | ||||||
|         id: step.id, |  | ||||||
|         key: step.key, |  | ||||||
|         parameters: step.parameters, |  | ||||||
|         connection: { |  | ||||||
|           id: step.connection?.id, |  | ||||||
|         }, |  | ||||||
|         flow: { |  | ||||||
|           id: flow.id, |  | ||||||
|         }, |  | ||||||
|       }; |  | ||||||
|  |  | ||||||
|       if (step.appKey) { |  | ||||||
|         mutationInput.appKey = step.appKey; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       await updateStep({ |  | ||||||
|         variables: { input: mutationInput }, |  | ||||||
|       }); |  | ||||||
|       await queryClient.invalidateQueries({ |  | ||||||
|         queryKey: ['steps', step.id, 'connection'], |  | ||||||
|       }); |  | ||||||
|       await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] }); |  | ||||||
|     }, |  | ||||||
|     [flow.id, updateStep, queryClient], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   const onAddStep = useCallback( |  | ||||||
|     async (previousStepId) => { |  | ||||||
|       const mutationInput = { |  | ||||||
|         previousStep: { |  | ||||||
|           id: previousStepId, |  | ||||||
|         }, |  | ||||||
|         flow: { |  | ||||||
|           id: flow.id, |  | ||||||
|         }, |  | ||||||
|       }; |  | ||||||
|  |  | ||||||
|       const { |  | ||||||
|         data: { createStep: createdStep }, |  | ||||||
|       } = await createStep({ |  | ||||||
|         variables: { input: mutationInput }, |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       const createdStepId = createdStep.id; |  | ||||||
|       await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] }); |  | ||||||
|       createdStepIdRef.current = createdStepId; |  | ||||||
|     }, |  | ||||||
|     [flow.id, createStep, queryClient], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (flow.steps.length + 1 !== nodes.length) { |  | ||||||
|       setNodes((nodes) => { |  | ||||||
|         const newNodes = flow.steps.map((step) => { |  | ||||||
|           const createdStepId = createdStepIdRef.current; |  | ||||||
|           const prevNode = nodes.find(({ id }) => id === step.id); |  | ||||||
|           if (prevNode) { |  | ||||||
|             return { |  | ||||||
|               ...prevNode, |  | ||||||
|               zIndex: createdStepId ? 0 : prevNode.zIndex, |  | ||||||
|               data: { |  | ||||||
|                 ...prevNode.data, |  | ||||||
|                 collapsed: createdStepId ? true : prevNode.data.collapsed, |  | ||||||
|               }, |  | ||||||
|             }; |  | ||||||
|           } else { |  | ||||||
|             return { |  | ||||||
|               id: step.id, |  | ||||||
|               type: NODE_TYPES.FLOW_STEP, |  | ||||||
|               position: { |  | ||||||
|                 x: 0, |  | ||||||
|                 y: 0, |  | ||||||
|               }, |  | ||||||
|               zIndex: 1, |  | ||||||
|               data: { |  | ||||||
|                 collapsed: false, |  | ||||||
|                 laidOut: false, |  | ||||||
|               }, |  | ||||||
|             }; |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         const prevInvisible = nodes.find(({ id }) => id === INVISIBLE_NODE_ID); |  | ||||||
|         return [ |  | ||||||
|           ...newNodes, |  | ||||||
|           { |  | ||||||
|             id: INVISIBLE_NODE_ID, |  | ||||||
|             type: NODE_TYPES.INVISIBLE, |  | ||||||
|             position: { |  | ||||||
|               x: prevInvisible?.position.x || 0, |  | ||||||
|               y: prevInvisible?.position.y || 0, |  | ||||||
|             }, |  | ||||||
|           }, |  | ||||||
|         ]; |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       setEdges((edges) => { |  | ||||||
|         const newEdges = flow.steps |  | ||||||
|           .map((step, i) => { |  | ||||||
|             const sourceId = step.id; |  | ||||||
|             const targetId = flow.steps[i + 1]?.id; |  | ||||||
|             const edge = edges?.find( |  | ||||||
|               (edge) => edge.id === generateEdgeId(sourceId, targetId), |  | ||||||
|             ); |  | ||||||
|             if (targetId) { |  | ||||||
|               return { |  | ||||||
|                 id: generateEdgeId(sourceId, targetId), |  | ||||||
|                 source: sourceId, |  | ||||||
|                 target: targetId, |  | ||||||
|                 type: 'addNodeEdge', |  | ||||||
|                 data: { |  | ||||||
|                   laidOut: edge ? edge?.data.laidOut : false, |  | ||||||
|                 }, |  | ||||||
|               }; |  | ||||||
|             } |  | ||||||
|             return null; |  | ||||||
|           }) |  | ||||||
|           .filter((edge) => !!edge); |  | ||||||
|  |  | ||||||
|         const lastStep = flow.steps[flow.steps.length - 1]; |  | ||||||
|         const lastEdge = edges[edges.length - 1]; |  | ||||||
|  |  | ||||||
|         return lastStep |  | ||||||
|           ? [ |  | ||||||
|               ...newEdges, |  | ||||||
|               { |  | ||||||
|                 id: generateEdgeId(lastStep.id, INVISIBLE_NODE_ID), |  | ||||||
|                 source: lastStep.id, |  | ||||||
|                 target: INVISIBLE_NODE_ID, |  | ||||||
|                 type: 'addNodeEdge', |  | ||||||
|                 data: { |  | ||||||
|                   laidOut: |  | ||||||
|                     lastEdge?.id === |  | ||||||
|                     generateEdgeId(lastStep.id, INVISIBLE_NODE_ID) |  | ||||||
|                       ? lastEdge?.data.laidOut |  | ||||||
|                       : false, |  | ||||||
|                 }, |  | ||||||
|               }, |  | ||||||
|             ] |  | ||||||
|           : newEdges; |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       if (createdStepIdRef.current) { |  | ||||||
|         createdStepIdRef.current = null; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, [flow.steps]); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <NodesContext.Provider |  | ||||||
|       value={{ |  | ||||||
|         openNextStep, |  | ||||||
|         onStepOpen, |  | ||||||
|         onStepClose, |  | ||||||
|         onStepChange, |  | ||||||
|         flowId: flow.id, |  | ||||||
|         steps: flow.steps, |  | ||||||
|       }} |  | ||||||
|     > |  | ||||||
|       <EdgesContext.Provider |  | ||||||
|         value={{ |  | ||||||
|           stepCreationInProgress, |  | ||||||
|           onAddStep, |  | ||||||
|           flowActive: flow.active, |  | ||||||
|         }} |  | ||||||
|       > |  | ||||||
|         <EditorWrapper direction="column"> |  | ||||||
|           <ReactFlow |  | ||||||
|             nodes={nodes} |  | ||||||
|             edges={edges} |  | ||||||
|             onNodesChange={onNodesChange} |  | ||||||
|             onEdgesChange={onEdgesChange} |  | ||||||
|             nodeTypes={nodeTypes} |  | ||||||
|             edgeTypes={edgeTypes} |  | ||||||
|             panOnScroll |  | ||||||
|             panOnScrollMode="vertical" |  | ||||||
|             panOnDrag={false} |  | ||||||
|             zoomOnScroll={false} |  | ||||||
|             zoomOnPinch={false} |  | ||||||
|             zoomOnDoubleClick={false} |  | ||||||
|             panActivationKeyCode={null} |  | ||||||
|             proOptions={{ hideAttribution: true }} |  | ||||||
|           /> |  | ||||||
|         </EditorWrapper> |  | ||||||
|       </EdgesContext.Provider> |  | ||||||
|     </NodesContext.Provider> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| EditorNew.propTypes = { |  | ||||||
|   flow: FlowPropType.isRequired, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default EditorNew; |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| import { Handle, Position } from 'reactflow'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
|  |  | ||||||
| import FlowStep from 'components/FlowStep'; |  | ||||||
|  |  | ||||||
| import { NodeWrapper, NodeInnerWrapper } from './style.js'; |  | ||||||
| import { useContext } from 'react'; |  | ||||||
| import { NodesContext } from '../EditorNew.jsx'; |  | ||||||
|  |  | ||||||
| function FlowStepNode({ data: { collapsed, laidOut }, id }) { |  | ||||||
|   const { openNextStep, onStepOpen, onStepClose, onStepChange, flowId, steps } = |  | ||||||
|     useContext(NodesContext); |  | ||||||
|  |  | ||||||
|   const step = steps.find(({ id: stepId }) => stepId === id); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <NodeWrapper |  | ||||||
|       className="nodrag" |  | ||||||
|       sx={{ |  | ||||||
|         visibility: laidOut ? 'visible' : 'hidden', |  | ||||||
|       }} |  | ||||||
|     > |  | ||||||
|       <NodeInnerWrapper> |  | ||||||
|         <Handle |  | ||||||
|           type="target" |  | ||||||
|           position={Position.Top} |  | ||||||
|           isConnectable={false} |  | ||||||
|           style={{ visibility: 'hidden' }} |  | ||||||
|         /> |  | ||||||
|         {step && ( |  | ||||||
|           <FlowStep |  | ||||||
|             step={step} |  | ||||||
|             collapsed={collapsed} |  | ||||||
|             onOpen={() => onStepOpen(step.id)} |  | ||||||
|             onClose={onStepClose} |  | ||||||
|             onChange={onStepChange} |  | ||||||
|             flowId={flowId} |  | ||||||
|             onContinue={() => openNextStep(step.id)} |  | ||||||
|           /> |  | ||||||
|         )} |  | ||||||
|         <Handle |  | ||||||
|           type="source" |  | ||||||
|           position={Position.Bottom} |  | ||||||
|           isConnectable={false} |  | ||||||
|           style={{ visibility: 'hidden' }} |  | ||||||
|         /> |  | ||||||
|       </NodeInnerWrapper> |  | ||||||
|     </NodeWrapper> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| FlowStepNode.propTypes = { |  | ||||||
|   id: PropTypes.string, |  | ||||||
|   data: PropTypes.shape({ |  | ||||||
|     collapsed: PropTypes.bool.isRequired, |  | ||||||
|     laidOut: PropTypes.bool.isRequired, |  | ||||||
|   }).isRequired, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default FlowStepNode; |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| import { styled } from '@mui/material/styles'; |  | ||||||
| import { Box } from '@mui/material'; |  | ||||||
|  |  | ||||||
| export const NodeWrapper = styled(Box)(({ theme }) => ({ |  | ||||||
|   width: '100vw', |  | ||||||
|   display: 'flex', |  | ||||||
|   justifyContent: 'center', |  | ||||||
|   padding: theme.spacing(0, 2.5), |  | ||||||
| })); |  | ||||||
|  |  | ||||||
| export const NodeInnerWrapper = styled(Box)(({ theme }) => ({ |  | ||||||
|   maxWidth: 900, |  | ||||||
|   flex: 1, |  | ||||||
| })); |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| import { Handle, Position } from 'reactflow'; |  | ||||||
| import { Box } from '@mui/material'; |  | ||||||
|  |  | ||||||
| // This node is used for adding an edge with add node button after the last flow step node |  | ||||||
| function InvisibleNode() { |  | ||||||
|   return ( |  | ||||||
|     <Box |  | ||||||
|       maxWidth={900} |  | ||||||
|       width="100vw" |  | ||||||
|       className="nodrag" |  | ||||||
|       sx={{ visibility: 'hidden' }} |  | ||||||
|     > |  | ||||||
|       <Handle type="target" position={Position.Top} isConnectable={false} /> |  | ||||||
|       Invisible node |  | ||||||
|     </Box> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export default InvisibleNode; |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| export const INVISIBLE_NODE_ID = 'invisible-node'; |  | ||||||
|  |  | ||||||
| export const NODE_TYPES = { |  | ||||||
|   FLOW_STEP: 'flowStep', |  | ||||||
|   INVISIBLE: 'invisible', |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const EDGE_TYPES = { |  | ||||||
|   ADD_NODE_EDGE: 'addNodeEdge', |  | ||||||
| }; |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| import { Stack } from '@mui/material'; |  | ||||||
| import { styled } from '@mui/material/styles'; |  | ||||||
|  |  | ||||||
| export const EditorWrapper = styled(Stack)(({ theme }) => ({ |  | ||||||
|   flexGrow: 1, |  | ||||||
|   '& > div': { |  | ||||||
|     flexGrow: 1, |  | ||||||
|   }, |  | ||||||
|  |  | ||||||
|   '& .react-flow__pane, & .react-flow__node': { |  | ||||||
|     cursor: 'auto !important', |  | ||||||
|   }, |  | ||||||
| })); |  | ||||||
| @@ -1,69 +0,0 @@ | |||||||
| import { useCallback, useEffect } from 'react'; |  | ||||||
| import Dagre from '@dagrejs/dagre'; |  | ||||||
| import { usePrevious } from 'hooks/usePrevious'; |  | ||||||
| import { isEqual } from 'lodash'; |  | ||||||
| import { useNodesInitialized, useNodes, useReactFlow } from 'reactflow'; |  | ||||||
|  |  | ||||||
| const getLaidOutElements = (nodes, edges) => { |  | ||||||
|   const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); |  | ||||||
|   graph.setGraph({ |  | ||||||
|     rankdir: 'TB', |  | ||||||
|     marginy: 60, |  | ||||||
|     ranksep: 64, |  | ||||||
|   }); |  | ||||||
|   edges.forEach((edge) => graph.setEdge(edge.source, edge.target)); |  | ||||||
|   nodes.forEach((node) => graph.setNode(node.id, node)); |  | ||||||
|  |  | ||||||
|   Dagre.layout(graph); |  | ||||||
|  |  | ||||||
|   return { |  | ||||||
|     nodes: nodes.map((node) => { |  | ||||||
|       const { x, y, width, height } = graph.node(node.id); |  | ||||||
|       return { |  | ||||||
|         ...node, |  | ||||||
|         position: { x: x - width / 2, y: y - height / 2 }, |  | ||||||
|       }; |  | ||||||
|     }), |  | ||||||
|     edges, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const useAutoLayout = () => { |  | ||||||
|   const nodes = useNodes(); |  | ||||||
|   const prevNodes = usePrevious(nodes); |  | ||||||
|   const nodesInitialized = useNodesInitialized(); |  | ||||||
|   const { getEdges, setNodes, setEdges } = useReactFlow(); |  | ||||||
|  |  | ||||||
|   const onLayout = useCallback( |  | ||||||
|     (nodes, edges) => { |  | ||||||
|       const laidOutElements = getLaidOutElements(nodes, edges); |  | ||||||
|  |  | ||||||
|       setNodes([ |  | ||||||
|         ...laidOutElements.nodes.map((node) => ({ |  | ||||||
|           ...node, |  | ||||||
|           data: { ...node.data, laidOut: true }, |  | ||||||
|         })), |  | ||||||
|       ]); |  | ||||||
|       setEdges([ |  | ||||||
|         ...laidOutElements.edges.map((edge) => ({ |  | ||||||
|           ...edge, |  | ||||||
|           data: { ...edge.data, laidOut: true }, |  | ||||||
|         })), |  | ||||||
|       ]); |  | ||||||
|     }, |  | ||||||
|     [setEdges, setNodes], |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     const shouldAutoLayout = |  | ||||||
|       nodesInitialized && |  | ||||||
|       !isEqual( |  | ||||||
|         nodes.map(({ width, height }) => ({ width, height })), |  | ||||||
|         prevNodes.map(({ width, height }) => ({ width, height })), |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|     if (shouldAutoLayout) { |  | ||||||
|       onLayout(nodes, getEdges()); |  | ||||||
|     } |  | ||||||
|   }, [nodes]); |  | ||||||
| }; |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| import { useEffect } from 'react'; |  | ||||||
| import { useViewport, useReactFlow } from 'reactflow'; |  | ||||||
|  |  | ||||||
| export const useScrollBoundaries = () => { |  | ||||||
|   const { setViewport } = useReactFlow(); |  | ||||||
|   const { x, y, zoom } = useViewport(); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (y > 0) { |  | ||||||
|       setViewport({ x, y: 0, zoom }); |  | ||||||
|     } |  | ||||||
|   }, [y]); |  | ||||||
| }; |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| import { INVISIBLE_NODE_ID, NODE_TYPES } from './constants'; |  | ||||||
|  |  | ||||||
| export const generateEdgeId = (sourceId, targetId) => `${sourceId}-${targetId}`; |  | ||||||
|  |  | ||||||
| export const updatedCollapsedNodes = (nodes, openStepId) => { |  | ||||||
|   return nodes.map((node) => { |  | ||||||
|     if (node.type !== NODE_TYPES.FLOW_STEP) { |  | ||||||
|       return node; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const collapsed = node.id !== openStepId; |  | ||||||
|     return { |  | ||||||
|       ...node, |  | ||||||
|       zIndex: collapsed ? 0 : 1, |  | ||||||
|       data: { ...node.data, collapsed }, |  | ||||||
|     }; |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const generateInitialNodes = (flow) => { |  | ||||||
|   const newNodes = flow.steps.map((step, index) => { |  | ||||||
|     const collapsed = index !== 0; |  | ||||||
|  |  | ||||||
|     return { |  | ||||||
|       id: step.id, |  | ||||||
|       type: NODE_TYPES.FLOW_STEP, |  | ||||||
|       position: { |  | ||||||
|         x: 0, |  | ||||||
|         y: 0, |  | ||||||
|       }, |  | ||||||
|       zIndex: collapsed ? 0 : 1, |  | ||||||
|       data: { |  | ||||||
|         collapsed, |  | ||||||
|         laidOut: false, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   return [ |  | ||||||
|     ...newNodes, |  | ||||||
|     { |  | ||||||
|       id: INVISIBLE_NODE_ID, |  | ||||||
|       type: NODE_TYPES.INVISIBLE, |  | ||||||
|       position: { |  | ||||||
|         x: 0, |  | ||||||
|         y: 0, |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ]; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const generateInitialEdges = (flow) => { |  | ||||||
|   const newEdges = flow.steps |  | ||||||
|     .map((step, i) => { |  | ||||||
|       const sourceId = step.id; |  | ||||||
|       const targetId = flow.steps[i + 1]?.id; |  | ||||||
|       if (targetId) { |  | ||||||
|         return { |  | ||||||
|           id: generateEdgeId(sourceId, targetId), |  | ||||||
|           source: sourceId, |  | ||||||
|           target: targetId, |  | ||||||
|           type: 'addNodeEdge', |  | ||||||
|           data: { |  | ||||||
|             laidOut: false, |  | ||||||
|           }, |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|       return null; |  | ||||||
|     }) |  | ||||||
|     .filter((edge) => !!edge); |  | ||||||
|  |  | ||||||
|   const lastStep = flow.steps[flow.steps.length - 1]; |  | ||||||
|  |  | ||||||
|   return lastStep |  | ||||||
|     ? [ |  | ||||||
|         ...newEdges, |  | ||||||
|         { |  | ||||||
|           id: generateEdgeId(lastStep.id, INVISIBLE_NODE_ID), |  | ||||||
|           source: lastStep.id, |  | ||||||
|           target: INVISIBLE_NODE_ID, |  | ||||||
|           type: 'addNodeEdge', |  | ||||||
|           data: { |  | ||||||
|             laidOut: false, |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|       ] |  | ||||||
|     : newEdges; |  | ||||||
| }; |  | ||||||
| @@ -28,12 +28,9 @@ function ContextMenu(props) { | |||||||
|       variables: { input: { id: flowId } }, |       variables: { input: { id: flowId } }, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     if (appKey) { |  | ||||||
|     await queryClient.invalidateQueries({ |     await queryClient.invalidateQueries({ | ||||||
|       queryKey: ['apps', appKey, 'flows'], |       queryKey: ['apps', appKey, 'flows'], | ||||||
|     }); |     }); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), { |     enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), { | ||||||
|       variant: 'success', |       variant: 'success', | ||||||
|       SnackbarProps: { |       SnackbarProps: { | ||||||
| @@ -59,12 +56,9 @@ function ContextMenu(props) { | |||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     if (appKey) { |  | ||||||
|     await queryClient.invalidateQueries({ |     await queryClient.invalidateQueries({ | ||||||
|       queryKey: ['apps', appKey, 'flows'], |       queryKey: ['apps', appKey, 'flows'], | ||||||
|     }); |     }); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     enqueueSnackbar(formatMessage('flow.successfullyDeleted'), { |     enqueueSnackbar(formatMessage('flow.successfullyDeleted'), { | ||||||
|       variant: 'success', |       variant: 'success', | ||||||
|     }); |     }); | ||||||
| @@ -116,7 +110,7 @@ ContextMenu.propTypes = { | |||||||
|   ]).isRequired, |   ]).isRequired, | ||||||
|   onDeleteFlow: PropTypes.func, |   onDeleteFlow: PropTypes.func, | ||||||
|   onDuplicateFlow: PropTypes.func, |   onDuplicateFlow: PropTypes.func, | ||||||
|   appKey: PropTypes.string, |   appKey: PropTypes.string.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default ContextMenu; | export default ContextMenu; | ||||||
|   | |||||||
| @@ -38,24 +38,20 @@ function FlowRow(props) { | |||||||
|   const contextButtonRef = React.useRef(null); |   const contextButtonRef = React.useRef(null); | ||||||
|   const [anchorEl, setAnchorEl] = React.useState(null); |   const [anchorEl, setAnchorEl] = React.useState(null); | ||||||
|   const { flow, onDuplicateFlow, onDeleteFlow, appKey } = props; |   const { flow, onDuplicateFlow, onDeleteFlow, appKey } = props; | ||||||
|  |  | ||||||
|   const handleClose = () => { |   const handleClose = () => { | ||||||
|     setAnchorEl(null); |     setAnchorEl(null); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const onContextMenuClick = (event) => { |   const onContextMenuClick = (event) => { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     event.stopPropagation(); |     event.stopPropagation(); | ||||||
|     event.nativeEvent.stopImmediatePropagation(); |     event.nativeEvent.stopImmediatePropagation(); | ||||||
|     setAnchorEl(contextButtonRef.current); |     setAnchorEl(contextButtonRef.current); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10)); |   const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10)); | ||||||
|   const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10)); |   const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10)); | ||||||
|   const isUpdated = updatedAt > createdAt; |   const isUpdated = updatedAt > createdAt; | ||||||
|   const relativeCreatedAt = createdAt.toRelative(); |   const relativeCreatedAt = createdAt.toRelative(); | ||||||
|   const relativeUpdatedAt = updatedAt.toRelative(); |   const relativeUpdatedAt = updatedAt.toRelative(); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <Card sx={{ mb: 1 }} data-test="flow-row"> |       <Card sx={{ mb: 1 }} data-test="flow-row"> | ||||||
| @@ -131,7 +127,7 @@ FlowRow.propTypes = { | |||||||
|   flow: FlowPropType.isRequired, |   flow: FlowPropType.isRequired, | ||||||
|   onDeleteFlow: PropTypes.func, |   onDeleteFlow: PropTypes.func, | ||||||
|   onDuplicateFlow: PropTypes.func, |   onDuplicateFlow: PropTypes.func, | ||||||
|   appKey: PropTypes.string, |   appKey: PropTypes.string.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default FlowRow; | export default FlowRow; | ||||||
|   | |||||||
| @@ -11,6 +11,9 @@ import IconButton from '@mui/material/IconButton'; | |||||||
| import ErrorIcon from '@mui/icons-material/Error'; | import ErrorIcon from '@mui/icons-material/Error'; | ||||||
| import CircularProgress from '@mui/material/CircularProgress'; | import CircularProgress from '@mui/material/CircularProgress'; | ||||||
| import CheckCircleIcon from '@mui/icons-material/CheckCircle'; | import CheckCircleIcon from '@mui/icons-material/CheckCircle'; | ||||||
|  | import { yupResolver } from '@hookform/resolvers/yup'; | ||||||
|  | import * as yup from 'yup'; | ||||||
|  |  | ||||||
| import { EditorContext } from 'contexts/Editor'; | import { EditorContext } from 'contexts/Editor'; | ||||||
| import { StepExecutionsProvider } from 'contexts/StepExecutions'; | import { StepExecutionsProvider } from 'contexts/StepExecutions'; | ||||||
| import TestSubstep from 'components/TestSubstep'; | import TestSubstep from 'components/TestSubstep'; | ||||||
| @@ -30,18 +33,77 @@ import { | |||||||
|   Header, |   Header, | ||||||
|   Wrapper, |   Wrapper, | ||||||
| } from './style'; | } from './style'; | ||||||
|  | import isEmpty from 'helpers/isEmpty'; | ||||||
| import { StepPropType } from 'propTypes/propTypes'; | import { StepPropType } from 'propTypes/propTypes'; | ||||||
| import useTriggers from 'hooks/useTriggers'; | import useTriggers from 'hooks/useTriggers'; | ||||||
| import useActions from 'hooks/useActions'; | import useActions from 'hooks/useActions'; | ||||||
| import useTriggerSubsteps from 'hooks/useTriggerSubsteps'; | import useTriggerSubsteps from 'hooks/useTriggerSubsteps'; | ||||||
| import useActionSubsteps from 'hooks/useActionSubsteps'; | import useActionSubsteps from 'hooks/useActionSubsteps'; | ||||||
| import useStepWithTestExecutions from 'hooks/useStepWithTestExecutions'; | import useStepWithTestExecutions from 'hooks/useStepWithTestExecutions'; | ||||||
| import { validationSchemaResolver } from './validation'; |  | ||||||
| import { isEqual } from 'lodash'; |  | ||||||
|  |  | ||||||
| const validIcon = <CheckCircleIcon color="success" />; | const validIcon = <CheckCircleIcon color="success" />; | ||||||
| const errorIcon = <ErrorIcon color="error" />; | const errorIcon = <ErrorIcon color="error" />; | ||||||
|  |  | ||||||
|  | function generateValidationSchema(substeps) { | ||||||
|  |   const fieldValidations = substeps?.reduce( | ||||||
|  |     (allValidations, { arguments: args }) => { | ||||||
|  |       if (!args || !Array.isArray(args)) return allValidations; | ||||||
|  |       const substepArgumentValidations = {}; | ||||||
|  |       for (const arg of args) { | ||||||
|  |         const { key, required } = arg; | ||||||
|  |         // base validation for the field if not exists | ||||||
|  |         if (!substepArgumentValidations[key]) { | ||||||
|  |           substepArgumentValidations[key] = yup.mixed(); | ||||||
|  |         } | ||||||
|  |         if ( | ||||||
|  |           typeof substepArgumentValidations[key] === 'object' && | ||||||
|  |           (arg.type === 'string' || arg.type === 'dropdown') | ||||||
|  |         ) { | ||||||
|  |           // if the field is required, add the required validation | ||||||
|  |           if (required) { | ||||||
|  |             substepArgumentValidations[key] = substepArgumentValidations[key] | ||||||
|  |               .required(`${key} is required.`) | ||||||
|  |               .test( | ||||||
|  |                 'empty-check', | ||||||
|  |                 `${key} must be not empty`, | ||||||
|  |                 (value) => !isEmpty(value), | ||||||
|  |               ); | ||||||
|  |           } | ||||||
|  |           // if the field depends on another field, add the dependsOn required validation | ||||||
|  |           if (Array.isArray(arg.dependsOn) && arg.dependsOn.length > 0) { | ||||||
|  |             for (const dependsOnKey of arg.dependsOn) { | ||||||
|  |               const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`; | ||||||
|  |               // TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported. | ||||||
|  |               // So the fields under the `parameters` key are subject to their siblings only and thus, `parameters.` is removed. | ||||||
|  |               substepArgumentValidations[key] = substepArgumentValidations[ | ||||||
|  |                 key | ||||||
|  |               ].when(`${dependsOnKey.replace('parameters.', '')}`, { | ||||||
|  |                 is: (value) => Boolean(value) === false, | ||||||
|  |                 then: (schema) => | ||||||
|  |                   schema | ||||||
|  |                     .notOneOf([''], missingDependencyValueMessage) | ||||||
|  |                     .required(missingDependencyValueMessage), | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return { | ||||||
|  |         ...allValidations, | ||||||
|  |         ...substepArgumentValidations, | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |     {}, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const validationSchema = yup.object({ | ||||||
|  |     parameters: yup.object(fieldValidations), | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   return yupResolver(validationSchema); | ||||||
|  | } | ||||||
|  |  | ||||||
| function FlowStep(props) { | function FlowStep(props) { | ||||||
|   const { collapsed, onChange, onContinue, flowId } = props; |   const { collapsed, onChange, onContinue, flowId } = props; | ||||||
|   const editorContext = React.useContext(EditorContext); |   const editorContext = React.useContext(EditorContext); | ||||||
| @@ -52,10 +114,6 @@ function FlowStep(props) { | |||||||
|   const isAction = step.type === 'action'; |   const isAction = step.type === 'action'; | ||||||
|   const formatMessage = useFormatMessage(); |   const formatMessage = useFormatMessage(); | ||||||
|   const [currentSubstep, setCurrentSubstep] = React.useState(0); |   const [currentSubstep, setCurrentSubstep] = React.useState(0); | ||||||
|   const [formResolverContext, setFormResolverContext] = React.useState({ |  | ||||||
|     substeps: [], |  | ||||||
|     additionalFields: {}, |  | ||||||
|   }); |  | ||||||
|   const useAppsOptions = {}; |   const useAppsOptions = {}; | ||||||
|  |  | ||||||
|   if (isTrigger) { |   if (isTrigger) { | ||||||
| @@ -110,12 +168,6 @@ function FlowStep(props) { | |||||||
|       ? triggerSubstepsData |       ? triggerSubstepsData | ||||||
|       : actionSubstepsData || []; |       : actionSubstepsData || []; | ||||||
|  |  | ||||||
|   React.useEffect(() => { |  | ||||||
|     if (!isEqual(substeps, formResolverContext.substeps)) { |  | ||||||
|       setFormResolverContext({ substeps, additionalFields: {} }); |  | ||||||
|     } |  | ||||||
|   }, [substeps]); |  | ||||||
|  |  | ||||||
|   const handleChange = React.useCallback(({ step }) => { |   const handleChange = React.useCallback(({ step }) => { | ||||||
|     onChange(step); |     onChange(step); | ||||||
|   }, []); |   }, []); | ||||||
| @@ -128,6 +180,11 @@ function FlowStep(props) { | |||||||
|     handleChange({ step: val }); |     handleChange({ step: val }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const stepValidationSchema = React.useMemo( | ||||||
|  |     () => generateValidationSchema(substeps), | ||||||
|  |     [substeps], | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   if (!apps?.data) { |   if (!apps?.data) { | ||||||
|     return ( |     return ( | ||||||
|       <CircularProgress |       <CircularProgress | ||||||
| @@ -156,15 +213,6 @@ function FlowStep(props) { | |||||||
|       value !== substepIndex ? substepIndex : null, |       value !== substepIndex ? substepIndex : null, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|   const addAdditionalFieldsValidation = (additionalFields) => { |  | ||||||
|     if (additionalFields) { |  | ||||||
|       setFormResolverContext((prev) => ({ |  | ||||||
|         ...prev, |  | ||||||
|         additionalFields: { ...prev.additionalFields, ...additionalFields }, |  | ||||||
|       })); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const validationStatusIcon = |   const validationStatusIcon = | ||||||
|     step.status === 'completed' ? validIcon : errorIcon; |     step.status === 'completed' ? validIcon : errorIcon; | ||||||
|  |  | ||||||
| @@ -218,8 +266,7 @@ function FlowStep(props) { | |||||||
|               <Form |               <Form | ||||||
|                 defaultValues={step} |                 defaultValues={step} | ||||||
|                 onSubmit={handleSubmit} |                 onSubmit={handleSubmit} | ||||||
|                 resolver={validationSchemaResolver} |                 resolver={stepValidationSchema} | ||||||
|                 context={formResolverContext} |  | ||||||
|               > |               > | ||||||
|                 <ChooseAppAndEventSubstep |                 <ChooseAppAndEventSubstep | ||||||
|                   expanded={currentSubstep === 0} |                   expanded={currentSubstep === 0} | ||||||
| @@ -283,9 +330,6 @@ function FlowStep(props) { | |||||||
|                             onSubmit={expandNextStep} |                             onSubmit={expandNextStep} | ||||||
|                             onChange={handleChange} |                             onChange={handleChange} | ||||||
|                             step={step} |                             step={step} | ||||||
|                             addAdditionalFieldsValidation={ |  | ||||||
|                               addAdditionalFieldsValidation |  | ||||||
|                             } |  | ||||||
|                           /> |                           /> | ||||||
|                         )} |                         )} | ||||||
|                     </React.Fragment> |                     </React.Fragment> | ||||||
| @@ -316,6 +360,7 @@ function FlowStep(props) { | |||||||
| FlowStep.propTypes = { | FlowStep.propTypes = { | ||||||
|   collapsed: PropTypes.bool, |   collapsed: PropTypes.bool, | ||||||
|   step: StepPropType.isRequired, |   step: StepPropType.isRequired, | ||||||
|  |   index: PropTypes.number, | ||||||
|   onOpen: PropTypes.func, |   onOpen: PropTypes.func, | ||||||
|   onClose: PropTypes.func, |   onClose: PropTypes.func, | ||||||
|   onChange: PropTypes.func.isRequired, |   onChange: PropTypes.func.isRequired, | ||||||
|   | |||||||
| @@ -1,120 +0,0 @@ | |||||||
| import * as yup from 'yup'; |  | ||||||
| import { yupResolver } from '@hookform/resolvers/yup'; |  | ||||||
| import isEmpty from 'helpers/isEmpty'; |  | ||||||
|  |  | ||||||
| function addRequiredValidation({ required, schema, key }) { |  | ||||||
|   // if the field is required, add the required validation |  | ||||||
|   if (required) { |  | ||||||
|     return schema |  | ||||||
|       .required(`${key} is required.`) |  | ||||||
|       .test( |  | ||||||
|         'empty-check', |  | ||||||
|         `${key} must be not empty`, |  | ||||||
|         (value) => !isEmpty(value), |  | ||||||
|       ); |  | ||||||
|   } |  | ||||||
|   return schema; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function addDependsOnValidation({ schema, dependsOn, key, args }) { |  | ||||||
|   // if the field depends on another field, add the dependsOn required validation |  | ||||||
|   if (Array.isArray(dependsOn) && dependsOn.length > 0) { |  | ||||||
|     for (const dependsOnKey of dependsOn) { |  | ||||||
|       const dependsOnKeyShort = dependsOnKey.replace('parameters.', ''); |  | ||||||
|       const dependsOnField = args.find(({ key }) => key === dependsOnKeyShort); |  | ||||||
|  |  | ||||||
|       if (dependsOnField?.required) { |  | ||||||
|         const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`; |  | ||||||
|  |  | ||||||
|         // TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported. |  | ||||||
|         // So the fields under the `parameters` key are subject to their siblings only and thus, `parameters.` is removed. |  | ||||||
|         return schema.when(dependsOnKeyShort, { |  | ||||||
|           is: (dependsOnValue) => Boolean(dependsOnValue) === false, |  | ||||||
|           then: (schema) => |  | ||||||
|             schema |  | ||||||
|               .notOneOf([''], missingDependencyValueMessage) |  | ||||||
|               .required(missingDependencyValueMessage), |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return schema; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function validationSchemaResolver(data, context, options) { |  | ||||||
|   const { substeps = [], additionalFields = {} } = context; |  | ||||||
|  |  | ||||||
|   const fieldValidations = [ |  | ||||||
|     ...substeps, |  | ||||||
|     { |  | ||||||
|       arguments: Object.values(additionalFields) |  | ||||||
|         .filter((field) => !!field) |  | ||||||
|         .flat(), |  | ||||||
|     }, |  | ||||||
|   ].reduce((allValidations, { arguments: args }) => { |  | ||||||
|     if (!args || !Array.isArray(args)) return allValidations; |  | ||||||
|  |  | ||||||
|     const substepArgumentValidations = {}; |  | ||||||
|  |  | ||||||
|     for (const arg of args) { |  | ||||||
|       const { key, required } = arg; |  | ||||||
|  |  | ||||||
|       // base validation for the field if not exists |  | ||||||
|       if (!substepArgumentValidations[key]) { |  | ||||||
|         substepArgumentValidations[key] = yup.mixed(); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (arg.type === 'dynamic') { |  | ||||||
|         const fieldsSchema = {}; |  | ||||||
|  |  | ||||||
|         for (const field of arg.fields) { |  | ||||||
|           fieldsSchema[field.key] = yup.mixed(); |  | ||||||
|  |  | ||||||
|           fieldsSchema[field.key] = addRequiredValidation({ |  | ||||||
|             required: field.required, |  | ||||||
|             schema: fieldsSchema[field.key], |  | ||||||
|             key: field.key, |  | ||||||
|           }); |  | ||||||
|  |  | ||||||
|           fieldsSchema[field.key] = addDependsOnValidation({ |  | ||||||
|             schema: fieldsSchema[field.key], |  | ||||||
|             dependsOn: field.dependsOn, |  | ||||||
|             key: field.key, |  | ||||||
|             args, |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         substepArgumentValidations[key] = yup |  | ||||||
|           .array() |  | ||||||
|           .of(yup.object(fieldsSchema)); |  | ||||||
|       } else if ( |  | ||||||
|         typeof substepArgumentValidations[key] === 'object' && |  | ||||||
|         (arg.type === 'string' || arg.type === 'dropdown') |  | ||||||
|       ) { |  | ||||||
|         substepArgumentValidations[key] = addRequiredValidation({ |  | ||||||
|           required, |  | ||||||
|           schema: substepArgumentValidations[key], |  | ||||||
|           key, |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         substepArgumentValidations[key] = addDependsOnValidation({ |  | ||||||
|           schema: substepArgumentValidations[key], |  | ||||||
|           dependsOn: arg.dependsOn, |  | ||||||
|           key, |  | ||||||
|           args, |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return { |  | ||||||
|       ...allValidations, |  | ||||||
|       ...substepArgumentValidations, |  | ||||||
|     }; |  | ||||||
|   }, {}); |  | ||||||
|  |  | ||||||
|   const validationSchema = yup.object({ |  | ||||||
|     parameters: yup.object(fieldValidations), |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   return yupResolver(validationSchema)(data, context, options); |  | ||||||
| } |  | ||||||
| @@ -43,10 +43,7 @@ function FlowStepContextMenu(props) { | |||||||
| FlowStepContextMenu.propTypes = { | FlowStepContextMenu.propTypes = { | ||||||
|   stepId: PropTypes.string.isRequired, |   stepId: PropTypes.string.isRequired, | ||||||
|   onClose: PropTypes.func.isRequired, |   onClose: PropTypes.func.isRequired, | ||||||
|   anchorEl: PropTypes.oneOfType([ |   anchorEl: PropTypes.element.isRequired, | ||||||
|     PropTypes.func, |  | ||||||
|     PropTypes.shape({ current: PropTypes.instanceOf(Element) }), |  | ||||||
|   ]).isRequired, |  | ||||||
|   deletable: PropTypes.bool.isRequired, |   deletable: PropTypes.bool.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,9 +19,7 @@ function FlowSubstep(props) { | |||||||
|     onCollapse, |     onCollapse, | ||||||
|     onSubmit, |     onSubmit, | ||||||
|     step, |     step, | ||||||
|     addAdditionalFieldsValidation, |  | ||||||
|   } = props; |   } = props; | ||||||
|  |  | ||||||
|   const { name, arguments: args } = substep; |   const { name, arguments: args } = substep; | ||||||
|   const editorContext = React.useContext(EditorContext); |   const editorContext = React.useContext(EditorContext); | ||||||
|   const formContext = useFormContext(); |   const formContext = useFormContext(); | ||||||
| @@ -56,7 +54,6 @@ function FlowSubstep(props) { | |||||||
|                   stepId={step.id} |                   stepId={step.id} | ||||||
|                   disabled={editorContext.readOnly} |                   disabled={editorContext.readOnly} | ||||||
|                   showOptionValue={true} |                   showOptionValue={true} | ||||||
|                   addAdditionalFieldsValidation={addAdditionalFieldsValidation} |  | ||||||
|                 /> |                 /> | ||||||
|               ))} |               ))} | ||||||
|             </Stack> |             </Stack> | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import { FormProvider, useForm, useWatch } from 'react-hook-form'; | import { FormProvider, useForm, useWatch } from 'react-hook-form'; | ||||||
|  |  | ||||||
| const noop = () => null; | const noop = () => null; | ||||||
|  |  | ||||||
| export default function Form(props) { | export default function Form(props) { | ||||||
|   const { |   const { | ||||||
|     children, |     children, | ||||||
| @@ -11,31 +9,24 @@ export default function Form(props) { | |||||||
|     resolver, |     resolver, | ||||||
|     render, |     render, | ||||||
|     mode = 'all', |     mode = 'all', | ||||||
|     context, |  | ||||||
|     ...formProps |     ...formProps | ||||||
|   } = props; |   } = props; | ||||||
|  |  | ||||||
|   const methods = useForm({ |   const methods = useForm({ | ||||||
|     defaultValues, |     defaultValues, | ||||||
|     reValidateMode: 'onBlur', |     reValidateMode: 'onBlur', | ||||||
|     resolver, |     resolver, | ||||||
|     mode, |     mode, | ||||||
|     context, |  | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const form = useWatch({ control: methods.control }); |   const form = useWatch({ control: methods.control }); | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * For fields having `dependsOn` fields, we need to re-validate the form. |    * For fields having `dependsOn` fields, we need to re-validate the form. | ||||||
|    */ |    */ | ||||||
|   React.useEffect(() => { |   React.useEffect(() => { | ||||||
|     methods.trigger(); |     methods.trigger(); | ||||||
|   }, [methods.trigger, form]); |   }, [methods.trigger, form]); | ||||||
|  |  | ||||||
|   React.useEffect(() => { |   React.useEffect(() => { | ||||||
|     methods.reset(defaultValues); |     methods.reset(defaultValues); | ||||||
|   }, [defaultValues]); |   }, [defaultValues]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <FormProvider {...methods}> |     <FormProvider {...methods}> | ||||||
|       <form onSubmit={methods.handleSubmit(onSubmit)} {...formProps}> |       <form onSubmit={methods.handleSubmit(onSubmit)} {...formProps}> | ||||||
|   | |||||||
| @@ -23,9 +23,7 @@ export default function InputCreator(props) { | |||||||
|     disabled, |     disabled, | ||||||
|     showOptionValue, |     showOptionValue, | ||||||
|     shouldUnregister, |     shouldUnregister, | ||||||
|     addAdditionalFieldsValidation, |  | ||||||
|   } = props; |   } = props; | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
|     key: name, |     key: name, | ||||||
|     label, |     label, | ||||||
| @@ -35,7 +33,6 @@ export default function InputCreator(props) { | |||||||
|     description, |     description, | ||||||
|     type, |     type, | ||||||
|   } = schema; |   } = schema; | ||||||
|  |  | ||||||
|   const { data, loading } = useDynamicData(stepId, schema); |   const { data, loading } = useDynamicData(stepId, schema); | ||||||
|   const { data: additionalFieldsData, isLoading: isDynamicFieldsLoading } = |   const { data: additionalFieldsData, isLoading: isDynamicFieldsLoading } = | ||||||
|     useDynamicFields(stepId, schema); |     useDynamicFields(stepId, schema); | ||||||
| @@ -43,10 +40,6 @@ export default function InputCreator(props) { | |||||||
|  |  | ||||||
|   const computedName = namePrefix ? `${namePrefix}.${name}` : name; |   const computedName = namePrefix ? `${namePrefix}.${name}` : name; | ||||||
|  |  | ||||||
|   React.useEffect(() => { |  | ||||||
|     addAdditionalFieldsValidation?.({ [name]: additionalFields }); |  | ||||||
|   }, [additionalFields]); |  | ||||||
|  |  | ||||||
|   if (type === 'dynamic') { |   if (type === 'dynamic') { | ||||||
|     return ( |     return ( | ||||||
|       <DynamicField |       <DynamicField | ||||||
| @@ -87,7 +80,6 @@ export default function InputCreator(props) { | |||||||
|             disabled={disabled} |             disabled={disabled} | ||||||
|             showOptionValue={showOptionValue} |             showOptionValue={showOptionValue} | ||||||
|             shouldUnregister={shouldUnregister} |             shouldUnregister={shouldUnregister} | ||||||
|             componentsProps={{ popper: { className: 'nowheel' } }} |  | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,10 +5,8 @@ import AddCircleIcon from '@mui/icons-material/AddCircle'; | |||||||
| import CardActionArea from '@mui/material/CardActionArea'; | import CardActionArea from '@mui/material/CardActionArea'; | ||||||
| import Typography from '@mui/material/Typography'; | import Typography from '@mui/material/Typography'; | ||||||
| import { CardContent } from './style'; | import { CardContent } from './style'; | ||||||
|  |  | ||||||
| export default function NoResultFound(props) { | export default function NoResultFound(props) { | ||||||
|   const { text, to } = props; |   const { text, to } = props; | ||||||
|  |  | ||||||
|   const ActionAreaLink = React.useMemo( |   const ActionAreaLink = React.useMemo( | ||||||
|     () => |     () => | ||||||
|       React.forwardRef(function InlineLink(linkProps, ref) { |       React.forwardRef(function InlineLink(linkProps, ref) { | ||||||
| @@ -17,12 +15,12 @@ export default function NoResultFound(props) { | |||||||
|       }), |       }), | ||||||
|     [to], |     [to], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Card elevation={0}> |     <Card elevation={0}> | ||||||
|       <CardActionArea component={ActionAreaLink} {...props}> |       <CardActionArea component={ActionAreaLink} {...props}> | ||||||
|         <CardContent> |         <CardContent> | ||||||
|           {!!to && <AddCircleIcon color="primary" />} |           {!!to && <AddCircleIcon color="primary" />} | ||||||
|  |  | ||||||
|           <Typography variant="body1">{text}</Typography> |           <Typography variant="body1">{text}</Typography> | ||||||
|         </CardContent> |         </CardContent> | ||||||
|       </CardActionArea> |       </CardActionArea> | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ import { StepExecutionsContext } from 'contexts/StepExecutions'; | |||||||
| import Popper from './Popper'; | import Popper from './Popper'; | ||||||
| import { processStepWithExecutions } from './data'; | import { processStepWithExecutions } from './data'; | ||||||
| import { ChildrenWrapper, FakeInput, InputLabelWrapper } from './style'; | import { ChildrenWrapper, FakeInput, InputLabelWrapper } from './style'; | ||||||
|  |  | ||||||
| const PowerInput = (props) => { | const PowerInput = (props) => { | ||||||
|   const { control } = useFormContext(); |   const { control } = useFormContext(); | ||||||
|   const { |   const { | ||||||
| @@ -32,41 +31,33 @@ const PowerInput = (props) => { | |||||||
|   } = props; |   } = props; | ||||||
|   const priorStepsWithExecutions = React.useContext(StepExecutionsContext); |   const priorStepsWithExecutions = React.useContext(StepExecutionsContext); | ||||||
|   const editorRef = React.useRef(null); |   const editorRef = React.useRef(null); | ||||||
|  |  | ||||||
|   const renderElement = React.useCallback( |   const renderElement = React.useCallback( | ||||||
|     (props) => <Element {...props} />, |     (props) => <Element {...props} />, | ||||||
|     [], |     [], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const [editor] = React.useState(() => customizeEditor(createEditor())); |   const [editor] = React.useState(() => customizeEditor(createEditor())); | ||||||
|  |  | ||||||
|   const [showVariableSuggestions, setShowVariableSuggestions] = |   const [showVariableSuggestions, setShowVariableSuggestions] = | ||||||
|     React.useState(false); |     React.useState(false); | ||||||
|  |  | ||||||
|   const disappearSuggestionsOnShift = (event) => { |   const disappearSuggestionsOnShift = (event) => { | ||||||
|     if (event.code === 'Tab') { |     if (event.code === 'Tab') { | ||||||
|       setShowVariableSuggestions(false); |       setShowVariableSuggestions(false); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const stepsWithVariables = React.useMemo(() => { |   const stepsWithVariables = React.useMemo(() => { | ||||||
|     return processStepWithExecutions(priorStepsWithExecutions); |     return processStepWithExecutions(priorStepsWithExecutions); | ||||||
|   }, [priorStepsWithExecutions]); |   }, [priorStepsWithExecutions]); | ||||||
|  |  | ||||||
|   const handleBlur = React.useCallback( |   const handleBlur = React.useCallback( | ||||||
|     (value) => { |     (value) => { | ||||||
|       onBlur?.(value); |       onBlur?.(value); | ||||||
|     }, |     }, | ||||||
|     [onBlur], |     [onBlur], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const handleVariableSuggestionClick = React.useCallback( |   const handleVariableSuggestionClick = React.useCallback( | ||||||
|     (variable) => { |     (variable) => { | ||||||
|       insertVariable(editor, variable, stepsWithVariables); |       insertVariable(editor, variable, stepsWithVariables); | ||||||
|     }, |     }, | ||||||
|     [stepsWithVariables], |     [stepsWithVariables], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Controller |     <Controller | ||||||
|       rules={{ required }} |       rules={{ required }} | ||||||
| @@ -136,7 +127,6 @@ const PowerInput = (props) => { | |||||||
|                 anchorEl={editorRef.current} |                 anchorEl={editorRef.current} | ||||||
|                 data={stepsWithVariables} |                 data={stepsWithVariables} | ||||||
|                 onSuggestionClick={handleVariableSuggestionClick} |                 onSuggestionClick={handleVariableSuggestionClick} | ||||||
|                 className="nowheel" |  | ||||||
|               /> |               /> | ||||||
|  |  | ||||||
|               <FormHelperText variant="outlined">{description}</FormHelperText> |               <FormHelperText variant="outlined">{description}</FormHelperText> | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import { useQuery } from '@tanstack/react-query'; | |||||||
| import api from 'helpers/api'; | import api from 'helpers/api'; | ||||||
|  |  | ||||||
| const variableRegExp = /({.*?})/; | const variableRegExp = /({.*?})/; | ||||||
|  |  | ||||||
| // TODO: extract this function to a separate file | // TODO: extract this function to a separate file | ||||||
| function computeArguments(args, getValues) { | function computeArguments(args, getValues) { | ||||||
|   const initialValue = {}; |   const initialValue = {}; | ||||||
|   | |||||||
| @@ -1,9 +0,0 @@ | |||||||
| import { useEffect, useRef } from "react"; |  | ||||||
|  |  | ||||||
| export const usePrevious = (value) => { |  | ||||||
|   const ref = useRef(); |  | ||||||
|   useEffect(() => { |  | ||||||
|     ref.current = value; |  | ||||||
|   }); |  | ||||||
|   return ref.current; |  | ||||||
| }; |  | ||||||
| @@ -1,6 +1,4 @@ | |||||||
| import { createRoot } from 'react-dom/client'; | import { createRoot } from 'react-dom/client'; | ||||||
| import { Settings } from 'luxon'; |  | ||||||
|  |  | ||||||
| import ThemeProvider from 'components/ThemeProvider'; | import ThemeProvider from 'components/ThemeProvider'; | ||||||
| import IntlProvider from 'components/IntlProvider'; | import IntlProvider from 'components/IntlProvider'; | ||||||
| import ApolloProvider from 'components/ApolloProvider'; | import ApolloProvider from 'components/ApolloProvider'; | ||||||
| @@ -12,9 +10,6 @@ import Router from 'components/Router'; | |||||||
| import routes from 'routes'; | import routes from 'routes'; | ||||||
| import reportWebVitals from './reportWebVitals'; | import reportWebVitals from './reportWebVitals'; | ||||||
|  |  | ||||||
| // Sets the default locale to English for all luxon DateTime instances created afterwards. |  | ||||||
| Settings.defaultLocale = 'en'; |  | ||||||
|  |  | ||||||
| const container = document.getElementById('root'); | const container = document.getElementById('root'); | ||||||
| const root = createRoot(container); | const root = createRoot(container); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ import AppIcon from 'components/AppIcon'; | |||||||
| import Container from 'components/Container'; | import Container from 'components/Container'; | ||||||
| import PageTitle from 'components/PageTitle'; | import PageTitle from 'components/PageTitle'; | ||||||
| import useApp from 'hooks/useApp'; | import useApp from 'hooks/useApp'; | ||||||
| import Can from 'components/Can'; |  | ||||||
|  |  | ||||||
| const ReconnectConnection = (props) => { | const ReconnectConnection = (props) => { | ||||||
|   const { application, onClose } = props; |   const { application, onClose } = props; | ||||||
| @@ -93,7 +92,7 @@ export default function Application() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return options; |     return options; | ||||||
|   }, [appKey, appConfig?.data, currentUserAbility, formatMessage]); |   }, [appKey, appConfig?.data, currentUserAbility]); | ||||||
|  |  | ||||||
|   if (loading) return null; |   if (loading) return null; | ||||||
|  |  | ||||||
| @@ -119,8 +118,6 @@ export default function Application() { | |||||||
|                 <Route |                 <Route | ||||||
|                   path={`${URLS.FLOWS}/*`} |                   path={`${URLS.FLOWS}/*`} | ||||||
|                   element={ |                   element={ | ||||||
|                     <Can I="create" a="Flow" passThrough> |  | ||||||
|                       {(allowed) => ( |  | ||||||
|                     <ConditionalIconButton |                     <ConditionalIconButton | ||||||
|                       type="submit" |                       type="submit" | ||||||
|                       variant="contained" |                       variant="contained" | ||||||
| @@ -133,23 +130,18 @@ export default function Application() { | |||||||
|                       )} |                       )} | ||||||
|                       fullWidth |                       fullWidth | ||||||
|                       icon={<AddIcon />} |                       icon={<AddIcon />} | ||||||
|                           disabled={!allowed} |                       disabled={!currentUserAbility.can('create', 'Flow')} | ||||||
|                     > |                     > | ||||||
|                       {formatMessage('app.createFlow')} |                       {formatMessage('app.createFlow')} | ||||||
|                     </ConditionalIconButton> |                     </ConditionalIconButton> | ||||||
|                       )} |  | ||||||
|                     </Can> |  | ||||||
|                   } |                   } | ||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|                 <Route |                 <Route | ||||||
|                   path={`${URLS.CONNECTIONS}/*`} |                   path={`${URLS.CONNECTIONS}/*`} | ||||||
|                   element={ |                   element={ | ||||||
|                     <Can I="create" a="Connection" passThrough> |  | ||||||
|                       {(allowed) => ( |  | ||||||
|                     <SplitButton |                     <SplitButton | ||||||
|                       disabled={ |                       disabled={ | ||||||
|                             !allowed || |  | ||||||
|                         (appConfig?.data && |                         (appConfig?.data && | ||||||
|                           !appConfig?.data?.canConnect && |                           !appConfig?.data?.canConnect && | ||||||
|                           !appConfig?.data?.canCustomConnect) || |                           !appConfig?.data?.canCustomConnect) || | ||||||
| @@ -157,8 +149,6 @@ export default function Application() { | |||||||
|                       } |                       } | ||||||
|                       options={connectionOptions} |                       options={connectionOptions} | ||||||
|                     /> |                     /> | ||||||
|                       )} |  | ||||||
|                     </Can> |  | ||||||
|                   } |                   } | ||||||
|                 /> |                 /> | ||||||
|               </Routes> |               </Routes> | ||||||
| @@ -179,20 +169,17 @@ export default function Application() { | |||||||
|                     label={formatMessage('app.connections')} |                     label={formatMessage('app.connections')} | ||||||
|                     to={URLS.APP_CONNECTIONS(appKey)} |                     to={URLS.APP_CONNECTIONS(appKey)} | ||||||
|                     value={URLS.APP_CONNECTIONS_PATTERN} |                     value={URLS.APP_CONNECTIONS_PATTERN} | ||||||
|                     disabled={ |                     disabled={!app.supportsConnections} | ||||||
|                       !currentUserAbility.can('read', 'Connection') || |  | ||||||
|                       !app.supportsConnections |  | ||||||
|                     } |  | ||||||
|                     component={Link} |                     component={Link} | ||||||
|                     data-test="connections-tab" |                     data-test="connections-tab" | ||||||
|                   /> |                   /> | ||||||
|  |  | ||||||
|                   <Tab |                   <Tab | ||||||
|                     label={formatMessage('app.flows')} |                     label={formatMessage('app.flows')} | ||||||
|                     to={URLS.APP_FLOWS(appKey)} |                     to={URLS.APP_FLOWS(appKey)} | ||||||
|                     value={URLS.APP_FLOWS_PATTERN} |                     value={URLS.APP_FLOWS_PATTERN} | ||||||
|                     component={Link} |                     component={Link} | ||||||
|                     data-test="flows-tab" |                     data-test="flows-tab" | ||||||
|                     disabled={!currentUserAbility.can('read', 'Flow')} |  | ||||||
|                   /> |                   /> | ||||||
|                 </Tabs> |                 </Tabs> | ||||||
|               </Box> |               </Box> | ||||||
| @@ -200,20 +187,14 @@ export default function Application() { | |||||||
|               <Routes> |               <Routes> | ||||||
|                 <Route |                 <Route | ||||||
|                   path={`${URLS.FLOWS}/*`} |                   path={`${URLS.FLOWS}/*`} | ||||||
|                   element={ |                   element={<AppFlows appKey={appKey} />} | ||||||
|                     <Can I="read" a="Flow"> |  | ||||||
|                       <AppFlows appKey={appKey} /> |  | ||||||
|                     </Can> |  | ||||||
|                   } |  | ||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|                 <Route |                 <Route | ||||||
|                   path={`${URLS.CONNECTIONS}/*`} |                   path={`${URLS.CONNECTIONS}/*`} | ||||||
|                   element={ |                   element={<AppConnections appKey={appKey} />} | ||||||
|                     <Can I="read" a="Connection"> |  | ||||||
|                       <AppConnections appKey={appKey} /> |  | ||||||
|                     </Can> |  | ||||||
|                   } |  | ||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|                 <Route |                 <Route | ||||||
|                   path="/" |                   path="/" | ||||||
|                   element={ |                   element={ | ||||||
| @@ -237,24 +218,17 @@ export default function Application() { | |||||||
|         <Route |         <Route | ||||||
|           path="/connections/add" |           path="/connections/add" | ||||||
|           element={ |           element={ | ||||||
|             <Can I="create" a="Connection"> |             <AddAppConnection onClose={goToApplicationPage} application={app} /> | ||||||
|               <AddAppConnection |  | ||||||
|                 onClose={goToApplicationPage} |  | ||||||
|                 application={app} |  | ||||||
|               /> |  | ||||||
|             </Can> |  | ||||||
|           } |           } | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <Route |         <Route | ||||||
|           path="/connections/:connectionId/reconnect" |           path="/connections/:connectionId/reconnect" | ||||||
|           element={ |           element={ | ||||||
|             <Can I="create" a="Connection"> |  | ||||||
|             <ReconnectConnection |             <ReconnectConnection | ||||||
|               application={app} |               application={app} | ||||||
|               onClose={goToApplicationPage} |               onClose={goToApplicationPage} | ||||||
|             /> |             /> | ||||||
|             </Can> |  | ||||||
|           } |           } | ||||||
|         /> |         /> | ||||||
|       </Routes> |       </Routes> | ||||||
|   | |||||||
| @@ -84,15 +84,11 @@ export default function Applications() { | |||||||
|         )} |         )} | ||||||
|  |  | ||||||
|         {!isLoading && !hasApps && ( |         {!isLoading && !hasApps && ( | ||||||
|           <Can I="create" a="Connection" passThrough> |  | ||||||
|             {(allowed) => ( |  | ||||||
|           <NoResultFound |           <NoResultFound | ||||||
|             text={formatMessage('apps.noConnections')} |             text={formatMessage('apps.noConnections')} | ||||||
|                 {...(allowed && { to: URLS.NEW_APP_CONNECTION })} |             to={URLS.NEW_APP_CONNECTION} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|           </Can> |  | ||||||
|         )} |  | ||||||
|  |  | ||||||
|         {!isLoading && |         {!isLoading && | ||||||
|           apps?.map((app) => ( |           apps?.map((app) => ( | ||||||
|   | |||||||
| @@ -7,15 +7,13 @@ import * as URLS from 'config/urls'; | |||||||
| import useFormatMessage from 'hooks/useFormatMessage'; | import useFormatMessage from 'hooks/useFormatMessage'; | ||||||
| import { CREATE_FLOW } from 'graphql/mutations/create-flow'; | import { CREATE_FLOW } from 'graphql/mutations/create-flow'; | ||||||
| import Box from '@mui/material/Box'; | import Box from '@mui/material/Box'; | ||||||
|  |  | ||||||
| export default function CreateFlow() { | export default function CreateFlow() { | ||||||
|   const [searchParams] = useSearchParams(); |   const [searchParams] = useSearchParams(); | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|   const formatMessage = useFormatMessage(); |   const formatMessage = useFormatMessage(); | ||||||
|   const [createFlow, { error }] = useMutation(CREATE_FLOW); |   const [createFlow] = useMutation(CREATE_FLOW); | ||||||
|   const appKey = searchParams.get('appKey'); |   const appKey = searchParams.get('appKey'); | ||||||
|   const connectionId = searchParams.get('connectionId'); |   const connectionId = searchParams.get('connectionId'); | ||||||
|  |  | ||||||
|   React.useEffect(() => { |   React.useEffect(() => { | ||||||
|     async function initiate() { |     async function initiate() { | ||||||
|       const variables = {}; |       const variables = {}; | ||||||
| @@ -35,11 +33,6 @@ export default function CreateFlow() { | |||||||
|     } |     } | ||||||
|     initiate(); |     initiate(); | ||||||
|   }, [createFlow, navigate, appKey, connectionId]); |   }, [createFlow, navigate, appKey, connectionId]); | ||||||
|  |  | ||||||
|   if (error) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Box |     <Box | ||||||
|       sx={{ |       sx={{ | ||||||
| @@ -52,6 +45,7 @@ export default function CreateFlow() { | |||||||
|       }} |       }} | ||||||
|     > |     > | ||||||
|       <CircularProgress size={16} thickness={7.5} /> |       <CircularProgress size={16} thickness={7.5} /> | ||||||
|  |  | ||||||
|       <Typography variant="body2"> |       <Typography variant="body2"> | ||||||
|         {formatMessage('createFlow.creating')} |         {formatMessage('createFlow.creating')} | ||||||
|       </Typography> |       </Typography> | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ import Container from 'components/Container'; | |||||||
| import PageTitle from 'components/PageTitle'; | import PageTitle from 'components/PageTitle'; | ||||||
| import SearchInput from 'components/SearchInput'; | import SearchInput from 'components/SearchInput'; | ||||||
| import useFormatMessage from 'hooks/useFormatMessage'; | import useFormatMessage from 'hooks/useFormatMessage'; | ||||||
| import useCurrentUserAbility from 'hooks/useCurrentUserAbility'; |  | ||||||
| import * as URLS from 'config/urls'; | import * as URLS from 'config/urls'; | ||||||
| import useLazyFlows from 'hooks/useLazyFlows'; | import useLazyFlows from 'hooks/useLazyFlows'; | ||||||
|  |  | ||||||
| @@ -27,7 +26,6 @@ export default function Flows() { | |||||||
|   const page = parseInt(searchParams.get('page') || '', 10) || 1; |   const page = parseInt(searchParams.get('page') || '', 10) || 1; | ||||||
|   const [flowName, setFlowName] = React.useState(''); |   const [flowName, setFlowName] = React.useState(''); | ||||||
|   const [isLoading, setIsLoading] = React.useState(false); |   const [isLoading, setIsLoading] = React.useState(false); | ||||||
|   const currentUserAbility = useCurrentUserAbility(); |  | ||||||
|  |  | ||||||
|   const { data, mutate: fetchFlows } = useLazyFlows( |   const { data, mutate: fetchFlows } = useLazyFlows( | ||||||
|     { flowName, page }, |     { flowName, page }, | ||||||
| @@ -126,9 +124,7 @@ export default function Flows() { | |||||||
|         {!isLoading && !hasFlows && ( |         {!isLoading && !hasFlows && ( | ||||||
|           <NoResultFound |           <NoResultFound | ||||||
|             text={formatMessage('flows.noFlows')} |             text={formatMessage('flows.noFlows')} | ||||||
|             {...(currentUserAbility.can('create', 'Flow') && { |             to={URLS.CREATE_FLOW} | ||||||
|               to: URLS.CREATE_FLOW, |  | ||||||
|             })} |  | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|         {!isLoading && pageInfo && pageInfo.totalPages > 1 && ( |         {!isLoading && pageInfo && pageInfo.totalPages > 1 && ( | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ services: | |||||||
|     name: automatisch-main |     name: automatisch-main | ||||||
|     env: docker |     env: docker | ||||||
|     dockerfilePath: ./docker/Dockerfile |     dockerfilePath: ./docker/Dockerfile | ||||||
|     dockerContext: . |     dockerContext: ./docker | ||||||
|     repo: https://github.com/automatisch/automatisch |     repo: https://github.com/automatisch/automatisch | ||||||
|     autoDeploy: false |     autoDeploy: false | ||||||
|     envVars: |     envVars: | ||||||
| @@ -47,7 +47,7 @@ services: | |||||||
|     name: automatisch-worker |     name: automatisch-worker | ||||||
|     env: docker |     env: docker | ||||||
|     dockerfilePath: ./docker/Dockerfile |     dockerfilePath: ./docker/Dockerfile | ||||||
|     dockerContext: . |     dockerContext: ./docker | ||||||
|     repo: https://github.com/automatisch/automatisch |     repo: https://github.com/automatisch/automatisch | ||||||
|     autoDeploy: false |     autoDeploy: false | ||||||
|     envVars: |     envVars: | ||||||
|   | |||||||
							
								
								
									
										384
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										384
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -1455,18 +1455,6 @@ | |||||||
|     enabled "2.0.x" |     enabled "2.0.x" | ||||||
|     kuler "^2.0.0" |     kuler "^2.0.0" | ||||||
|  |  | ||||||
| "@dagrejs/dagre@^1.1.2": |  | ||||||
|   version "1.1.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@dagrejs/dagre/-/dagre-1.1.2.tgz#5ec339979447091f48d2144deed8c70dfadae374" |  | ||||||
|   integrity sha512-F09dphqvHsbe/6C2t2unbmpr5q41BNPEfJCdn8Z7aEBpVSy/zFQ/b4SWsweQjWNsYMDvE2ffNUN8X0CeFsEGNw== |  | ||||||
|   dependencies: |  | ||||||
|     "@dagrejs/graphlib" "2.2.2" |  | ||||||
|  |  | ||||||
| "@dagrejs/graphlib@2.2.2": |  | ||||||
|   version "2.2.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.2.tgz#74154d5cb880a23b4fae71034a09b4b5aef06feb" |  | ||||||
|   integrity sha512-CbyGpCDKsiTg/wuk79S7Muoj8mghDGAESWGxcSyhHX5jD35vYMBZochYVFzlHxynpE9unpu6O+4ZuhrLxASsOg== |  | ||||||
|  |  | ||||||
| "@docsearch/css@3.2.1", "@docsearch/css@^3.2.1": | "@docsearch/css@3.2.1", "@docsearch/css@^3.2.1": | ||||||
|   version "3.2.1" |   version "3.2.1" | ||||||
|   resolved "https://registry.npmjs.org/@docsearch/css/-/css-3.2.1.tgz" |   resolved "https://registry.npmjs.org/@docsearch/css/-/css-3.2.1.tgz" | ||||||
| @@ -3345,72 +3333,6 @@ | |||||||
|   resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz" |   resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz" | ||||||
|   integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== |   integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== | ||||||
|  |  | ||||||
| "@reactflow/background@11.3.12": |  | ||||||
|   version "11.3.12" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.3.12.tgz#9c9491cce4659bae13074fcdb48ac25664879d3f" |  | ||||||
|   integrity sha512-jBuWVb43JQy5h4WOS7G0PU8voGTEJNA+qDmx8/jyBtrjbasTesLNfQvboTGjnQYYiJco6mw5vrtQItAJDNoIqw== |  | ||||||
|   dependencies: |  | ||||||
|     "@reactflow/core" "11.11.2" |  | ||||||
|     classcat "^5.0.3" |  | ||||||
|     zustand "^4.4.1" |  | ||||||
|  |  | ||||||
| "@reactflow/controls@11.2.12": |  | ||||||
|   version "11.2.12" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.2.12.tgz#85e2aa5de17e2af28a5ecf6a75bb9c828a20640b" |  | ||||||
|   integrity sha512-L9F3+avFRShoprdT+5oOijm5gVsz2rqWCXBzOAgD923L1XFGIspdiHLLf8IlPGsT+mfl0GxbptZhaEeEzl1e3g== |  | ||||||
|   dependencies: |  | ||||||
|     "@reactflow/core" "11.11.2" |  | ||||||
|     classcat "^5.0.3" |  | ||||||
|     zustand "^4.4.1" |  | ||||||
|  |  | ||||||
| "@reactflow/core@11.11.2": |  | ||||||
|   version "11.11.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.11.2.tgz#c62f78297bda9d2e86a12228617ec3f91fbd4b22" |  | ||||||
|   integrity sha512-+GfgyskweL1PsgRSguUwfrT2eDotlFgaKfDLm7x0brdzzPJY2qbCzVetaxedaiJmIli3817iYbILvE9qLKwbRA== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3" "^7.4.0" |  | ||||||
|     "@types/d3-drag" "^3.0.1" |  | ||||||
|     "@types/d3-selection" "^3.0.3" |  | ||||||
|     "@types/d3-zoom" "^3.0.1" |  | ||||||
|     classcat "^5.0.3" |  | ||||||
|     d3-drag "^3.0.0" |  | ||||||
|     d3-selection "^3.0.0" |  | ||||||
|     d3-zoom "^3.0.0" |  | ||||||
|     zustand "^4.4.1" |  | ||||||
|  |  | ||||||
| "@reactflow/minimap@11.7.12": |  | ||||||
|   version "11.7.12" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.7.12.tgz#6b2fc671ee17e37ccd3bc038ae8d2121d0ce6291" |  | ||||||
|   integrity sha512-SRDU77c2PCF54PV/MQfkz7VOW46q7V1LZNOQlXAp7dkNyAOI6R+tb9qBUtUJOvILB+TCN6pRfD9fQ+2T99bW3Q== |  | ||||||
|   dependencies: |  | ||||||
|     "@reactflow/core" "11.11.2" |  | ||||||
|     "@types/d3-selection" "^3.0.3" |  | ||||||
|     "@types/d3-zoom" "^3.0.1" |  | ||||||
|     classcat "^5.0.3" |  | ||||||
|     d3-selection "^3.0.0" |  | ||||||
|     d3-zoom "^3.0.0" |  | ||||||
|     zustand "^4.4.1" |  | ||||||
|  |  | ||||||
| "@reactflow/node-resizer@2.2.12": |  | ||||||
|   version "2.2.12" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz#df82a7dfba883afea6a01a9c8210008a1ddba01f" |  | ||||||
|   integrity sha512-6LHJGuI1zHyRrZHw5gGlVLIWnvVxid9WIqw8FMFSg+oF2DuS3pAPwSoZwypy7W22/gDNl9eD1Dcl/OtFtDFQ+w== |  | ||||||
|   dependencies: |  | ||||||
|     "@reactflow/core" "11.11.2" |  | ||||||
|     classcat "^5.0.4" |  | ||||||
|     d3-drag "^3.0.0" |  | ||||||
|     d3-selection "^3.0.0" |  | ||||||
|     zustand "^4.4.1" |  | ||||||
|  |  | ||||||
| "@reactflow/node-toolbar@1.3.12": |  | ||||||
|   version "1.3.12" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz#89e7aa9d34b6213bb5e64c344d4e2e3cb7af3163" |  | ||||||
|   integrity sha512-4kJRvNna/E3y2MZW9/80wTKwkhw4pLJiz3D5eQrD13XcmojSb1rArO9CiwyrI+rMvs5gn6NlCFB4iN1F+Q+lxQ== |  | ||||||
|   dependencies: |  | ||||||
|     "@reactflow/core" "11.11.2" |  | ||||||
|     classcat "^5.0.3" |  | ||||||
|     zustand "^4.4.1" |  | ||||||
|  |  | ||||||
| "@rollup/plugin-babel@^5.2.0": | "@rollup/plugin-babel@^5.2.0": | ||||||
|   version "5.3.0" |   version "5.3.0" | ||||||
|   resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz" |   resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz" | ||||||
| @@ -3901,216 +3823,6 @@ | |||||||
|   dependencies: |   dependencies: | ||||||
|     "@types/node" "*" |     "@types/node" "*" | ||||||
|  |  | ||||||
| "@types/d3-array@*": |  | ||||||
|   version "3.2.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" |  | ||||||
|   integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== |  | ||||||
|  |  | ||||||
| "@types/d3-axis@*": |  | ||||||
|   version "3.0.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" |  | ||||||
|   integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-selection" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-brush@*": |  | ||||||
|   version "3.0.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" |  | ||||||
|   integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-selection" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-chord@*": |  | ||||||
|   version "3.0.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" |  | ||||||
|   integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== |  | ||||||
|  |  | ||||||
| "@types/d3-color@*": |  | ||||||
|   version "3.1.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" |  | ||||||
|   integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== |  | ||||||
|  |  | ||||||
| "@types/d3-contour@*": |  | ||||||
|   version "3.0.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" |  | ||||||
|   integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-array" "*" |  | ||||||
|     "@types/geojson" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-delaunay@*": |  | ||||||
|   version "6.0.4" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" |  | ||||||
|   integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== |  | ||||||
|  |  | ||||||
| "@types/d3-dispatch@*": |  | ||||||
|   version "3.0.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7" |  | ||||||
|   integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ== |  | ||||||
|  |  | ||||||
| "@types/d3-drag@*", "@types/d3-drag@^3.0.1": |  | ||||||
|   version "3.0.7" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" |  | ||||||
|   integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-selection" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-dsv@*": |  | ||||||
|   version "3.0.7" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" |  | ||||||
|   integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== |  | ||||||
|  |  | ||||||
| "@types/d3-ease@*": |  | ||||||
|   version "3.0.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" |  | ||||||
|   integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== |  | ||||||
|  |  | ||||||
| "@types/d3-fetch@*": |  | ||||||
|   version "3.0.7" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" |  | ||||||
|   integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-dsv" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-force@*": |  | ||||||
|   version "3.0.9" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29" |  | ||||||
|   integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA== |  | ||||||
|  |  | ||||||
| "@types/d3-format@*": |  | ||||||
|   version "3.0.4" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" |  | ||||||
|   integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== |  | ||||||
|  |  | ||||||
| "@types/d3-geo@*": |  | ||||||
|   version "3.1.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" |  | ||||||
|   integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/geojson" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-hierarchy@*": |  | ||||||
|   version "3.1.7" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b" |  | ||||||
|   integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== |  | ||||||
|  |  | ||||||
| "@types/d3-interpolate@*": |  | ||||||
|   version "3.0.4" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" |  | ||||||
|   integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-color" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-path@*": |  | ||||||
|   version "3.1.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" |  | ||||||
|   integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== |  | ||||||
|  |  | ||||||
| "@types/d3-polygon@*": |  | ||||||
|   version "3.0.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" |  | ||||||
|   integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== |  | ||||||
|  |  | ||||||
| "@types/d3-quadtree@*": |  | ||||||
|   version "3.0.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" |  | ||||||
|   integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== |  | ||||||
|  |  | ||||||
| "@types/d3-random@*": |  | ||||||
|   version "3.0.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" |  | ||||||
|   integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== |  | ||||||
|  |  | ||||||
| "@types/d3-scale-chromatic@*": |  | ||||||
|   version "3.0.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz#fc0db9c10e789c351f4c42d96f31f2e4df8f5644" |  | ||||||
|   integrity sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw== |  | ||||||
|  |  | ||||||
| "@types/d3-scale@*": |  | ||||||
|   version "4.0.8" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" |  | ||||||
|   integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-time" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-selection@*", "@types/d3-selection@^3.0.3": |  | ||||||
|   version "3.0.10" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe" |  | ||||||
|   integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg== |  | ||||||
|  |  | ||||||
| "@types/d3-shape@*": |  | ||||||
|   version "3.1.6" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" |  | ||||||
|   integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-path" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-time-format@*": |  | ||||||
|   version "4.0.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" |  | ||||||
|   integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== |  | ||||||
|  |  | ||||||
| "@types/d3-time@*": |  | ||||||
|   version "3.0.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" |  | ||||||
|   integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== |  | ||||||
|  |  | ||||||
| "@types/d3-timer@*": |  | ||||||
|   version "3.0.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" |  | ||||||
|   integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== |  | ||||||
|  |  | ||||||
| "@types/d3-transition@*": |  | ||||||
|   version "3.0.8" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.8.tgz#677707f5eed5b24c66a1918cde05963021351a8f" |  | ||||||
|   integrity sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-selection" "*" |  | ||||||
|  |  | ||||||
| "@types/d3-zoom@*", "@types/d3-zoom@^3.0.1": |  | ||||||
|   version "3.0.8" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" |  | ||||||
|   integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-interpolate" "*" |  | ||||||
|     "@types/d3-selection" "*" |  | ||||||
|  |  | ||||||
| "@types/d3@^7.4.0": |  | ||||||
|   version "7.4.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" |  | ||||||
|   integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== |  | ||||||
|   dependencies: |  | ||||||
|     "@types/d3-array" "*" |  | ||||||
|     "@types/d3-axis" "*" |  | ||||||
|     "@types/d3-brush" "*" |  | ||||||
|     "@types/d3-chord" "*" |  | ||||||
|     "@types/d3-color" "*" |  | ||||||
|     "@types/d3-contour" "*" |  | ||||||
|     "@types/d3-delaunay" "*" |  | ||||||
|     "@types/d3-dispatch" "*" |  | ||||||
|     "@types/d3-drag" "*" |  | ||||||
|     "@types/d3-dsv" "*" |  | ||||||
|     "@types/d3-ease" "*" |  | ||||||
|     "@types/d3-fetch" "*" |  | ||||||
|     "@types/d3-force" "*" |  | ||||||
|     "@types/d3-format" "*" |  | ||||||
|     "@types/d3-geo" "*" |  | ||||||
|     "@types/d3-hierarchy" "*" |  | ||||||
|     "@types/d3-interpolate" "*" |  | ||||||
|     "@types/d3-path" "*" |  | ||||||
|     "@types/d3-polygon" "*" |  | ||||||
|     "@types/d3-quadtree" "*" |  | ||||||
|     "@types/d3-random" "*" |  | ||||||
|     "@types/d3-scale" "*" |  | ||||||
|     "@types/d3-scale-chromatic" "*" |  | ||||||
|     "@types/d3-selection" "*" |  | ||||||
|     "@types/d3-shape" "*" |  | ||||||
|     "@types/d3-time" "*" |  | ||||||
|     "@types/d3-time-format" "*" |  | ||||||
|     "@types/d3-timer" "*" |  | ||||||
|     "@types/d3-transition" "*" |  | ||||||
|     "@types/d3-zoom" "*" |  | ||||||
|  |  | ||||||
| "@types/debug@^4.1.7": | "@types/debug@^4.1.7": | ||||||
|   version "4.1.8" |   version "4.1.8" | ||||||
|   resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz" |   resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz" | ||||||
| @@ -4201,11 +3913,6 @@ | |||||||
|     "@types/qs" "*" |     "@types/qs" "*" | ||||||
|     "@types/serve-static" "*" |     "@types/serve-static" "*" | ||||||
|  |  | ||||||
| "@types/geojson@*": |  | ||||||
|   version "7946.0.14" |  | ||||||
|   resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" |  | ||||||
|   integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== |  | ||||||
|  |  | ||||||
| "@types/graceful-fs@^4.1.2": | "@types/graceful-fs@^4.1.2": | ||||||
|   version "4.1.5" |   version "4.1.5" | ||||||
|   resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" |   resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" | ||||||
| @@ -6337,11 +6044,6 @@ cjs-module-lexer@^1.0.0: | |||||||
|   resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" |   resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" | ||||||
|   integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== |   integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== | ||||||
|  |  | ||||||
| classcat@^5.0.3, classcat@^5.0.4: |  | ||||||
|   version "5.0.5" |  | ||||||
|   resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.5.tgz#8c209f359a93ac302404a10161b501eba9c09c77" |  | ||||||
|   integrity sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w== |  | ||||||
|  |  | ||||||
| clean-css@^5.2.2: | clean-css@^5.2.2: | ||||||
|   version "5.2.2" |   version "5.2.2" | ||||||
|   resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz" |   resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz" | ||||||
| @@ -7127,68 +6829,6 @@ csstype@^3.1.1: | |||||||
|   resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz" |   resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz" | ||||||
|   integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== |   integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== | ||||||
|  |  | ||||||
| "d3-color@1 - 3": |  | ||||||
|   version "3.1.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" |  | ||||||
|   integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== |  | ||||||
|  |  | ||||||
| "d3-dispatch@1 - 3": |  | ||||||
|   version "3.0.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" |  | ||||||
|   integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== |  | ||||||
|  |  | ||||||
| "d3-drag@2 - 3", d3-drag@^3.0.0: |  | ||||||
|   version "3.0.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" |  | ||||||
|   integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== |  | ||||||
|   dependencies: |  | ||||||
|     d3-dispatch "1 - 3" |  | ||||||
|     d3-selection "3" |  | ||||||
|  |  | ||||||
| "d3-ease@1 - 3": |  | ||||||
|   version "3.0.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" |  | ||||||
|   integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== |  | ||||||
|  |  | ||||||
| "d3-interpolate@1 - 3": |  | ||||||
|   version "3.0.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" |  | ||||||
|   integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== |  | ||||||
|   dependencies: |  | ||||||
|     d3-color "1 - 3" |  | ||||||
|  |  | ||||||
| "d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: |  | ||||||
|   version "3.0.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" |  | ||||||
|   integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== |  | ||||||
|  |  | ||||||
| "d3-timer@1 - 3": |  | ||||||
|   version "3.0.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" |  | ||||||
|   integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== |  | ||||||
|  |  | ||||||
| "d3-transition@2 - 3": |  | ||||||
|   version "3.0.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" |  | ||||||
|   integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== |  | ||||||
|   dependencies: |  | ||||||
|     d3-color "1 - 3" |  | ||||||
|     d3-dispatch "1 - 3" |  | ||||||
|     d3-ease "1 - 3" |  | ||||||
|     d3-interpolate "1 - 3" |  | ||||||
|     d3-timer "1 - 3" |  | ||||||
|  |  | ||||||
| d3-zoom@^3.0.0: |  | ||||||
|   version "3.0.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" |  | ||||||
|   integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== |  | ||||||
|   dependencies: |  | ||||||
|     d3-dispatch "1 - 3" |  | ||||||
|     d3-drag "2 - 3" |  | ||||||
|     d3-interpolate "1 - 3" |  | ||||||
|     d3-selection "2 - 3" |  | ||||||
|     d3-transition "2 - 3" |  | ||||||
|  |  | ||||||
| damerau-levenshtein@^1.0.7: | damerau-levenshtein@^1.0.7: | ||||||
|   version "1.0.8" |   version "1.0.8" | ||||||
|   resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" |   resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" | ||||||
| @@ -14169,18 +13809,6 @@ react@^18.2.0: | |||||||
|   dependencies: |   dependencies: | ||||||
|     loose-envify "^1.1.0" |     loose-envify "^1.1.0" | ||||||
|  |  | ||||||
| reactflow@^11.11.2: |  | ||||||
|   version "11.11.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.2.tgz#4968866a9372e6004ad1e424a2141996f0ba769a" |  | ||||||
|   integrity sha512-o1fT3stSdhzW+SedCGNSmEvZvULZygZIMLyW67NcWNZrgwx1wuJfzLg5fuQ0Nzf389wItumZX/zP3zdaPX7lEw== |  | ||||||
|   dependencies: |  | ||||||
|     "@reactflow/background" "11.3.12" |  | ||||||
|     "@reactflow/controls" "11.2.12" |  | ||||||
|     "@reactflow/core" "11.11.2" |  | ||||||
|     "@reactflow/minimap" "11.7.12" |  | ||||||
|     "@reactflow/node-resizer" "2.2.12" |  | ||||||
|     "@reactflow/node-toolbar" "1.3.12" |  | ||||||
|  |  | ||||||
| read-cmd-shim@^2.0.0: | read-cmd-shim@^2.0.0: | ||||||
|   version "2.0.0" |   version "2.0.0" | ||||||
|   resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz" |   resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz" | ||||||
| @@ -16349,11 +15977,6 @@ url-parse-lax@^3.0.0: | |||||||
|   dependencies: |   dependencies: | ||||||
|     prepend-http "^2.0.0" |     prepend-http "^2.0.0" | ||||||
|  |  | ||||||
| use-sync-external-store@1.2.0: |  | ||||||
|   version "1.2.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" |  | ||||||
|   integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== |  | ||||||
|  |  | ||||||
| util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: | util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: | ||||||
|   version "1.0.2" |   version "1.0.2" | ||||||
|   resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" |   resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" | ||||||
| @@ -17325,10 +16948,3 @@ zen-observable@0.8.15: | |||||||
|   version "0.8.15" |   version "0.8.15" | ||||||
|   resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz" |   resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz" | ||||||
|   integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== |   integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== | ||||||
|  |  | ||||||
| zustand@^4.4.1: |  | ||||||
|   version "4.5.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848" |  | ||||||
|   integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g== |  | ||||||
|   dependencies: |  | ||||||
|     use-sync-external-store "1.2.0" |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user