Compare commits

...

20 Commits

Author SHA1 Message Date
Rıdvan Akca
176d056aed feat(asana): add find project action 2024-06-07 12:30:01 +02:00
Rıdvan Akca
0526ec5b06 feat(asana): add create project action 2024-06-07 12:28:14 +02:00
Rıdvan Akca
061a9c6947 feat(asana): add create task action 2024-06-07 11:39:38 +02:00
Rıdvan Akca
ab307cdee0 feat(asana): add new projects trigger 2024-06-05 11:18:52 +02:00
Rıdvan Akca
9d5dac1701 feat(asana): add asana integration 2024-06-05 10:17:36 +02:00
Ali BARIN
751eb41e72 Merge pull request #1817 from automatisch/new-editor-feature-flag
feat: introduce feature flag for new flow editor
2024-06-04 12:45:00 +02:00
kasia.oczkowska
f08dc25711 feat: introduce style and behavior improvements 2024-06-04 07:18:18 +00:00
kasia.oczkowska
737eb31776 feat: introduce custom edges, auto layout improvements and node data updates 2024-06-04 07:17:38 +00:00
kasia.oczkowska
d6abf283bc feat: introduce automatic layout for new flow editor 2024-06-04 07:17:38 +00:00
kasia.oczkowska
bac4ab5aa4 feat: introduce feature flag for new flow editor 2024-06-04 07:17:38 +00:00
Ali BARIN
b5839390fd Merge pull request #1911 from automatisch/render-yaml-fix
fix(render.yaml): correct docker contexts
2024-06-03 11:21:31 +02:00
Ali BARIN
d19271dae1 fix(render.yaml): correct docker contexts 2024-06-03 10:23:28 +02:00
Ali BARIN
ef5a09314e Merge pull request #1907 from automatisch/fix-admin-apps-undefined-error
fix(AdminApplicationSettings): handle undefined appConfig object
2024-05-31 13:52:13 +02:00
Rıdvan Akca
ba52e298eb fix(AdminApplicationSettings): handle undefined appConfig object 2024-05-31 13:26:06 +02:00
Ali BARIN
b3c3998189 Merge pull request #1895 from automatisch/fix-appkey-error-in-flowrow
fix: remove unnecessary appKey in FlowRow and FlowContextMenu
2024-05-31 12:54:13 +02:00
Ömer Faruk Aydın
782f9b5c04 Merge pull request #1900 from automatisch/release/v0.12.0
release(v0.12.0): Update version to 0.12.0
2024-05-28 12:05:52 +02:00
Faruk AYDIN
3079d8c605 Update version to 0.12.0 2024-05-28 11:13:54 +02:00
Rıdvan Akca
c5202d7b3e fix: remove unnecessary appKey in FlowRow and FlowContextMenu 2024-05-24 14:11:22 +02:00
Ali BARIN
fbae83f4de Merge pull request #1874 from automatisch/make-value-column-text-in-datastore
fix(datastore): make value column text
2024-05-23 10:13:47 +02:00
Ali BARIN
1dc9646894 fix(datastore): make value column text 2024-05-09 20:31:47 +00:00
53 changed files with 2183 additions and 21 deletions

View File

@@ -0,0 +1,216 @@
import defineAction from '../../../../helpers/define-action.js';
import omitBy from 'lodash/omitBy.js';
import isEmpty from 'lodash/isEmpty.js';
export default defineAction({
name: 'Create project',
key: 'createProject',
description: 'Creates a new project.',
arguments: [
{
label: 'Workspace',
key: 'workspaceId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWorkspaces',
},
],
},
},
{
label: 'Team',
key: 'teamId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTeams',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
{
label: 'Due date',
key: 'dueDate',
type: 'string',
required: false,
description: 'Example due on: 2019-09-15',
variables: true,
},
{
label: 'Name',
key: 'name',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Notes',
key: 'notes',
type: 'string',
required: true,
description: 'You can format the notes using html.',
variables: true,
},
{
label: 'Is the notes rich text?',
key: 'richText',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'No',
value: 'false',
},
{
label: 'Yes',
value: 'true',
},
],
},
{
label: 'Default View',
key: 'defaultView',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'List',
value: 'list',
},
{
label: 'Board',
value: 'board',
},
{
label: 'Calendar',
value: 'calendar',
},
{
label: 'Timeline',
value: 'timeline',
},
],
},
{
label: 'Owner',
key: 'ownerId',
type: 'dropdown',
required: false,
dependsOn: ['parameters.workspaceId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listUsers',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
{
label: 'Followers',
key: 'followerIds',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'Follower',
key: 'followerId',
type: 'dropdown',
required: false,
dependsOn: ['parameters.workspaceId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listUsers',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
],
},
],
async run($) {
const {
workspaceId,
teamId,
dueDate,
name,
notes,
richText,
defaultView,
ownerId,
followerIds,
} = $.step.parameters;
const allFollowers = followerIds
.map((followerId) => followerId.followerId)
.filter(Boolean);
const data = {
workspace: workspaceId,
team: teamId,
due_on: dueDate,
name,
default_view: defaultView,
owner: ownerId,
followers: allFollowers,
};
if (richText === 'true') {
data.html_notes = notes;
} else {
data.notes = notes;
}
const filteredData = omitBy(data, isEmpty);
const response = await $.http.post('/1.0/projects', { data: filteredData });
$.setActionItem({
raw: response.data,
});
},
});

View File

@@ -0,0 +1,312 @@
import defineAction from '../../../../helpers/define-action.js';
import omitBy from 'lodash/omitBy.js';
import isEmpty from 'lodash/isEmpty.js';
export default defineAction({
name: 'Create task',
key: 'createTask',
description: 'Creates a new task.',
arguments: [
{
label: 'Workspace',
key: 'workspaceId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWorkspaces',
},
],
},
},
{
label: 'Project',
key: 'projectId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listProjects',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
{
label: 'Section',
key: 'sectionId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSections',
},
{
name: 'parameters.projectId',
value: '{parameters.projectId}',
},
],
},
},
{
label: 'Due date type',
key: 'dueDateType',
type: 'dropdown',
required: false,
description: "If not filled in, 'Date & time' will be assumed.",
options: [
{
label: 'Date & time',
value: 'at',
},
{
label: 'Date only',
value: 'on',
},
],
},
{
label: 'Due date (date & time)',
key: 'dueDate',
type: 'string',
required: false,
description:
'Example due at: 2019-09-15T02:06:58.147Z, example due on: 2019-09-15',
variables: true,
},
{
label: 'Name',
key: 'name',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Description',
key: 'description',
type: 'string',
required: false,
description: 'You can format the description using html.',
variables: true,
},
{
label: 'Is the description rich text?',
key: 'richText',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'No',
value: 'false',
},
{
label: 'Yes',
value: 'true',
},
],
},
{
label: 'Mark Task as complete?',
key: 'taskCompleted',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'No',
value: 'false',
},
{
label: 'Yes',
value: 'true',
},
],
},
{
label: 'Mark Task as liked?',
key: 'taskLiked',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'No',
value: 'false',
},
{
label: 'Yes',
value: 'true',
},
],
},
{
label: 'Assignee',
key: 'assigneeId',
type: 'dropdown',
required: false,
dependsOn: ['parameters.workspaceId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listUsers',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
{
label: 'Followers',
key: 'followerIds',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'Follower',
key: 'followerId',
type: 'dropdown',
required: false,
dependsOn: ['parameters.workspaceId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listUsers',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
],
},
{
label: 'Tags',
key: 'tagIds',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'Tag',
key: 'tagId',
type: 'dropdown',
required: false,
dependsOn: ['parameters.workspaceId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTags',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
],
},
],
async run($) {
const {
workspaceId,
projectId,
sectionId,
dueDateType,
dueDate,
name,
description,
richText,
taskCompleted,
taskLiked,
assigneeId,
followerIds,
tagIds,
} = $.step.parameters;
const allFollowers = followerIds
.map((followerId) => followerId.followerId)
.filter(Boolean);
const allTags = tagIds.map((tagId) => tagId.tagId).filter(Boolean);
const data = {
name,
completed: taskCompleted,
liked: taskLiked,
assignee: assigneeId,
assignee_section: sectionId,
followers: allFollowers,
projects: projectId,
tags: allTags,
workspace: workspaceId,
};
if (richText === 'true') {
data.html_notes = description;
} else {
data.notes = description;
}
if (dueDateType === 'on') {
data.due_on = dueDate;
} else {
data.due_at = dueDate;
}
const filteredData = omitBy(data, isEmpty);
const response = await $.http.post('/1.0/tasks', { data: filteredData });
$.setActionItem({
raw: response.data,
});
},
});

View File

@@ -0,0 +1,49 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Find project',
key: 'findProject',
description: 'Finds an existing project.',
arguments: [
{
label: 'Workspace',
key: 'workspaceId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWorkspaces',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const { workspaceId, name } = $.step.parameters;
const { data } = await $.http.get(
`/1.0/workspaces/${workspaceId}/projects`
);
const project = data.data.find((project) => project.name === name);
$.setActionItem({
raw: project,
});
},
});

View File

@@ -0,0 +1,5 @@
import createProject from './create-project/index.js';
import createTask from './create-task/index.js';
import findProject from './find-project/index.js';
export default [createProject, createTask, findProject];

View File

@@ -0,0 +1,8 @@
<svg width="555" height="110" viewBox="0 0 555 110" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M541.011 83.513C541.347 87.339 544.407 92.209 549.709 92.209H552.811C554.014 92.209 555 91.2232 555 90.0197V21.7948H554.986C554.923 20.6454 553.974 19.7248 552.811 19.7248H543.199C542.036 19.7248 541.087 20.6454 541.023 21.7948H541.011V27.3385C535.122 20.0789 525.836 17.0657 516.525 17.0657C495.359 17.0657 478.202 34.2365 478.202 55.419C478.202 76.6027 495.359 93.7741 516.525 93.7741V93.7759C525.836 93.7759 535.983 90.1606 541.01 83.5042L541.011 83.513V83.513ZM516.562 80.3509C503.101 80.3509 492.188 69.1896 492.188 55.4192C492.188 41.6511 503.101 30.4892 516.562 30.4892C530.022 30.4892 540.934 41.6511 540.934 55.4192C540.934 69.1896 530.022 80.3509 516.562 80.3509V80.3509Z" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M466.05 85.8589L466.045 50.5554H466.046C466.046 30.655 453.501 17.2299 433.497 17.2299C423.947 17.2299 416.119 22.7561 413.355 27.5032C412.757 23.7913 410.788 19.8896 404.681 19.8896H401.569C400.365 19.8896 399.382 20.876 399.382 22.0795V83.6835C399.382 83.6853 399.382 83.69 399.382 83.6929V90.3102H399.394C399.457 91.4579 400.408 92.3796 401.57 92.3796H411.182C411.33 92.3796 411.474 92.3621 411.613 92.3347C411.677 92.3225 411.736 92.2975 411.798 92.28C411.869 92.2579 411.944 92.241 412.012 92.213C412.097 92.1775 412.175 92.1298 412.255 92.0855C412.294 92.0617 412.334 92.0448 412.372 92.0197C412.468 91.958 412.556 91.8835 412.641 91.8072C412.655 91.7932 412.672 91.7839 412.686 91.7711C412.781 91.6785 412.868 91.5766 412.946 91.4707C412.946 91.4689 412.946 91.4689 412.946 91.4689C413.187 91.1382 413.333 90.7399 413.357 90.3102H413.369V50.0081C413.369 39.3201 422.028 30.655 432.709 30.655C443.389 30.655 452.047 39.3201 452.047 50.0081L452.056 83.6952L452.058 83.6835C452.058 83.7132 452.063 83.7441 452.063 83.7761V90.3102H452.076C452.139 91.4579 453.089 92.3796 454.251 92.3796H463.864C464.012 92.3796 464.156 92.3621 464.295 92.3347C464.352 92.3243 464.404 92.3015 464.46 92.2858C464.538 92.2631 464.619 92.2433 464.695 92.213C464.773 92.1804 464.845 92.135 464.92 92.0931C464.965 92.0675 465.013 92.0489 465.056 92.0197C465.145 91.9615 465.226 91.8911 465.306 91.8212C465.326 91.8026 465.349 91.7886 465.368 91.7694C465.459 91.6814 465.54 91.5865 465.615 91.487C465.62 91.4794 465.626 91.4736 465.632 91.466C465.868 91.1382 466.013 90.7428 466.038 90.3161C466.038 90.3131 466.039 90.3102 466.039 90.3102H466.052V85.86L466.05 85.8589" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M365.94 83.5127C366.276 87.3387 369.336 92.2088 374.638 92.2088H377.74C378.943 92.2088 379.927 91.223 379.927 90.0195V21.7945H379.915C379.852 20.6452 378.901 19.7246 377.74 19.7246H368.128C366.965 19.7246 366.016 20.6452 365.951 21.7945H365.94V27.3382C360.05 20.0786 350.764 17.0654 341.453 17.0654C320.288 17.0654 303.131 34.2362 303.131 55.4188C303.131 76.6025 320.288 93.7739 341.453 93.7739V93.7756C350.764 93.7756 360.912 90.1604 365.939 83.504L365.94 83.5127V83.5127ZM341.49 80.3506C328.03 80.3506 317.117 69.1893 317.117 55.4189C317.117 41.6509 328.03 30.489 341.49 30.489C354.952 30.489 365.862 41.6509 365.862 55.4189C365.862 69.1893 354.952 80.3506 341.49 80.3506V80.3506Z" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M246.284 73.7415C252.702 78.1905 259.706 80.3513 266.437 80.3513C272.85 80.3513 279.479 77.0242 279.479 71.2337C279.479 63.5024 265.033 62.2995 255.957 59.2124C246.88 56.1252 239.061 49.7437 239.061 39.4092C239.061 23.5956 253.14 17.0645 266.281 17.0645C274.607 17.0645 283.198 19.8121 288.767 23.7482C290.686 25.2027 289.517 26.8726 289.517 26.8726L284.201 34.4716C283.603 35.3276 282.559 36.067 281.059 35.1407C279.559 34.2149 274.298 30.4884 266.281 30.4884C258.263 30.4884 253.434 34.1939 253.434 38.7868C253.434 44.2943 259.711 46.0266 267.063 47.9038C279.875 51.36 293.852 55.5144 293.852 71.2337C293.852 85.1665 280.829 93.777 266.437 93.777C255.53 93.777 246.244 90.6654 238.456 84.9459C236.834 83.3208 237.967 81.8121 237.967 81.8121L243.257 74.2515C244.334 72.8378 245.691 73.331 246.284 73.7415" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M209.331 83.5127C209.668 87.3387 212.728 92.2088 218.03 92.2088H221.132C222.334 92.2088 223.32 91.223 223.32 90.0195V21.7945H223.307C223.244 20.6452 222.294 19.7246 221.132 19.7246H211.519C210.357 19.7246 209.408 20.6452 209.343 21.7945H209.331V27.3382C203.442 20.0786 194.156 17.0654 184.845 17.0654C163.68 17.0654 146.522 34.2362 146.522 55.4188C146.522 76.6025 163.68 93.7739 184.845 93.7739V93.7756C194.156 93.7756 204.304 90.1604 209.33 83.504L209.331 83.5127V83.5127ZM184.883 80.3506C171.422 80.3506 160.509 69.1893 160.509 55.4189C160.509 41.6509 171.422 30.489 184.883 30.489C198.343 30.489 209.255 41.6509 209.255 55.4189C209.255 69.1893 198.343 80.3506 184.883 80.3506V80.3506Z" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.794 58.0274C78.5507 58.0274 67.0041 69.5741 67.0041 83.8185C67.0041 98.0618 78.5507 109.608 92.794 109.608C107.037 109.608 118.584 98.0618 118.584 83.8185C118.584 69.5741 107.037 58.0274 92.794 58.0274V58.0274ZM25.7899 58.0298C11.5466 58.0298 0 69.5741 0 83.8186C0 98.0618 11.5466 109.608 25.7899 109.608C40.0338 109.608 51.581 98.0618 51.581 83.8186C51.581 69.5741 40.0338 58.0298 25.7899 58.0298V58.0298ZM85.0815 25.7894C85.0815 40.0338 73.5354 51.5816 59.2921 51.5816C45.0483 51.5816 33.5022 40.0338 33.5022 25.7894C33.5022 11.5478 45.0483 0 59.2921 0C73.5354 0 85.0815 11.5478 85.0815 25.7894V25.7894Z" fill="#FF584A"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,25 @@
import { URLSearchParams } from 'url';
import crypto from 'crypto';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const state = crypto.randomBytes(100).toString('base64url');
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
response_type: 'code',
//scope: authScope.join(' '),
state,
});
const url = `https://app.asana.com/-/oauth_authorize?${searchParams.toString()}`;
await $.auth.set({
url,
originalState: state,
});
}

View File

@@ -0,0 +1,48 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/asana/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Asana, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -0,0 +1,8 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.data.email;
};
export default isStillVerified;

View File

@@ -0,0 +1,37 @@
import { URLSearchParams } from 'node:url';
const refreshToken = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const params = new URLSearchParams({
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
redirect_uri: redirectUri,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken,
});
const { data } = await $.http.post(
'https://app.asana.com/-/oauth_token',
params.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -0,0 +1,39 @@
const verifyCredentials = async ($) => {
if ($.auth.data.originalState !== $.auth.data.state) {
throw new Error("The 'state' parameter does not match.");
}
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const { data } = await $.http.post(
'https://app.asana.com/-/oauth_token',
{
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
id: data.data.id,
gid: data.data.gid,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
screenName: `${data.data.name} - ${data.data.email}`,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,12 @@
const addAuthHeader = ($, requestConfig) => {
if (requestConfig.additionalProperties?.skipAddingAuthHeader)
return requestConfig;
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,8 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get(
`/1.0/users/${$.auth.data.gid}`
);
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,15 @@
import listProjects from './list-projects/index.js';
import listSections from './list-sections/index.js';
import listTags from './list-tags/index.js';
import listTeams from './list-teams/index.js';
import listUsers from './list-users/index.js';
import listWorkspaces from './list-workspaces/index.js';
export default [
listProjects,
listSections,
listTags,
listTeams,
listUsers,
listWorkspaces,
];

View File

@@ -0,0 +1,42 @@
export default {
name: 'List projects',
key: 'listProjects',
async run($) {
const projects = {
data: [],
};
const workspaceId = $.step.parameters.workspaceId;
if (!workspaceId) {
return projects;
}
const params = {
limit: 100,
offset: undefined,
workspace: workspaceId,
};
do {
const {
data: { data, next_page },
} = await $.http.get('/1.0/projects', {
params,
});
params.offset = next_page?.offset;
if (data) {
for (const project of data) {
projects.data.push({
value: project.gid,
name: project.name,
});
}
}
} while (params.offset);
return projects;
},
};

View File

@@ -0,0 +1,41 @@
export default {
name: 'List sections',
key: 'listSections',
async run($) {
const sections = {
data: [],
};
const projectId = $.step.parameters.projectId;
if (!projectId) {
return sections;
}
const params = {
limit: 100,
offset: undefined,
};
do {
const {
data: { data, next_page },
} = await $.http.get(`/1.0/projects/${projectId}/sections`, {
params,
});
params.offset = next_page?.offset;
if (data) {
for (const section of data) {
sections.data.push({
value: section.gid,
name: section.name,
});
}
}
} while (params.offset);
return sections;
},
};

View File

@@ -0,0 +1,42 @@
export default {
name: 'List tags',
key: 'listTags',
async run($) {
const tags = {
data: [],
};
const workspaceId = $.step.parameters.workspaceId;
if (!workspaceId) {
return workspaceId;
}
const params = {
limit: 100,
offset: undefined,
workspace: workspaceId,
};
do {
const {
data: { data, next_page },
} = await $.http.get('/1.0/tags', {
params,
});
params.offset = next_page?.offset;
if (data) {
for (const tag of data) {
tags.data.push({
value: tag.gid,
name: tag.name,
});
}
}
} while (params.offset);
return tags;
},
};

View File

@@ -0,0 +1,42 @@
export default {
name: 'List teams',
key: 'listTeams',
async run($) {
const teams = {
data: [],
};
const workspaceId = $.step.parameters.workspaceId;
if (!workspaceId) {
return workspaceId;
}
const params = {
limit: 100,
offset: undefined,
workspace: workspaceId,
};
do {
const {
data: { data, next_page },
} = await $.http.get('/1.0/teams', {
params,
});
params.offset = next_page?.offset;
if (data) {
for (const team of data) {
teams.data.push({
value: team.gid,
name: team.name,
});
}
}
} while (params.offset);
return teams;
},
};

View File

@@ -0,0 +1,42 @@
export default {
name: 'List users',
key: 'listUsers',
async run($) {
const users = {
data: [],
};
const workspaceId = $.step.parameters.workspaceId;
if (!workspaceId) {
return workspaceId;
}
const params = {
limit: 100,
offset: undefined,
workspace: workspaceId,
};
do {
const {
data: { data, next_page },
} = await $.http.get('/1.0/users', {
params,
});
params.offset = next_page?.offset;
if (data) {
for (const user of data) {
users.data.push({
value: user.gid,
name: user.name,
});
}
}
} while (params.offset);
return users;
},
};

View File

@@ -0,0 +1,34 @@
export default {
name: 'List workspaces',
key: 'listWorkspaces',
async run($) {
const workspaces = {
data: [],
};
const params = {
limit: 100,
offset: undefined,
};
do {
const {
data: { data, next_page },
} = await $.http.get('/1.0/workspaces', { params });
params.offset = next_page?.offset;
if (data) {
for (const workspace of data) {
workspaces.data.push({
value: workspace.gid,
name: workspace.name,
});
}
}
} while (params.offset);
return workspaces;
},
};

View File

@@ -0,0 +1,22 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import dynamicData from './dynamic-data/index.js';
import triggers from './triggers/index.js';
import actions from './actions/index.js';
export default defineApp({
name: 'Asana',
key: 'asana',
baseUrl: 'https://asana.com',
apiBaseUrl: 'https://app.asana.com/api',
iconUrl: '{BASE_URL}/apps/asana/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/asana/connection',
primaryColor: '690031',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
dynamicData,
triggers,
actions,
});

View File

@@ -0,0 +1,3 @@
import newProjects from './new-projects/index.js';
export default [newProjects];

View File

@@ -0,0 +1,59 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New projects',
key: 'newProjects',
pollInterval: 15,
description: 'Triggers when a new project is created.',
arguments: [
{
label: 'Workspace',
key: 'workspaceId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWorkspaces',
},
],
},
},
],
async run($) {
const workspaceId = $.step.parameters.workspaceId;
const params = {
limit: 100,
offset: undefined,
workspace: workspaceId,
};
do {
const {
data: { data, next_page },
} = await $.http.get('/1.0/projects', {
params,
});
params.offset = next_page?.offset;
if (data) {
for (const project of data) {
$.pushTriggerItem({
raw: project,
meta: {
internalId: project.gid,
},
});
}
}
} while (params.offset);
},
});

View File

@@ -52,7 +52,7 @@ const appConfig = {
isDev: appEnv === 'development',
isTest: appEnv === 'test',
isProd: appEnv === 'production',
version: '0.11.0',
version: '0.12.0',
postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development',
postgresSchema: process.env.POSTGRES_SCHEMA || 'public',
postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'),

View File

@@ -10,7 +10,7 @@ describe('GET /api/v1/automatisch/version', () => {
const expectedPayload = {
data: {
version: '0.11.0',
version: '0.12.0',
},
meta: {
count: 1,

View File

@@ -0,0 +1,11 @@
export async function up(knex) {
return knex.schema.alterTable('datastore', (table) => {
table.text('value').alter();
});
}
export async function down(knex) {
return knex.schema.alterTable('datastore', (table) => {
table.string('value').alter();
});
}

View File

@@ -50,6 +50,16 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/appwrite/connection' },
],
},
{
text: 'Asana',
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/asana/triggers' },
{ text: 'Actions', link: '/apps/asana/actions' },
{ text: 'Connection', link: '/apps/asana/connection' },
],
},
{
text: 'Carbone',
collapsible: true,

View File

@@ -0,0 +1,16 @@
---
favicon: /favicons/asana.svg
items:
- name: Create project
desc: Creates a new project.
- name: Create task
desc: Creates a new task.
- name: Find project
desc: Finds an existing project.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -0,0 +1,17 @@
# Asana
:::info
This page explains the steps you need to follow to set up the Asana
connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Go to the [Asana developer console](https://app.asana.com/0/my-apps) to create a project.
2. Click on the **Create new app** in **My Apps**, and click on the **New Project** button.
3. Fill the form for your project and click on the **Create app** button.
4. Go to **Manage distrubition** and select **Any workspace** option in **Choose a distribution method** and save it.
5. Go to **OAuth** tab from left panel and click on the **+Add redirect URL** button.
6. Copy **OAuth Redirect URL** from Automatisch to the redirect url field, and click on the **Add** button.
7. Copy the **Your Client ID** value on the same page to the `Client ID` field on Automatisch.
8. Copy the **Your Client secret** value on the same page to the `Client Secret` field on Automatisch.
9. Click **Submit** button on Automatisch.
10. Congrats! Start using your new Asana connection within the flows.

View File

@@ -0,0 +1,12 @@
---
favicon: /favicons/asana.svg
items:
- name: New projects
desc: Triggers when a new project is created.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -4,6 +4,7 @@ The following integrations are currently supported by Automatisch.
- [Airtable](/apps/airtable/actions)
- [Appwrite](/apps/appwrite/triggers)
- [Asana](/apps/asana/triggers)
- [Carbone](/apps/carbone/actions)
- [Datastore](/apps/datastore/actions)
- [DeepL](/apps/deepl/actions)

View File

@@ -0,0 +1,8 @@
<svg width="555" height="110" viewBox="0 0 555 110" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M541.011 83.513C541.347 87.339 544.407 92.209 549.709 92.209H552.811C554.014 92.209 555 91.2232 555 90.0197V21.7948H554.986C554.923 20.6454 553.974 19.7248 552.811 19.7248H543.199C542.036 19.7248 541.087 20.6454 541.023 21.7948H541.011V27.3385C535.122 20.0789 525.836 17.0657 516.525 17.0657C495.359 17.0657 478.202 34.2365 478.202 55.419C478.202 76.6027 495.359 93.7741 516.525 93.7741V93.7759C525.836 93.7759 535.983 90.1606 541.01 83.5042L541.011 83.513V83.513ZM516.562 80.3509C503.101 80.3509 492.188 69.1896 492.188 55.4192C492.188 41.6511 503.101 30.4892 516.562 30.4892C530.022 30.4892 540.934 41.6511 540.934 55.4192C540.934 69.1896 530.022 80.3509 516.562 80.3509V80.3509Z" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M466.05 85.8589L466.045 50.5554H466.046C466.046 30.655 453.501 17.2299 433.497 17.2299C423.947 17.2299 416.119 22.7561 413.355 27.5032C412.757 23.7913 410.788 19.8896 404.681 19.8896H401.569C400.365 19.8896 399.382 20.876 399.382 22.0795V83.6835C399.382 83.6853 399.382 83.69 399.382 83.6929V90.3102H399.394C399.457 91.4579 400.408 92.3796 401.57 92.3796H411.182C411.33 92.3796 411.474 92.3621 411.613 92.3347C411.677 92.3225 411.736 92.2975 411.798 92.28C411.869 92.2579 411.944 92.241 412.012 92.213C412.097 92.1775 412.175 92.1298 412.255 92.0855C412.294 92.0617 412.334 92.0448 412.372 92.0197C412.468 91.958 412.556 91.8835 412.641 91.8072C412.655 91.7932 412.672 91.7839 412.686 91.7711C412.781 91.6785 412.868 91.5766 412.946 91.4707C412.946 91.4689 412.946 91.4689 412.946 91.4689C413.187 91.1382 413.333 90.7399 413.357 90.3102H413.369V50.0081C413.369 39.3201 422.028 30.655 432.709 30.655C443.389 30.655 452.047 39.3201 452.047 50.0081L452.056 83.6952L452.058 83.6835C452.058 83.7132 452.063 83.7441 452.063 83.7761V90.3102H452.076C452.139 91.4579 453.089 92.3796 454.251 92.3796H463.864C464.012 92.3796 464.156 92.3621 464.295 92.3347C464.352 92.3243 464.404 92.3015 464.46 92.2858C464.538 92.2631 464.619 92.2433 464.695 92.213C464.773 92.1804 464.845 92.135 464.92 92.0931C464.965 92.0675 465.013 92.0489 465.056 92.0197C465.145 91.9615 465.226 91.8911 465.306 91.8212C465.326 91.8026 465.349 91.7886 465.368 91.7694C465.459 91.6814 465.54 91.5865 465.615 91.487C465.62 91.4794 465.626 91.4736 465.632 91.466C465.868 91.1382 466.013 90.7428 466.038 90.3161C466.038 90.3131 466.039 90.3102 466.039 90.3102H466.052V85.86L466.05 85.8589" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M365.94 83.5127C366.276 87.3387 369.336 92.2088 374.638 92.2088H377.74C378.943 92.2088 379.927 91.223 379.927 90.0195V21.7945H379.915C379.852 20.6452 378.901 19.7246 377.74 19.7246H368.128C366.965 19.7246 366.016 20.6452 365.951 21.7945H365.94V27.3382C360.05 20.0786 350.764 17.0654 341.453 17.0654C320.288 17.0654 303.131 34.2362 303.131 55.4188C303.131 76.6025 320.288 93.7739 341.453 93.7739V93.7756C350.764 93.7756 360.912 90.1604 365.939 83.504L365.94 83.5127V83.5127ZM341.49 80.3506C328.03 80.3506 317.117 69.1893 317.117 55.4189C317.117 41.6509 328.03 30.489 341.49 30.489C354.952 30.489 365.862 41.6509 365.862 55.4189C365.862 69.1893 354.952 80.3506 341.49 80.3506V80.3506Z" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M246.284 73.7415C252.702 78.1905 259.706 80.3513 266.437 80.3513C272.85 80.3513 279.479 77.0242 279.479 71.2337C279.479 63.5024 265.033 62.2995 255.957 59.2124C246.88 56.1252 239.061 49.7437 239.061 39.4092C239.061 23.5956 253.14 17.0645 266.281 17.0645C274.607 17.0645 283.198 19.8121 288.767 23.7482C290.686 25.2027 289.517 26.8726 289.517 26.8726L284.201 34.4716C283.603 35.3276 282.559 36.067 281.059 35.1407C279.559 34.2149 274.298 30.4884 266.281 30.4884C258.263 30.4884 253.434 34.1939 253.434 38.7868C253.434 44.2943 259.711 46.0266 267.063 47.9038C279.875 51.36 293.852 55.5144 293.852 71.2337C293.852 85.1665 280.829 93.777 266.437 93.777C255.53 93.777 246.244 90.6654 238.456 84.9459C236.834 83.3208 237.967 81.8121 237.967 81.8121L243.257 74.2515C244.334 72.8378 245.691 73.331 246.284 73.7415" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M209.331 83.5127C209.668 87.3387 212.728 92.2088 218.03 92.2088H221.132C222.334 92.2088 223.32 91.223 223.32 90.0195V21.7945H223.307C223.244 20.6452 222.294 19.7246 221.132 19.7246H211.519C210.357 19.7246 209.408 20.6452 209.343 21.7945H209.331V27.3382C203.442 20.0786 194.156 17.0654 184.845 17.0654C163.68 17.0654 146.522 34.2362 146.522 55.4188C146.522 76.6025 163.68 93.7739 184.845 93.7739V93.7756C194.156 93.7756 204.304 90.1604 209.33 83.504L209.331 83.5127V83.5127ZM184.883 80.3506C171.422 80.3506 160.509 69.1893 160.509 55.4189C160.509 41.6509 171.422 30.489 184.883 30.489C198.343 30.489 209.255 41.6509 209.255 55.4189C209.255 69.1893 198.343 80.3506 184.883 80.3506V80.3506Z" fill="#690031"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.794 58.0274C78.5507 58.0274 67.0041 69.5741 67.0041 83.8185C67.0041 98.0618 78.5507 109.608 92.794 109.608C107.037 109.608 118.584 98.0618 118.584 83.8185C118.584 69.5741 107.037 58.0274 92.794 58.0274V58.0274ZM25.7899 58.0298C11.5466 58.0298 0 69.5741 0 83.8186C0 98.0618 11.5466 109.608 25.7899 109.608C40.0338 109.608 51.581 98.0618 51.581 83.8186C51.581 69.5741 40.0338 58.0298 25.7899 58.0298V58.0298ZM85.0815 25.7894C85.0815 40.0338 73.5354 51.5816 59.2921 51.5816C45.0483 51.5816 33.5022 40.0338 33.5022 25.7894C33.5022 11.5478 45.0483 0 59.2921 0C73.5354 0 85.0815 11.5478 85.0815 25.7894V25.7894Z" fill="#FF584A"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -7,6 +7,7 @@
"@apollo/client": "^3.6.9",
"@casl/ability": "^6.5.0",
"@casl/react": "^3.1.0",
"@dagrejs/dagre": "^1.1.2",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@hookform/resolvers": "^2.8.8",
@@ -32,6 +33,7 @@
"react-router-dom": "^6.0.2",
"react-scripts": "5.0.0",
"react-window": "^1.8.9",
"reactflow": "^11.11.2",
"slate": "^0.94.1",
"slate-history": "^0.93.0",
"slate-react": "^0.94.2",

View File

@@ -36,7 +36,7 @@ function AdminApplicationSettings(props) {
const handleSubmit = async (values) => {
try {
if (!appConfig.data) {
if (!appConfig?.data) {
await createAppConfig({
variables: {
input: { key: props.appKey, ...values },
@@ -69,6 +69,7 @@ function AdminApplicationSettings(props) {
}),
[appConfig?.data],
);
return (
<Form
defaultValues={defaultValues}

View File

@@ -165,6 +165,7 @@ function ChooseAppAndEventSubstep(props) {
value={getOption(appOptions, step.appKey) || null}
onChange={onAppChange}
data-test="choose-app-autocomplete"
componentsProps={{ popper: { className: 'nowheel' } }}
/>
{step.appKey && (
@@ -227,6 +228,7 @@ function ChooseAppAndEventSubstep(props) {
value={getOption(actionOrTriggerOptions, step.key) || null}
onChange={onEventChange}
data-test="choose-event-autocomplete"
componentsProps={{ popper: { className: 'nowheel' } }}
/>
</Box>
)}

View File

@@ -240,6 +240,7 @@ function ChooseConnectionSubstep(props) {
onChange={handleChange}
loading={isAppConnectionsLoading}
data-test="choose-connection-autocomplete"
componentsProps={{ popper: { className: 'nowheel' } }}
/>
<Button

View File

@@ -32,9 +32,11 @@ function ControlledAutocomplete(props) {
...autocompleteProps
} = props;
let dependsOnValues = [];
if (dependsOn?.length) {
dependsOnValues = watch(dependsOn);
}
React.useEffect(() => {
const hasDependencies = dependsOnValues.length;
const allDepsSatisfied = dependsOnValues.every(Boolean);
@@ -44,6 +46,7 @@ function ControlledAutocomplete(props) {
resetField(name);
}
}, dependsOnValues);
return (
<Controller
rules={{ required }}

View File

@@ -47,6 +47,7 @@ const CustomOptions = (props) => {
},
},
]}
className="nowheel"
>
<Paper elevation={5} sx={{ width: '100%' }}>
<Tabs

View File

@@ -8,6 +8,7 @@ import Tooltip from '@mui/material/Tooltip';
import IconButton from '@mui/material/IconButton';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
import Snackbar from '@mui/material/Snackbar';
import { ReactFlowProvider } from 'reactflow';
import { EditorProvider } from 'contexts/Editor';
import EditableTypography from 'components/EditableTypography';
@@ -20,6 +21,9 @@ import * as URLS from 'config/urls';
import { TopBar } from './style';
import useFlow from 'hooks/useFlow';
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() {
const { flowId } = useParams();
@@ -131,15 +135,28 @@ export default function EditorLayout() {
</Button>
</Box>
</TopBar>
<Stack direction="column" height="100%">
<Container maxWidth="md">
<EditorProvider value={{ readOnly: !!flow?.active }}>
{!flow && !isFlowLoading && 'not found'}
{flow && <Editor flow={flow} />}
</EditorProvider>
</Container>
</Stack>
{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%">
<Container maxWidth="md">
<EditorProvider value={{ readOnly: !!flow?.active }}>
{!flow && !isFlowLoading && 'not found'}
{flow && <Editor flow={flow} />}
</EditorProvider>
</Container>
</Stack>
)}
<Snackbar
data-test="flow-cannot-edit-info-snackbar"

View File

@@ -0,0 +1,79 @@
import { EdgeLabelRenderer, getStraightPath } from 'reactflow';
import IconButton from '@mui/material/IconButton';
import AddIcon from '@mui/icons-material/Add';
import { useMutation } from '@apollo/client';
import { CREATE_STEP } from 'graphql/mutations/create-step';
import { useQueryClient } from '@tanstack/react-query';
import PropTypes from 'prop-types';
export default function Edge({
sourceX,
sourceY,
targetX,
targetY,
source,
data: { flowId, setCurrentStepId, flowActive, layouted },
}) {
const [createStep, { loading: creationInProgress }] =
useMutation(CREATE_STEP);
const queryClient = useQueryClient();
const [edgePath, labelX, labelY] = getStraightPath({
sourceX,
sourceY,
targetX,
targetY,
});
const addStep = async (previousStepId) => {
const mutationInput = {
previousStep: {
id: previousStepId,
},
flow: {
id: flowId,
},
};
const createdStep = await createStep({
variables: { input: mutationInput },
});
const createdStepId = createdStep.data.createStep.id;
setCurrentStepId(createdStepId);
await queryClient.invalidateQueries({ queryKey: ['flows', flowId] });
};
return (
<>
<EdgeLabelRenderer>
<IconButton
onClick={() => addStep(source)}
color="primary"
sx={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
pointerEvents: 'all',
visibility: layouted ? 'visible' : 'hidden',
}}
disabled={creationInProgress || 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({
flowId: PropTypes.string.isRequired,
setCurrentStepId: PropTypes.func.isRequired,
flowActive: PropTypes.bool.isRequired,
layouted: PropTypes.bool,
}).isRequired,
};

View File

@@ -0,0 +1,258 @@
import { useEffect, useState, useCallback } from 'react';
import { useMutation } from '@apollo/client';
import { useQueryClient } from '@tanstack/react-query';
import { FlowPropType } from 'propTypes/propTypes';
import ReactFlow, { useNodesState, useEdgesState, addEdge } from 'reactflow';
import 'reactflow/dist/style.css';
import { Stack } from '@mui/material';
import { UPDATE_STEP } from 'graphql/mutations/update-step';
import { useAutoLayout } from './useAutoLayout';
import { useScrollBoundries } from './useScrollBoundries';
import FlowStepNode from './FlowStepNode/FlowStepNode';
import Edge from './Edge/Edge';
import InvisibleNode from './InvisibleNode/InvisibleNode';
import { EditorWrapper } from './style';
const nodeTypes = { flowStep: FlowStepNode, invisible: InvisibleNode };
const edgeTypes = {
addNodeEdge: Edge,
};
const INVISIBLE_NODE_ID = 'invisible-node';
const generateEdgeId = (sourceId, targetId) => `${sourceId}-${targetId}`;
const EditorNew = ({ flow }) => {
const [triggerStep] = flow.steps;
const [currentStepId, setCurrentStepId] = useState(triggerStep.id);
const [updateStep] = useMutation(UPDATE_STEP);
const queryClient = useQueryClient();
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
useAutoLayout();
useScrollBoundries();
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges],
);
const openNextStep = useCallback(
(nextStep) => () => {
setCurrentStepId(nextStep?.id);
},
[],
);
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 generateEdges = useCallback((flow, prevEdges) => {
const newEdges =
flow.steps
.map((step, i) => {
const sourceId = step.id;
const targetId = flow.steps[i + 1]?.id;
const edge = prevEdges?.find(
(edge) => edge.id === generateEdgeId(sourceId, targetId),
);
if (targetId) {
return {
id: generateEdgeId(sourceId, targetId),
source: sourceId,
target: targetId,
type: 'addNodeEdge',
data: {
flowId: flow.id,
flowActive: flow.active,
setCurrentStepId,
layouted: !!edge,
},
};
}
})
.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: {
flowId: flow.id,
flowActive: flow.active,
setCurrentStepId,
layouted: false,
},
},
]
: newEdges;
}, []);
const generateNodes = useCallback(
(flow, prevNodes) => {
const newNodes = flow.steps.map((step, index) => {
const node = prevNodes?.find(({ id }) => id === step.id);
const collapsed = currentStepId !== step.id;
return {
id: step.id,
type: 'flowStep',
position: {
x: node ? node.position.x : 0,
y: node ? node.position.y : 0,
},
zIndex: collapsed ? 0 : 1,
data: {
step,
index: index,
flowId: flow.id,
collapsed,
openNextStep: openNextStep(flow.steps[index + 1]),
onOpen: () => setCurrentStepId(step.id),
onClose: () => setCurrentStepId(null),
onChange: onStepChange,
layouted: !!node,
},
};
});
const prevInvisibleNode = nodes.find((node) => node.type === 'invisible');
return [
...newNodes,
{
id: INVISIBLE_NODE_ID,
type: 'invisible',
position: {
x: prevInvisibleNode ? prevInvisibleNode.position.x : 0,
y: prevInvisibleNode ? prevInvisibleNode.position.y : 0,
},
},
];
},
[currentStepId, nodes, onStepChange, openNextStep],
);
const updateNodesData = useCallback(
(steps) => {
setNodes((nodes) =>
nodes.map((node) => {
const step = steps.find((step) => step.id === node.id);
if (step) {
return { ...node, data: { ...node.data, step: { ...step } } };
}
return node;
}),
);
},
[setNodes],
);
const updateEdgesData = useCallback(
(flow) => {
setEdges((edges) =>
edges.map((edge) => {
return {
...edge,
data: { ...edge.data, flowId: flow.id, flowActive: flow.active },
};
}),
);
},
[setEdges],
);
useEffect(() => {
setNodes(
nodes.map((node) => {
if (node.type === 'flowStep') {
const collapsed = currentStepId !== node.data.step.id;
return {
...node,
zIndex: collapsed ? 0 : 1,
data: {
...node.data,
collapsed,
},
};
}
return node;
}),
);
}, [currentStepId]);
useEffect(() => {
if (flow.steps.length + 1 !== nodes.length) {
const newNodes = generateNodes(flow, nodes);
const newEdges = generateEdges(flow, edges);
setNodes(newNodes);
setEdges(newEdges);
} else {
updateNodesData(flow.steps);
updateEdgesData(flow);
}
}, [flow]);
return (
<EditorWrapper direction="column">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
panOnScroll
panOnScrollMode="vertical"
panOnDrag={false}
zoomOnScroll={false}
zoomOnPinch={false}
zoomOnDoubleClick={false}
panActivationKeyCode={null}
/>
</EditorWrapper>
);
};
EditorNew.propTypes = {
flow: FlowPropType.isRequired,
};
export default EditorNew;

View File

@@ -0,0 +1,72 @@
import { Handle, Position } from 'reactflow';
import { Box } from '@mui/material';
import PropTypes from 'prop-types';
import FlowStep from 'components/FlowStep';
import { StepPropType } from 'propTypes/propTypes';
import { NodeWrapper, NodeInnerWrapper } from './style.js';
function FlowStepNode({
data: {
step,
index,
flowId,
collapsed,
openNextStep,
onOpen,
onClose,
onChange,
layouted,
},
}) {
return (
<NodeWrapper
className="nodrag"
sx={{
visibility: layouted ? 'visible' : 'hidden',
}}
>
<NodeInnerWrapper>
<Handle
type="target"
position={Position.Top}
isConnectable={false}
style={{ visibility: 'hidden' }}
/>
<FlowStep
step={step}
index={index + 1}
collapsed={collapsed}
onOpen={onOpen}
onClose={onClose}
onChange={onChange}
flowId={flowId}
onContinue={openNextStep}
/>
<Handle
type="source"
position={Position.Bottom}
isConnectable={false}
style={{ visibility: 'hidden' }}
/>
</NodeInnerWrapper>
</NodeWrapper>
);
}
FlowStepNode.propTypes = {
data: PropTypes.shape({
step: StepPropType.isRequired,
index: PropTypes.number.isRequired,
flowId: PropTypes.string.isRequired,
collapsed: PropTypes.bool.isRequired,
openNextStep: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
layouted: PropTypes.bool.isRequired,
}).isRequired,
};
export default FlowStepNode;

View File

@@ -0,0 +1,14 @@
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,
}));

View File

@@ -0,0 +1,19 @@
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;

View File

@@ -0,0 +1,13 @@
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',
},
}));

View File

@@ -0,0 +1,69 @@
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 getLayoutedElements = (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 layoutedElements = getLayoutedElements(nodes, edges);
setNodes([
...layoutedElements.nodes.map((node) => ({
...node,
data: { ...node.data, layouted: true },
})),
]);
setEdges([
...layoutedElements.edges.map((edge) => ({
...edge,
data: { ...edge.data, layouted: 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]);
};

View File

@@ -0,0 +1,13 @@
import { useEffect } from 'react';
import { useViewport, useReactFlow } from 'reactflow';
export const useScrollBoundries = () => {
const { setViewport } = useReactFlow();
const { x, y, zoom } = useViewport();
useEffect(() => {
if (y > 0) {
setViewport({ x, y: 0, zoom });
}
}, [y]);
};

View File

@@ -28,9 +28,12 @@ function ContextMenu(props) {
variables: { input: { id: flowId } },
});
await queryClient.invalidateQueries({
queryKey: ['apps', appKey, 'flows'],
});
if (appKey) {
await queryClient.invalidateQueries({
queryKey: ['apps', appKey, 'flows'],
});
}
enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), {
variant: 'success',
SnackbarProps: {
@@ -56,9 +59,12 @@ function ContextMenu(props) {
},
});
await queryClient.invalidateQueries({
queryKey: ['apps', appKey, 'flows'],
});
if (appKey) {
await queryClient.invalidateQueries({
queryKey: ['apps', appKey, 'flows'],
});
}
enqueueSnackbar(formatMessage('flow.successfullyDeleted'), {
variant: 'success',
});
@@ -110,7 +116,7 @@ ContextMenu.propTypes = {
]).isRequired,
onDeleteFlow: PropTypes.func,
onDuplicateFlow: PropTypes.func,
appKey: PropTypes.string.isRequired,
appKey: PropTypes.string,
};
export default ContextMenu;

View File

@@ -38,20 +38,24 @@ function FlowRow(props) {
const contextButtonRef = React.useRef(null);
const [anchorEl, setAnchorEl] = React.useState(null);
const { flow, onDuplicateFlow, onDeleteFlow, appKey } = props;
const handleClose = () => {
setAnchorEl(null);
};
const onContextMenuClick = (event) => {
event.preventDefault();
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
setAnchorEl(contextButtonRef.current);
};
const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10));
const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10));
const isUpdated = updatedAt > createdAt;
const relativeCreatedAt = createdAt.toRelative();
const relativeUpdatedAt = updatedAt.toRelative();
return (
<>
<Card sx={{ mb: 1 }} data-test="flow-row">
@@ -127,7 +131,7 @@ FlowRow.propTypes = {
flow: FlowPropType.isRequired,
onDeleteFlow: PropTypes.func,
onDuplicateFlow: PropTypes.func,
appKey: PropTypes.string.isRequired,
appKey: PropTypes.string,
};
export default FlowRow;

View File

@@ -80,6 +80,7 @@ export default function InputCreator(props) {
disabled={disabled}
showOptionValue={showOptionValue}
shouldUnregister={shouldUnregister}
componentsProps={{ popper: { className: 'nowheel' } }}
/>
)}

View File

@@ -17,6 +17,7 @@ import { StepExecutionsContext } from 'contexts/StepExecutions';
import Popper from './Popper';
import { processStepWithExecutions } from './data';
import { ChildrenWrapper, FakeInput, InputLabelWrapper } from './style';
const PowerInput = (props) => {
const { control } = useFormContext();
const {
@@ -31,33 +32,41 @@ const PowerInput = (props) => {
} = props;
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
const editorRef = React.useRef(null);
const renderElement = React.useCallback(
(props) => <Element {...props} />,
[],
);
const [editor] = React.useState(() => customizeEditor(createEditor()));
const [showVariableSuggestions, setShowVariableSuggestions] =
React.useState(false);
const disappearSuggestionsOnShift = (event) => {
if (event.code === 'Tab') {
setShowVariableSuggestions(false);
}
};
const stepsWithVariables = React.useMemo(() => {
return processStepWithExecutions(priorStepsWithExecutions);
}, [priorStepsWithExecutions]);
const handleBlur = React.useCallback(
(value) => {
onBlur?.(value);
},
[onBlur],
);
const handleVariableSuggestionClick = React.useCallback(
(variable) => {
insertVariable(editor, variable, stepsWithVariables);
},
[stepsWithVariables],
);
return (
<Controller
rules={{ required }}
@@ -127,6 +136,7 @@ const PowerInput = (props) => {
anchorEl={editorRef.current}
data={stepsWithVariables}
onSuggestionClick={handleVariableSuggestionClick}
className="nowheel"
/>
<FormHelperText variant="outlined">{description}</FormHelperText>

View File

@@ -0,0 +1,9 @@
import { useEffect, useRef } from "react";
export const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};

View File

@@ -3,7 +3,7 @@ services:
name: automatisch-main
env: docker
dockerfilePath: ./docker/Dockerfile
dockerContext: ./docker
dockerContext: .
repo: https://github.com/automatisch/automatisch
autoDeploy: false
envVars:
@@ -47,7 +47,7 @@ services:
name: automatisch-worker
env: docker
dockerfilePath: ./docker/Dockerfile
dockerContext: ./docker
dockerContext: .
repo: https://github.com/automatisch/automatisch
autoDeploy: false
envVars:

384
yarn.lock
View File

@@ -1455,6 +1455,18 @@
enabled "2.0.x"
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":
version "3.2.1"
resolved "https://registry.npmjs.org/@docsearch/css/-/css-3.2.1.tgz"
@@ -3333,6 +3345,72 @@
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz"
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":
version "5.3.0"
resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz"
@@ -3823,6 +3901,216 @@
dependencies:
"@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":
version "4.1.8"
resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz"
@@ -3913,6 +4201,11 @@
"@types/qs" "*"
"@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":
version "4.1.5"
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz"
@@ -6044,6 +6337,11 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
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:
version "5.2.2"
resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz"
@@ -6829,6 +7127,68 @@ csstype@^3.1.1:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz"
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:
version "1.0.8"
resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz"
@@ -13809,6 +14169,18 @@ react@^18.2.0:
dependencies:
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:
version "2.0.0"
resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz"
@@ -15977,6 +16349,11 @@ url-parse-lax@^3.0.0:
dependencies:
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:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -16948,3 +17325,10 @@ zen-observable@0.8.15:
version "0.8.15"
resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz"
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"