Compare commits

...

71 Commits

Author SHA1 Message Date
Faruk AYDIN
ff72b2ae35 Release v0.4.0 2023-01-13 17:29:30 +03:00
Ali BARIN
315edea5ca docs(twitter): add missing space after comma 2023-01-11 22:05:39 +01:00
Ali BARIN
27e0ef4e38 docs(todoist): describe auth with oauth 2.0 2023-01-11 22:05:39 +01:00
Ali BARIN
8090d8be5a feat(todoist): enhance auth with oauth 2.0 2023-01-11 22:05:39 +01:00
Ali BARIN
8c8fd98bd5 feat(rss): cover atom feeds 2023-01-11 20:04:02 +01:00
dependabot[bot]
6f724b5bec chore(deps): bump luxon from 2.5.0 to 2.5.2
Bumps [luxon](https://github.com/moment/luxon) from 2.5.0 to 2.5.2.
- [Release notes](https://github.com/moment/luxon/releases)
- [Changelog](https://github.com/moment/luxon/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moment/luxon/compare/2.5.0...2.5.2)

---
updated-dependencies:
- dependency-name: luxon
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 15:47:28 +01:00
Ali BARIN
909cbd1f0c fix(google-forms): platform name in OAuth field description 2023-01-09 14:09:33 +01:00
Ali BARIN
094de0ca21 fix(google-forms): remove unused and wrong docUrl in auth 2023-01-09 14:09:33 +01:00
dependabot[bot]
af8ca4af9e chore(deps): bump json5 from 1.0.1 to 1.0.2
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-07 16:23:57 +01:00
Ali BARIN
709e788f5f docs(todoist): restructure trigger and add in nav 2023-01-07 16:12:47 +01:00
Ian Levesque
1a4a1f7f8b feat(todoist): add app, authentication, docs (#826) 2023-01-07 15:09:39 +01:00
Ömer Faruk Aydın
ff0bde059a Merge pull request #825 from automatisch/google-forms-integration
Add google forms
2023-01-05 19:50:33 +03:00
Ali BARIN
ac671110a3 fix: wrap lines when showing error details 2023-01-03 21:24:29 +01:00
Ali BARIN
30c95da90e feat(google-forms): add new form responses trigger 2023-01-03 21:24:27 +01:00
Ali BARIN
0b52a1bd01 feat(google-forms): add list forms dynamic data 2023-01-03 21:23:35 +01:00
Ali BARIN
3fc7fce9ca feat(google-forms): add authentication 2023-01-03 19:06:04 +01:00
Ali BARIN
f8df7987b8 feat(google-forms): add primitive app 2023-01-03 19:02:56 +01:00
Ali BARIN
697594b63e Merge pull request #815 from automatisch/dependabot/npm_and_yarn/jsonwebtoken-9.0.0
chore(deps): bump jsonwebtoken from 8.5.1 to 9.0.0
2022-12-28 11:52:51 +01:00
dependabot[bot]
7be3923224 chore(deps): bump jsonwebtoken from 8.5.1 to 9.0.0
Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0.
- [Release notes](https://github.com/auth0/node-jsonwebtoken/releases)
- [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0)

---
updated-dependencies:
- dependency-name: jsonwebtoken
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-28 10:35:59 +00:00
Ömer Faruk Aydın
5b913aa30a Merge pull request #818 from automatisch/handle-different-content-types-in-webhook
feat(webhooks): handle form-data and text/plain reqs
2022-12-28 13:35:02 +03:00
Ali BARIN
da9cb7d6eb feat(webhooks): handle form-data and text/plain reqs 2022-12-25 16:28:58 +01:00
Ali BARIN
aca19f7f73 Merge pull request #814 from automatisch/docs/create-flow
docs: Add the guide for how to create flows
2022-12-22 23:21:07 +01:00
Faruk AYDIN
faf151cd62 docs: Add the guide for how to create flows 2022-12-23 01:02:35 +03:00
Ömer Faruk Aydın
675cd4de2a Merge pull request #813 from automatisch/docs/ngrok-usage
docs: Add usage of ngrok for webhook-based trigger development
2022-12-21 23:10:03 +03:00
Faruk AYDIN
74ebe492c5 docs: Add usage of ngrok for webhook-based trigger development 2022-12-21 23:07:40 +03:00
Ali BARIN
254fcecf33 Merge pull request #811 from automatisch/chore/dockerfile
chore: Cover different architectures in Dockerfile
2022-12-17 18:45:51 +01:00
Faruk AYDIN
a334864980 chore: Cover different architectures in Dockerfile 2022-12-17 20:40:44 +03:00
Ömer Faruk Aydın
b6b377c209 Merge pull request #809 from automatisch/docs/change-advantages-copy
docs: Update advantages section of Automatisch
2022-12-17 16:46:56 +03:00
Faruk AYDIN
5f76aa02dd docs: Adjust advantages text of Automatisch 2022-12-17 16:29:41 +03:00
Ömer Faruk Aydın
0ba0c998e9 Merge pull request #810 from automatisch/readme/installation
chore: Update readme to refer installation guide
2022-12-17 03:12:36 +03:00
Faruk AYDIN
2e54216469 chore: Update readme to refer installation guide 2022-12-17 02:05:09 +03:00
Faruk AYDIN
56dbe62cad docs: Update advantages section of Automatisch 2022-12-17 01:37:59 +03:00
Ömer Faruk Aydın
048db60a10 Merge pull request #808 from automatisch/docs/delay-app
docs: Add connection and actions pages for delay app
2022-12-17 01:09:35 +03:00
Faruk AYDIN
f3b1b2254f docs: Add connection and actions pages for delay app 2022-12-17 01:04:50 +03:00
Ali BARIN
e705aaa2f6 Merge pull request #807 from automatisch/docs/configuration
docs: Add WEB_APP_URL and WEBHOOK_URL to configuration
2022-12-16 23:02:17 +01:00
Faruk AYDIN
1f80f843ba docs: Add WEB_APP_URL and WEBHOOK_URL to configuration 2022-12-17 00:54:49 +03:00
Ömer Faruk Aydın
02a76418fc Merge pull request #806 from automatisch/add-flowers-software
Add flowers software
2022-12-17 00:33:06 +03:00
Ömer Faruk Aydın
d1245705de Merge pull request #805 from automatisch/revise-web-app-url
feat: prioritize WEB_APP_URL
2022-12-16 23:12:09 +03:00
Ali BARIN
6d74f7c64d feat(flowers-software): add new activity trigger 2022-12-16 21:06:54 +01:00
Ali BARIN
bf1076b7d2 fix(AddAppConnection): don't render empty error details 2022-12-16 21:06:53 +01:00
Ali BARIN
7175d92eaf feat(flowers-software): add app with authentication 2022-12-16 21:06:53 +01:00
Ali BARIN
f30ac46672 feat($): add flow.setRemoteWebhookId 2022-12-16 21:06:52 +01:00
Ali BARIN
0970db3295 feat: add remoteWebhookId in Flow 2022-12-16 21:06:52 +01:00
Ali BARIN
5d24216124 feat: prioritize WEB_APP_URL 2022-12-16 21:06:40 +01:00
Ali BARIN
9a55333ce8 Merge pull request #804 from automatisch/delay-until-action
feat: Add delay until action for delay app
2022-12-16 20:16:10 +01:00
Faruk AYDIN
cb58ab5fb7 feat: Add delay until action for delay app 2022-12-16 20:13:39 +01:00
Ali BARIN
5d9ed13003 Merge pull request #801 from automatisch/delay-app
feat: Add delay application along with delay for action
2022-12-16 20:12:06 +01:00
Ali BARIN
5b51cb30f5 refactor: use step.appKey to recognize delay 2022-12-16 19:54:55 +01:00
Ali BARIN
d8c5850eaa Merge pull request #793 from automatisch/issue-788
feat: take search bar out of scroll in add app connection
2022-12-16 16:36:26 +01:00
Rıdvan Akca
4839b100cb feat: take search bar out of scroll in add app connection 2022-12-16 16:33:46 +01:00
Faruk AYDIN
469050914b feat: Add delay application along with delay for action 2022-12-16 02:32:57 +03:00
Ali BARIN
8036d20bf9 Merge pull request #794 from automatisch/fix-broken-flow-editor
fix: make type optional for webhook check
2022-12-12 15:57:30 +01:00
Ali BARIN
f04597335f fix: make type optional for webhook check 2022-12-12 15:54:36 +01:00
Ali BARIN
3d26ca6cc2 Merge pull request #792 from automatisch/available-apps
docs: Add webhooks to available apps
2022-12-12 14:15:05 +01:00
Faruk AYDIN
ec3454291b docs: Add webhooks to available apps 2022-12-12 16:08:04 +03:00
Ali BARIN
21e2f394a9 Merge pull request #791 from automatisch/docs/render-deployment
docs: render deployment
2022-12-12 12:00:41 +01:00
Faruk AYDIN
dd9f69e4cd docs: Add installation type for render 2022-12-12 13:43:04 +03:00
Faruk AYDIN
f7e8cb24ee feat: Add render.yaml for easy deployment to Render 2022-12-12 12:29:36 +03:00
Ömer Faruk Aydın
3b5a77ec22 Merge pull request #790 from automatisch/docs/github-stars
docs: Add cta for github stars
2022-12-12 11:56:43 +03:00
Ali BARIN
16715129d1 docs: fix mobile nav for GH stars announcement 2022-12-11 18:14:21 +01:00
Faruk AYDIN
3aafaabda6 docs: Add cta for github stars 2022-12-11 15:55:16 +03:00
Ali BARIN
279abfedfd Merge pull request #789 from automatisch/issue-760
feat: show "instant" chip for webhook triggers
2022-12-10 16:46:55 +01:00
Ali BARIN
e77580dd35 feat: show "instant" chip for webhook triggers 2022-12-10 16:39:25 +01:00
Ali BARIN
9cde662092 feat(grapql-schema): add type property in Trigger 2022-12-10 16:39:24 +01:00
Ali BARIN
862734c572 Merge pull request #784 from automatisch/dependabot/npm_and_yarn/express-4.17.3
chore(deps): bump express from 4.16.4 to 4.17.3
2022-12-08 23:09:25 +01:00
Ali BARIN
61dc611588 Merge pull request #787 from automatisch/fix-generic-webhook-internal-id-for-get-reqs
fix(webhook): correct internal ID for GET requests
2022-12-08 23:09:06 +01:00
Ali BARIN
03368e2cca fix(webhook): correct internal ID for GET requests 2022-12-08 22:19:45 +01:00
Ali BARIN
6382325280 feat(TestSubstep): put some space between info and test output 2022-12-08 22:19:00 +01:00
dependabot[bot]
a5b0910b60 chore(deps): bump express from 4.16.4 to 4.17.3
Bumps [express](https://github.com/expressjs/express) from 4.16.4 to 4.17.3.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.16.4...4.17.3)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 19:19:13 +00:00
Ali BARIN
ba1e1ef20c Merge pull request #786 from automatisch/release/0.3.0
Release v0.3.0
2022-12-08 20:18:27 +01:00
Ali BARIN
dd8bab299d chore: update cli version in dockerfiles 2022-12-08 20:15:06 +01:00
106 changed files with 2471 additions and 392 deletions

1
.yarnrc Normal file
View File

@@ -0,0 +1 @@
network-timeout 400000

View File

@@ -10,7 +10,7 @@
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
The most significant advantage of having Automatisch is keeping your data on your own servers. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
One of the main benefits of using Automatisch is that it allows you to store your data on your own servers, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
@@ -35,6 +35,8 @@ docker compose up
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
For other installation types, you can check the [installation](https://automatisch.io/docs/guide/installation) guide.
## Community Links
- [Discord](https://discord.gg/dJSah9CVrC)

View File

@@ -2,9 +2,13 @@
FROM node:16-alpine
WORKDIR /automatisch
RUN apk --no-cache add --virtual build-dependencies python3 build-base
COPY ./entrypoint.sh /entrypoint.sh
RUN yarn global add @automatisch/cli@0.2.0
RUN yarn global add @automatisch/cli@0.3.0
RUN apk del build-dependencies python3 build-base
EXPOSE 3000
ENTRYPOINT ["sh", "/entrypoint.sh"]

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM automatischio/automatisch:0.2.0
FROM automatischio/automatisch:0.3.0
WORKDIR /automatisch
RUN apk add --no-cache openssl dos2unix

View File

@@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "0.3.0",
"version": "0.4.0",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/backend",
"version": "0.3.0",
"version": "0.4.0",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": {
@@ -22,7 +22,7 @@
"prebuild": "rm -rf ./dist"
},
"dependencies": {
"@automatisch/web": "^0.3.0",
"@automatisch/web": "^0.4.0",
"@bull-board/express": "^3.10.1",
"@graphql-tools/graphql-file-loader": "^7.3.4",
"@graphql-tools/load": "^7.5.2",
@@ -37,7 +37,7 @@
"crypto-js": "^4.1.1",
"debug": "~2.6.9",
"dotenv": "^10.0.0",
"express": "~4.16.1",
"express": "~4.18.2",
"express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0",
"fast-xml-parser": "^4.0.11",
@@ -46,11 +46,12 @@
"graphql-tools": "^8.2.0",
"graphql-type-json": "^0.3.2",
"http-errors": "~1.6.3",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.0",
"knex": "^0.95.11",
"lodash.get": "^4.4.2",
"luxon": "2.3.1",
"luxon": "2.5.2",
"morgan": "^1.10.0",
"multer": "1.4.5-lts.1",
"nodemailer": "6.7.0",
"oauth-1.0a": "^2.2.6",
"objection": "^3.0.0",
@@ -93,16 +94,17 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@automatisch/types": "^0.3.0",
"@automatisch/types": "^0.4.0",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",
"@types/crypto-js": "^4.0.2",
"@types/express": "^4.17.13",
"@types/express": "^4.17.15",
"@types/http-errors": "^1.8.1",
"@types/jsonwebtoken": "^8.5.8",
"@types/lodash.get": "^4.4.6",
"@types/morgan": "^1.9.3",
"@types/multer": "1.4.7",
"@types/node": "^16.10.2",
"@types/nodemailer": "^6.4.4",
"@types/pg": "^8.6.1",

View File

@@ -26,12 +26,17 @@ appAssetsHandler(app);
app.use(morgan);
app.use(
express.json({
verify: (req, res, buf) => {
verify(req, res, buf) {
(req as IRequest).rawBody = buf;
},
})
);
app.use(express.urlencoded({ extended: false }));
app.use(express.urlencoded({
extended: false,
verify(req, res, buf) {
(req as IRequest).rawBody = buf;
},
}));
app.use(cors(corsOptions));
app.use('/', router);

View File

@@ -0,0 +1,56 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Delay For',
key: 'delayFor',
description:
'Delays the execution of the next action by a specified amount of time.',
arguments: [
{
label: 'Delay for unit',
key: 'delayForUnit',
type: 'dropdown' as const,
required: true,
value: null,
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
variables: false,
options: [
{
label: 'Minutes',
value: 'minutes',
},
{
label: 'Hours',
value: 'hours',
},
{
label: 'Days',
value: 'days',
},
{
label: 'Weeks',
value: 'weeks',
},
],
},
{
label: 'Delay for value',
key: 'delayForValue',
type: 'string' as const,
required: true,
description: 'Delay for value, use a number, e.g. 1, 2, 3.',
variables: true,
},
],
async run($) {
const { delayForUnit, delayForValue } = $.step.parameters;
const dataItem = {
delayForUnit,
delayForValue,
};
$.setActionItem({ raw: dataItem });
},
});

View File

@@ -0,0 +1,28 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Delay Until',
key: 'delayUntil',
description:
'Delays the execution of the next action until a specified date.',
arguments: [
{
label: 'Delay until (Date)',
key: 'delayUntil',
type: 'string' as const,
required: true,
description: 'Delay until the date. E.g. 2022-12-18',
variables: true,
},
],
async run($) {
const { delayUntil } = $.step.parameters;
const dataItem = {
delayUntil,
};
$.setActionItem({ raw: dataItem });
},
});

View File

@@ -0,0 +1,4 @@
import delayFor from './delay-for';
import delayUntil from './delay-until';
export default [delayFor, delayUntil];

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
<g>
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

View File

@@ -0,0 +1,14 @@
import defineApp from '../../helpers/define-app';
import actions from './actions';
export default defineApp({
name: 'Delay',
key: 'delay',
iconUrl: '{BASE_URL}/apps/delay/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/delay/connection',
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '001F52',
actions,
});

View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="762.564" height="769.312" viewBox="0 0 772772" fill="none">
<g clip-path="url(#clip0_493_7535)">
<path d="M747.782 474.701C725.051 423.2 683.614 383.657 631.048 363.294L413.561 279.117L429.189 369.569L602.633 436.697C670.591 462.98 704.451 539.58 678.168 607.537C651.885 675.494 575.285 709.354 507.328 683.071C473.231 669.811 446.711 643.883 432.741 610.023C429.426 601.854 426.939 593.33 425.164 584.569L358.509 636.07C358.982 637.372 359.338 638.674 359.811 639.858C382.069 693.963 424.216 735.282 478.795 756.356C503.776 765.946 529.349 770.563 554.566 770.563C639.098 770.563 719.131 719.299 751.334 635.833C771.697 583.267 770.277 526.083 747.545 474.583L747.782 474.701Z" fill="#02AFC7"/>
<path d="M440.91 419.881L294.459 533.064C287.119 538.628 279.187 543.482 270.781 547.507C237.631 563.254 200.693 564.556 166.477 551.414C133.564 538.628 107.636 513.884 93.3109 481.563C78.9854 449.242 78.1567 413.369 90.943 380.456C103.729 347.543 128.473 321.615 160.794 307.29C190.866 294.03 224.134 292.491 255.271 302.672L241.064 219.206C147.061 207.13 53.2943 259.815 17.6583 352.042C-2.70517 404.608 -1.28446 461.792 21.4468 513.292C44.1781 564.793 85.6154 604.336 138.181 624.699C162.57 634.171 188.38 638.788 214.189 638.788C245.208 638.788 276.227 632.039 304.641 618.543C318.019 612.268 330.687 604.454 342.29 595.575L527.81 452.083L440.91 419.762V419.881Z" fill="#31DEA4"/>
<path d="M624.42 193.396C629.629 229.624 619.921 265.379 597.19 294.148C594.822 297.108 592.335 299.831 589.731 302.554L665.62 333.454C696.402 289.649 709.899 235.307 702.203 182.149C694.153 126.386 665.028 77.135 619.921 43.3933C574.932 9.76989 519.406 -4.43717 463.643 3.49511C407.88 11.5458 358.629 40.6703 324.888 85.7777C291.146 130.885 276.939 186.411 284.989 242.055L323.23 457.529L393.673 404.371L362.773 230.334C357.919 195.646 366.798 161.075 387.872 133.016C409.064 104.72 439.846 86.488 474.89 81.3972C509.935 76.4247 544.623 85.3041 572.801 106.378C601.096 127.57 619.329 158.47 624.42 193.396V193.396Z" fill="#FF5986"/>
</g>
<defs>
<clipPath id="clip0_493_7535">
<rect width="762.564" height="769.312" fill="white" transform="translate(3.33301 1.36719)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,43 @@
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'username',
label: 'Username',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'password',
label: 'Password',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable } from '@automatisch/types';
import verifyCredentials from './verify-credentials';
const isStillVerified = async ($: IGlobalVariable) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,20 @@
import { IGlobalVariable } from '@automatisch/types';
import getWebhooks from '../common/get-webhooks';
const verifyCredentials = async ($: IGlobalVariable) => {
const response = await getWebhooks($);
const successful = Array.isArray(response.data);
if (!successful) {
throw new Error('Failed while authorizing!');
}
await $.auth.set({
screenName: $.auth.data.username,
username: $.auth.data.username,
password: $.auth.data.password,
apiKey: $.auth.data.apiKey,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,18 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const { data } = $.auth;
if (data?.username && data.password && data.apiKey) {
requestConfig.headers['x-api-key'] = data.apiKey as string;
requestConfig.auth = {
username: data.username as string,
password: data.password as string,
};
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,5 @@
import type { IGlobalVariable } from "@automatisch/types";
export default async function getWebhooks($: IGlobalVariable) {
return await $.http.get('/v2/public/api/webhooks');
}

View File

@@ -0,0 +1,488 @@
const webhookFilters = [
{
label: "Contact Company Created",
value: "CONTACT_COMPANY_CREATED"
},
{
label: "Contact Company Deleted",
value: "CONTACT_COMPANY_DELETED"
},
{
label: "Contact Company Updated",
value: "CONTACT_COMPANY_UPDATED"
},
{
label: "Contact Created",
value: "CONTACT_CREATED"
},
{
label: "Contact Deleted",
value: "CONTACT_DELETED"
},
{
label: "Contact Updated",
value: "CONTACT_UPDATED"
},
{
label: "Customer Created",
value: "CUSTOMER_CREATED"
},
{
label: "Customer Updated",
value: "CUSTOMER_UPDATED"
},
{
label: "Document Deleted",
value: "DOCUMENT_DELETED"
},
{
label: "Document Downloaded",
value: "DOCUMENT_DOWNLOADED"
},
{
label: "Document Saved",
value: "DOCUMENT_SAVED"
},
{
label: "Document Updated",
value: "DOCUMENT_UPDATED"
},
{
label: "Flow Archived",
value: "FLOW_ARCHIVED"
},
{
label: "Flow Created",
value: "FLOW_CREATED"
},
{
label: "Flow Object Automation Action Created",
value: "FLOW_OBJECT_AUTOMATION_ACTION_CREATED"
},
{
label: "Flow Object Automation Action Deleted",
value: "FLOW_OBJECT_AUTOMATION_ACTION_DELETED"
},
{
label: "Flow Object Automation Created",
value: "FLOW_OBJECT_AUTOMATION_CREATED"
},
{
label: "Flow Object Automation Deleted",
value: "FLOW_OBJECT_AUTOMATION_DELETED"
},
{
label: "Flow Object Automation Updated",
value: "FLOW_OBJECT_AUTOMATION_UPDATED"
},
{
label: "Flow Object Automation Webdav Created",
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_CREATED"
},
{
label: "Flow Object Automation Webdav Deleted",
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_DELETED"
},
{
label: "Flow Object Automation Webdav Updated",
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_UPDATED"
},
{
label: "Flow Object Created",
value: "FLOW_OBJECT_CREATED"
},
{
label: "Flow Object Deleted",
value: "FLOW_OBJECT_DELETED"
},
{
label: "Flow Object Document Added",
value: "FLOW_OBJECT_DOCUMENT_ADDED"
},
{
label: "Flow Object Document Removed",
value: "FLOW_OBJECT_DOCUMENT_REMOVED"
},
{
label: "Flow Object Resource Created",
value: "FLOW_OBJECT_RESOURCE_CREATED"
},
{
label: "Flow Object Resource Deleted",
value: "FLOW_OBJECT_RESOURCE_DELETED"
},
{
label: "Flow Object Resource Updated",
value: "FLOW_OBJECT_RESOURCE_UPDATED"
},
{
label: "Flow Object Task Condition Created",
value: "FLOW_OBJECT_TASK_CONDITION_CREATED"
},
{
label: "Flow Object Task Condition Deleted",
value: "FLOW_OBJECT_TASK_CONDITION_DELETED"
},
{
label: "Flow Object Task Condition Updated",
value: "FLOW_OBJECT_TASK_CONDITION_UPDATED"
},
{
label: "Flow Object Task Created",
value: "FLOW_OBJECT_TASK_CREATED"
},
{
label: "Flow Object Task Deleted",
value: "FLOW_OBJECT_TASK_DELETED"
},
{
label: "Flow Object Task Updated",
value: "FLOW_OBJECT_TASK_UPDATED"
},
{
label: "Flow Object Updated",
value: "FLOW_OBJECT_UPDATED"
},
{
label: "Flow Objects Connection Created",
value: "FLOW_OBJECTS_CONNECTION_CREATED"
},
{
label: "Flow Objects Connection Deleted",
value: "FLOW_OBJECTS_CONNECTION_DELETED"
},
{
label: "Flow Objects Connection Updated",
value: "FLOW_OBJECTS_CONNECTION_UPDATED"
},
{
label: "Flow Objects External Connection Created",
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
},
{
label: "Flow Objects External Connection Deleted",
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_DELETED"
},
{
label: "Flow Objects External Connection Updated",
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_UPDATED"
},
{
label: "Flow Objects External Connections Group Created",
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_CREATED"
},
{
label: "Flow Objects External Connections Group Deleted",
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_DELETED"
},
{
label: "Flow Objects External Connections Group Updated",
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_UPDATED"
},
{
label: "Flow Unarchived",
value: "FLOW_UNARCHIVED"
},
{
label: "Flow Updated",
value: "FLOW_UPDATED"
},
{
label: "Note Created",
value: "NOTE_CREATED"
},
{
label: "Note Deleted",
value: "NOTE_DELETED"
},
{
label: "Note Updated",
value: "NOTE_UPDATED"
},
{
label: "Team Created",
value: "TEAM_CREATED"
},
{
label: "Team Deleted",
value: "TEAM_DELETED"
},
{
label: "Team Updated",
value: "TEAM_UPDATED"
},
{
label: "User Added To Team",
value: "USER_ADDED_TO_TEAM"
},
{
label: "User Added To Teamleads",
value: "USER_ADDED_TO_TEAMLEADS"
},
{
label: "User Archived",
value: "USER_ARCHIVED"
},
{
label: "User Changed Password",
value: "USER_CHANGED_PASSWORD"
},
{
label: "User Created",
value: "USER_CREATED"
},
{
label: "User Forgot Password",
value: "USER_FORGOT_PASSWORD"
},
{
label: "User Invited",
value: "USER_INVITED"
},
{
label: "User Logged In",
value: "USER_LOGGED_IN"
},
{
label: "User Notification Settings Changed",
value: "USER_NOTIFICATION_SETTINGS_CHANGED"
},
{
label: "User Profile Updated",
value: "USER_PROFILE_UPDATED"
},
{
label: "User Removed From Team",
value: "USER_REMOVED_FROM_TEAM"
},
{
label: "User Removed From Teamleads",
value: "USER_REMOVED_FROM_TEAMLEADS"
},
{
label: "User Unarchived",
value: "USER_UNARCHIVED"
},
{
label: "Workflow Archived",
value: "WORKFLOW_ARCHIVED"
},
{
label: "Workflow Completed",
value: "WORKFLOW_COMPLETED"
},
{
label: "Workflow Created",
value: "WORKFLOW_CREATED"
},
{
label: "Workflow Creation Failed",
value: "WORKFLOW_CREATION_FAILED"
},
{
label: "Workflow Object Automation Api Get Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_COMPLETED"
},
{
label: "Workflow Object Automation Api Get Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_FAILED"
},
{
label: "Workflow Object Automation Api Post Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_COMPLETED"
},
{
label: "Workflow Object Automation Api Post Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_FAILED"
},
{
label: "Workflow Object Automation Datev Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_COMPLETED"
},
{
label: "Workflow Object Automation Datev Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_FAILED"
},
{
label: "Workflow Object Automation Email Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_COMPLETED"
},
{
label: "Workflow Object Automation Email Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_FAILED"
},
{
label: "Workflow Object Automation Lexoffice Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_COMPLETED"
},
{
label: "Workflow Object Automation Lexoffice Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_FAILED"
},
{
label: "Workflow Object Automation Rejected",
value: "WORKFLOW_OBJECT_AUTOMATION_REJECTED"
},
{
label: "Workflow Object Automation Retried",
value: "WORKFLOW_OBJECT_AUTOMATION_RETRIED"
},
{
label: "Workflow Object Automation Sevdesk Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_COMPLETED"
},
{
label: "Workflow Object Automation Sevdesk Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_FAILED"
},
{
label: "Workflow Object Automation Stamp Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_COMPLETED"
},
{
label: "Workflow Object Automation Stamp Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_FAILED"
},
{
label: "Workflow Object Automation Task Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_COMPLETED"
},
{
label: "Workflow Object Automation Task Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_FAILED"
},
{
label: "Workflow Object Automation Template Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_COMPLETED"
},
{
label: "Workflow Object Automation Template Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_FAILED"
},
{
label: "Workflow Object Automation Webdav Document Uploaded",
value: "WORKFLOW_OBJECT_AUTOMATION_WEBDAV_DOCUMENT_UPLOADED"
},
{
label: "Workflow Object Automation Zapier Completed",
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_COMPLETED"
},
{
label: "Workflow Object Automation Zapier Failed",
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_FAILED"
},
{
label: "Workflow Object Combination Task Group Created",
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_CREATED"
},
{
label: "Workflow Object Combination Task Group Deleted",
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_DELETED"
},
{
label: "Workflow Object Completed Automations Finished",
value: "WORKFLOW_OBJECT_COMPLETED_AUTOMATIONS_FINISHED"
},
{
label: "Workflow Object Completed",
value: "WORKFLOW_OBJECT_COMPLETED"
},
{
label: "Workflow Object Created",
value: "WORKFLOW_OBJECT_CREATED"
},
{
label: "Workflow Object Document Added",
value: "WORKFLOW_OBJECT_DOCUMENT_ADDED"
},
{
label: "Workflow Object Document Lock Added",
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_ADDED"
},
{
label: "Workflow Object Document Lock Deleted",
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_DELETED"
},
{
label: "Workflow Object Document Removed",
value: "WORKFLOW_OBJECT_DOCUMENT_REMOVED"
},
{
label: "Workflow Object Email Added",
value: "WORKFLOW_OBJECT_EMAIL_ADDED"
},
{
label: "Workflow Object Email Removed",
value: "WORKFLOW_OBJECT_EMAIL_REMOVED"
},
{
label: "Workflow Object External User Created",
value: "WORKFLOW_OBJECT_EXTERNAL_USER_CREATED"
},
{
label: "Workflow Object External User Deleted",
value: "WORKFLOW_OBJECT_EXTERNAL_USER_DELETED"
},
{
label: "Workflow Object Note Added",
value: "WORKFLOW_OBJECT_NOTE_ADDED"
},
{
label: "Workflow Object Note Removed",
value: "WORKFLOW_OBJECT_NOTE_REMOVED"
},
{
label: "Workflow Object Resource Created",
value: "WORKFLOW_OBJECT_RESOURCE_CREATED"
},
{
label: "Workflow Object Snapshot Created",
value: "WORKFLOW_OBJECT_SNAPSHOT_CREATED"
},
{
label: "Workflow Object Task Condition Created",
value: "WORKFLOW_OBJECT_TASK_CONDITION_CREATED"
},
{
label: "Workflow Object Task Created",
value: "WORKFLOW_OBJECT_TASK_CREATED"
},
{
label: "Workflow Object Task Deleted",
value: "WORKFLOW_OBJECT_TASK_DELETED"
},
{
label: "Workflow Object Task Snapshot Created",
value: "WORKFLOW_OBJECT_TASK_SNAPSHOT_CREATED"
},
{
label: "Workflow Object Task Updated",
value: "WORKFLOW_OBJECT_TASK_UPDATED"
},
{
label: "Workflow Object Updated",
value: "WORKFLOW_OBJECT_UPDATED"
},
{
label: "Workflow Objects Connection Created",
value: "WORKFLOW_OBJECTS_CONNECTION_CREATED"
},
{
label: "Workflow Objects External Connection Created",
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
},
{
label: "Workflow Objects External Connection Group Created",
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_GROUP_CREATED"
},
{
label: "Workflow Unarchived",
value: "WORKFLOW_UNARCHIVED"
},
{
label: "Workflow Updated",
value: "WORKFLOW_UPDATED"
}
];
export default webhookFilters;

View File

View File

@@ -0,0 +1,18 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
export default defineApp({
name: 'Flowers Software',
key: 'flowers-software',
iconUrl: '{BASE_URL}/apps/flowers-software/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/flowers-software/connection',
supportsConnections: true,
baseUrl: 'https://flowers-software.com',
apiBaseUrl: 'https://webapp.flowers-software.com/api',
primaryColor: '02AFC7',
beforeRequest: [addAuthHeader],
auth,
triggers,
});

View File

@@ -0,0 +1,3 @@
import newActivity from './new-activity';
export default [newActivity];

View File

@@ -0,0 +1,54 @@
import isEmpty from 'lodash/isEmpty';
import defineTrigger from '../../../../helpers/define-trigger';
import webhookFilters from '../../common/webhook-filters';
export default defineTrigger({
name: 'New activity',
key: 'newActivity',
type: 'webhook',
description: 'Triggers when a new activity occurs.',
arguments: [
{
label: 'Activity type',
key: 'filters',
type: 'dropdown' as const,
required: true,
description: 'Pick an activity type to receive events for.',
variables: false,
options: webhookFilters,
},
],
async testRun($) {
if (!isEmpty($.lastExecutionStep?.dataOut)) {
$.pushTriggerItem({
raw: $.lastExecutionStep.dataOut,
meta: {
internalId: '',
}
});
}
},
async registerHook($) {
const payload = {
name: $.flow.id,
type: 'POST',
url: $.webhookUrl,
filters: [$.step.parameters.filters]
};
const { data } = await $.http.post(
`/v2/public/api/webhooks`,
payload
);
await $.flow.setRemoteWebhookId(data.id);
},
async unregisterHook($) {
await $.http.delete(
`/v2/public/api/webhooks/${$.flow.remoteWebhookId}`
);
},
});

View File

@@ -13,8 +13,7 @@ export default async function generateAuthUrl($: IGlobalVariable) {
scope: scopes.join(','),
});
const url = `${
$.app.baseUrl
const url = `${$.app.baseUrl
}/login/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({

View File

@@ -0,0 +1,41 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1904.8 2500" style="enable-background:new 0 0 1904.8 2500;" xml:space="preserve">
<style type="text/css">
.st0{fill:#673AB7;}
.st1{fill:#F1F1F1;}
.st2{fill:url(#SVGID_1_);}
.st3{fill:#B39DDB;}
.st4{fill:#FFFFFF;fill-opacity:0.2;}
.st5{fill:#311B92;fill-opacity:0.2;}
.st6{fill:#311B92;fill-opacity:0.1;}
.st7{fill:url(#SVGID_2_);}
</style>
<g>
<path class="st0" d="M1190.5,0H178.6C83.3,0,0,83.3,0,178.6v2142.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
c95.2,0,178.6-83.3,178.6-178.6V714.3l-416.7-297.6L1190.5,0z"/>
<path class="st1" d="M714.3,1845.2h714.3v-119H714.3V1845.2z M714.3,1071.4v119h714.3v-119H714.3z M607.1,1131
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1083.3,607.1,1131z M607.1,1464.3
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1416.7,607.1,1464.3z M607.1,1785.7
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1738.1,607.1,1785.7z M714.3,1547.6h714.3
v-119.1H714.3L714.3,1547.6L714.3,1547.6z"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="122.5479" y1="1635.2429" x2="122.5479" y2="1634.3186" gradientTransform="matrix(666.67 0 0 -654.7559 -80127.6016 1071403.75)">
<stop offset="0" style="stop-color:#311B92;stop-opacity:0.2"/>
<stop offset="1" style="stop-color:#311B92;stop-opacity:2.000000e-02"/>
</linearGradient>
<path class="st2" d="M1238.1,666.7l666.7,654.8V714.3L1238.1,666.7z"/>
<path class="st3" d="M1190.5,0v535.7c0,95.2,83.3,178.6,178.6,178.6h535.7L1190.5,0z"/>
<path class="st4" d="M178.6,0C83.3,0,0,83.3,0,178.6v11.9C0,95.2,83.3,11.9,178.6,11.9h1011.9V0H178.6L178.6,0z"/>
<path class="st5" d="M1726.2,2488.1H178.6C83.3,2488.1,0,2404.8,0,2309.5v11.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
c95.2,0,178.6-83.3,178.6-178.6v-11.9C1904.8,2404.8,1821.4,2488.1,1726.2,2488.1z"/>
<path class="st6" d="M1369,714.3c-95.2,0-178.6-83.3-178.6-178.6v11.9c0,95.2,83.3,178.6,178.6,178.6h535.7v-11.9L1369,714.3
L1369,714.3z"/>
<radialGradient id="SVGID_2_" cx="122.6324" cy="1634.4275" r="12.899" gradientTransform="matrix(1904.7655 0 0 -1904.75 -233525.7031 3113242.5)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.1"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</radialGradient>
<path class="st7" d="M1190.5,0H178.6C83.3,0,0,83.3,0,178.6v2142.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
c95.2,0,178.6-83.3,178.6-178.6V714.3L1190.5,0z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,24 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
redirect_uri: redirectUri,
prompt: 'select_account',
scope: authScope.join(' '),
response_type: 'code',
access_type: 'offline',
});
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

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

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
const currentUser = await getCurrentUser($);
return !!currentUser.resourceName;
};
export default isStillVerified;

View File

@@ -0,0 +1,26 @@
import { URLSearchParams } from 'node:url';
import { IGlobalVariable } from '@automatisch/types';
import authScope from '../common/auth-scope';
const refreshToken = async ($: IGlobalVariable) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken as string,
});
const { data } = await $.http.post(
'https://oauth2.googleapis.com/token',
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
scope: authScope.join(' '),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -0,0 +1,57 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
type TUser = {
displayName: string;
metadata: {
primary: boolean;
};
};
type TEmailAddress = {
value: string;
metadata: {
primary: boolean;
};
};
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
});
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
const { displayName } = currentUser.names.find(
(name: TUser) => name.metadata.primary
);
const { value: email } = currentUser.emailAddresses.find(
(emailAddress: TEmailAddress) => emailAddress.metadata.primary
);
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
idToken: data.id_token,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
resourceName: currentUser.resourceName,
screenName: `${displayName} - ${email}`,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,11 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if (requestConfig.headers && $.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,9 @@
const authScope: string[] = [
'https://www.googleapis.com/auth/forms.body.readonly',
'https://www.googleapis.com/auth/forms.responses.readonly',
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/userinfo.email',
'profile',
];
export default authScope;

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable) => {
const { data: currentUser } = await $.http.get(
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
);
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,3 @@
import listForms from './list-forms';
export default [listForms];

View File

@@ -0,0 +1,34 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List forms',
key: 'listForms',
async run($: IGlobalVariable) {
const forms: {
data: IJSONObject[];
} = {
data: [],
};
const params = {
q: `mimeType='application/vnd.google-apps.form'`,
spaces: 'drive',
pageToken: undefined as unknown as string,
};
do {
const { data } = await $.http.get(`https://www.googleapis.com/drive/v3/files`, { params });
params.pageToken = data.nextPageToken;
for (const file of data.files) {
forms.data.push({
value: file.id,
name: file.name,
});
}
} while (params.pageToken);
return forms;
},
};

View File

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'Google Forms',
key: 'google-forms',
baseUrl: 'https://docs.google.com/forms',
apiBaseUrl: 'https://forms.googleapis.com',
iconUrl: '{BASE_URL}/apps/google-forms/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/google-forms/connection',
primaryColor: '673AB7',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,3 @@
import newFormResponses from './new-form-responses';
export default [newFormResponses];

View File

@@ -0,0 +1,33 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newFormResponses from './new-form-responses';
export default defineTrigger({
name: 'New Form Responses',
key: 'newFormResponses',
pollInterval: 15,
description: 'Triggers when a new form response is submitted.',
arguments: [
{
label: 'Form',
key: 'formId',
type: 'dropdown' as const,
required: true,
description: 'Pick a form to receive form responses.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForms',
},
],
},
},
],
async run($) {
await newFormResponses($);
},
});

View File

@@ -0,0 +1,28 @@
import { IGlobalVariable } from '@automatisch/types';
const newResponses = async ($: IGlobalVariable) => {
const params = {
pageToken: undefined as unknown as string,
};
do {
const pathname = `/v1/forms/${$.step.parameters.formId}/responses`;
const { data } = await $.http.get(pathname, { params });
params.pageToken = data.nextPageToken;
if (data.responses?.length) {
for (const formResponse of data.responses) {
const dataItem = {
raw: formResponse,
meta: {
internalId: formResponse.responseId,
},
};
$.pushTriggerItem(dataItem);
}
}
} while (params.pageToken);
};
export default newResponses;

View File

@@ -1,16 +1,34 @@
import { IGlobalVariable } from '@automatisch/types';
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import { XMLParser } from 'fast-xml-parser';
import bcrypt from 'bcrypt';
const getInternalId = async (item: IJSONObject): Promise<string> => {
if (item.guid) {
return item.guid.toString();
} else if (item.id) {
return item.id.toString();
}
return await hashItem(JSON.stringify(item));
};
const hashItem = async (value: string) => {
return await bcrypt.hash(value, 1);
};
const newItemsInFeed = async ($: IGlobalVariable) => {
const { data } = await $.http.get($.step.parameters.feedUrl as string);
const parser = new XMLParser();
const parsedData = parser.parse(data);
for (const item of parsedData.rss.channel.item) {
// naive implementation to cover atom and rss feeds
const items = parsedData.rss?.channel?.item || parsedData.feed?.entry || [];
for (const item of items) {
const dataItem = {
raw: item,
meta: {
internalId: item.guid,
internalId: await getInternalId(item),
},
};

View File

@@ -0,0 +1,100 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Create Task',
key: 'createTask',
description: 'Creates a Task in Todoist',
arguments: [
{
label: 'Project ID',
key: 'projectId',
type: 'dropdown' as const,
required: false,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listProjects',
},
],
},
},
{
label: 'Section ID',
key: 'sectionId',
type: 'dropdown' as const,
required: false,
variables: false,
dependsOn: ['parameters.projectId'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSections',
},
{
name: 'parameters.projectId',
value: '{parameters.projectId}',
},
],
},
},
{
label: 'Labels',
key: 'labels',
type: 'string' as const,
required: false,
variables: true,
description:
'Labels to add to task (comma separated). Examples: "work" "work,imported"',
},
{
label: 'Content',
key: 'content',
type: 'string' as const,
required: true,
variables: true,
description:
'Task content, may be markdown. Example: "Foo"',
},
{
label: 'Description',
key: 'description',
type: 'string' as const,
required: false,
variables: true,
description:
'Task description, may be markdown. Example: "Foo"',
},
],
async run($) {
const requestPath = `/tasks`;
const {
projectId,
sectionId,
labels,
content,
description
} = $.step.parameters;
const labelsArray = (labels as string).split(',')
const payload = {
content,
description: description || null,
project_id: projectId || null,
labels: labelsArray || null,
section_id: sectionId || null,
}
const response = await $.http.post(requestPath, payload);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -0,0 +1,3 @@
import createTask from './create-task';
export default [createTask];

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
</path>
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
</path>
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
</path>
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,17 @@
import { IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($: IGlobalVariable) {
const scopes = ['data:read_write'];
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
scope: scopes.join(','),
});
const url = `${$.app.baseUrl
}/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,46 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Name your connection (only used for Automatisch UI).',
clickToCopy: false,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,8 @@
import { IGlobalVariable } from '@automatisch/types';
const isStillVerified = async ($: IGlobalVariable) => {
await $.http.get('/projects');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,19 @@
import { IGlobalVariable } from '@automatisch/types';
const verifyCredentials = async ($: IGlobalVariable) => {
const { data } = await $.http.post(
`${$.app.baseUrl}/oauth/access_token`,
{
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
},
);
await $.auth.set({
tokenType: data.token_type,
accessToken: data.access_token,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,13 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const authData = $.auth.data;
if (authData?.accessToken && authData?.tokenType) {
const authorizationHeader = `${authData.tokenType} ${authData.accessToken}`;
requestConfig.headers.Authorization = authorizationHeader;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,5 @@
import listProjects from './list-projects';
import listSections from './list-sections';
import listLabels from './list-labels';
export default [listProjects, listSections, listLabels];

View File

@@ -0,0 +1,19 @@
import { IGlobalVariable } from '@automatisch/types';
export default {
name: 'List labels',
key: 'listLabels',
async run($: IGlobalVariable) {
const response = await $.http.get('/labels');
response.data = response.data.map((label: { name: string }) => {
return {
value: label.name,
name: label.name,
};
});
return response;
},
};

View File

@@ -0,0 +1,19 @@
import { IGlobalVariable } from '@automatisch/types';
export default {
name: 'List projects',
key: 'listProjects',
async run($: IGlobalVariable) {
const response = await $.http.get('/projects');
response.data = response.data.map((project: { id: string, name: string }) => {
return {
value: project.id,
name: project.name,
};
});
return response;
},
};

View File

@@ -0,0 +1,23 @@
import { IGlobalVariable } from '@automatisch/types';
export default {
name: 'List sections',
key: 'listSections',
async run($: IGlobalVariable) {
const params = {
project_id: ($.step.parameters.projectId as string),
};
const response = await $.http.get('/sections', {params});
response.data = response.data.map((section: { id: string, name: string }) => {
return {
value: section.id,
name: section.name,
};
});
return response;
},
};

View File

View File

@@ -0,0 +1,22 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import actions from './actions';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'Todoist',
key: 'todoist',
iconUrl: '{BASE_URL}/apps/todoist/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/todoist/connection',
supportsConnections: true,
baseUrl: 'https://todoist.com',
apiBaseUrl: 'https://api.todoist.com/rest/v2',
primaryColor: 'e44332',
beforeRequest: [addAuthHeader],
auth,
triggers,
actions,
dynamicData,
});

View File

@@ -0,0 +1,30 @@
import { IGlobalVariable } from '@automatisch/types';
const getActiveTasks = async ($: IGlobalVariable) => {
const params = {
project_id: ($.step.parameters.projectId as string)?.trim(),
section_id: ($.step.parameters.sectionId as string)?.trim(),
label: ($.step.parameters.label as string)?.trim(),
filter: ($.step.parameters.filter as string)?.trim(),
};
const response = await $.http.get('/tasks', { params });
// todoist api doesn't offer sorting, so we inverse sort on id here
response.data.sort((a: { id: number; }, b: { id: number; }) => {
return b.id - a.id;
})
for (const task of response.data) {
$.pushTriggerItem({
raw: task,
meta:{
internalId: task.id as string,
}
});
}
};
export default getActiveTasks;

View File

@@ -0,0 +1,80 @@
import defineTrigger from '../../../../helpers/define-trigger';
import getActiveTasks from './get-tasks';
export default defineTrigger({
name: 'Get Active Tasks',
key: 'getActiveTasks',
pollInterval: 15,
description: 'Triggers when new Task(s) are found',
arguments: [
{
label: 'Project ID',
key: 'projectId',
type: 'dropdown' as const,
required: false,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listProjects',
},
],
},
},
{
label: 'Section ID',
key: 'sectionId',
type: 'dropdown' as const,
required: false,
variables: false,
dependsOn: ['parameters.projectId'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSections',
},
{
name: 'parameters.projectId',
value: '{parameters.projectId}',
},
],
},
},
{
label: 'Label',
key: 'label',
type: 'dropdown' as const,
required: false,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLabels',
},
],
},
},
{
label: 'Filter',
key: 'filter',
type: 'string' as const,
required: false,
variables: false,
description:
'Limit queried tasks to this filter. Example: "Meeting & today"',
},
],
async run($) {
await getActiveTasks($);
},
});

View File

@@ -0,0 +1,3 @@
import getTasks from './get-tasks';
export default [getTasks];

View File

@@ -1,3 +1,4 @@
import { URL } from 'node:url';
import * as dotenv from 'dotenv';
dotenv.config();
@@ -38,14 +39,23 @@ const port = process.env.PORT || '3000';
const serveWebAppSeparately =
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let webAppUrl = `${protocol}://${host}:${port}`;
const webhookUrl = process.env.WEBHOOK_URL || webAppUrl;
let apiUrl = (new URL(`${protocol}://${host}:${port}`)).toString();
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
if (serveWebAppSeparately) {
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
// use apiUrl by default, which has less priority over the following cases
let webAppUrl = apiUrl;
if (process.env.WEB_APP_URL) {
// use env. var. if provided
webAppUrl = (new URL(process.env.WEB_APP_URL)).toString();
webAppUrl = webAppUrl.substring(0, webAppUrl.length - 1);
} else if (serveWebAppSeparately) {
// no env. var. and serving separately, sign of development
webAppUrl = 'http://localhost:3001'
}
const baseUrl = `${protocol}://${host}:${port}`;
let webhookUrl = (new URL(process.env.WEBHOOK_URL || apiUrl)).toString();
webhookUrl = webhookUrl.substring(0, webhookUrl.length - 1);
const appEnv = process.env.APP_ENV || 'development';
@@ -75,7 +85,7 @@ const appConfig: AppConfig = {
enableBullMQDashboard: process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME,
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
baseUrl,
baseUrl: apiUrl,
webAppUrl,
webhookUrl,
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,

View File

@@ -45,6 +45,7 @@ export default async (request: IRequest, response: Response) => {
// in case trigger type is 'webhook'
let payload = request.body;
let rawInternalId: string | Buffer = request.rawBody;
// in case it's our built-in generic webhook trigger
if (isWebhookApp) {
@@ -53,12 +54,14 @@ export default async (request: IRequest, response: Response) => {
body: request.body,
query: request.query,
}
rawInternalId = JSON.stringify(payload);
}
const triggerItem: ITriggerItem = {
raw: payload,
meta: {
internalId: await bcrypt.hash(request.rawBody, 1),
internalId: await bcrypt.hash(rawInternalId, 1),
},
};

View File

@@ -0,0 +1,13 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.table('flows', (table) => {
table.string('remote_webhook_id');
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.table('flows', (table) => {
table.dropColumn('remote_webhook_id');
});
}

View File

@@ -381,6 +381,7 @@ type Trigger {
key: String
description: String
pollInterval: Int
type: String
substeps: [TriggerSubstep]
}

View File

@@ -0,0 +1,25 @@
import Step from '../models/step';
import delayForAsMilliseconds, {
TDelayForUnit,
} from './delay-for-as-milliseconds';
import delayUntilAsMilliseconds from './delay-until-as-milliseconds';
const delayAsMilliseconds = (step: Step) => {
let delayDuration = 0;
if (step.key === 'delayFor') {
const { delayForUnit, delayForValue } = step.parameters;
delayDuration = delayForAsMilliseconds(
delayForUnit as TDelayForUnit,
Number(delayForValue)
);
} else if (step.key === 'delayUntil') {
const { delayUntil } = step.parameters;
delayDuration = delayUntilAsMilliseconds(delayUntil as string);
}
return delayDuration;
};
export default delayAsMilliseconds;

View File

@@ -0,0 +1,21 @@
export type TDelayForUnit = 'minutes' | 'hours' | 'days' | 'weeks';
const delayAsMilliseconds = (
delayForUnit: TDelayForUnit,
delayForValue: number
) => {
switch (delayForUnit) {
case 'minutes':
return delayForValue * 60 * 1000;
case 'hours':
return delayForValue * 60 * 60 * 1000;
case 'days':
return delayForValue * 24 * 60 * 60 * 1000;
case 'weeks':
return delayForValue * 7 * 24 * 60 * 60 * 1000;
default:
return 0;
}
};
export default delayAsMilliseconds;

View File

@@ -0,0 +1,8 @@
const delayUntilAsMilliseconds = (delayUntil: string) => {
const delayUntilDate = new Date(delayUntil);
const now = new Date();
return delayUntilDate.getTime() - now.getTime();
};
export default delayUntilAsMilliseconds;

View File

@@ -37,6 +37,7 @@ const globalVariable = async (
testRun = false,
} = options;
const isTrigger = step?.isTrigger;
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
const nextStep = await step?.getNextStep();
@@ -123,6 +124,18 @@ const globalVariable = async (
$.webhookUrl = webhookUrl;
}
if (isTrigger && (await step.getTriggerCommand()).type === 'webhook') {
$.flow.setRemoteWebhookId = async (remoteWebhookId) => {
await flow.$query().patchAndFetch({
remoteWebhookId,
});
$.flow.remoteWebhookId = remoteWebhookId;
};
$.flow.remoteWebhookId = flow.remoteWebhookId;
}
const lastInternalIds =
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);

View File

@@ -13,6 +13,7 @@ class Flow extends Base {
active: boolean;
steps: Step[];
published_at: string;
remoteWebhookId: string;
executions?: Execution[];
static tableName = 'flows';
@@ -25,6 +26,7 @@ class Flow extends Base {
id: { type: 'string', format: 'uuid' },
name: { type: 'string', minLength: 1 },
userId: { type: 'string', format: 'uuid' },
remoteWebhookId: { type: 'string' },
active: { type: 'boolean' },
},
};

View File

@@ -1,7 +1,18 @@
import { Router } from 'express';
import express, { Router } from 'express';
import multer from 'multer';
import { IRequest } from '@automatisch/types';
import webhookHandler from '../controllers/webhooks/handler';
const router = Router();
const upload = multer();
router.use(upload.none());
router.use(express.text({
verify(req, res, buf) {
(req as IRequest).rawBody = buf;
},
}));
router.get('/:flowId', webhookHandler);
router.put('/:flowId', webhookHandler);

View File

@@ -4,7 +4,11 @@ import logger from '../helpers/logger';
import Step from '../models/step';
import actionQueue from '../queues/action';
import { processAction } from '../services/action';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../helpers/remove-job-configuration';
import {
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from '../helpers/remove-job-configuration';
import delayAsMilliseconds from '../helpers/delay-as-milliseconds';
type JobData = {
flowId: string;
@@ -12,6 +16,8 @@ type JobData = {
stepId: string;
};
const DEFAULT_DELAY_DURATION = 0;
export const worker = new Worker(
'action',
async (job) => {
@@ -35,6 +41,11 @@ export const worker = new Worker(
const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
delay: DEFAULT_DELAY_DURATION,
};
if (step.appKey === 'delay') {
jobOptions.delay = delayAsMilliseconds(step);
}
await actionQueue.add(jobName, jobPayload, jobOptions);

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/cli",
"version": "0.3.0",
"version": "0.4.0",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"contributors": [
@@ -33,7 +33,7 @@
"version": "oclif readme && git add README.md"
},
"dependencies": {
"@automatisch/backend": "^0.3.0",
"@automatisch/backend": "^0.4.0",
"@oclif/core": "^1",
"@oclif/plugin-help": "^5",
"@oclif/plugin-plugins": "^2.0.1",

View File

@@ -10171,21 +10171,14 @@ json-stringify-safe@^5.0.1:
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
json5@1.0.2, json5@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
dependencies:
minimist "^1.2.0"
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
json5@^2.2.0, json5@^2.2.1:
json5@^2.1.2, json5@^2.2.0, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
@@ -10891,9 +10884,9 @@ minimatch@^5.0.1:
brace-expansion "^2.0.1"
minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
minipass-collect@^1.0.2:
version "1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/docs",
"version": "0.3.0",
"version": "0.4.0",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"private": true,

View File

@@ -41,6 +41,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/deepl/connection' },
],
},
{
text: 'Delay',
collapsible: true,
collapsed: true,
items: [
{ text: 'Actions', link: '/apps/delay/actions' },
{ text: 'Connection', link: '/apps/delay/connection' },
],
},
{
text: 'Discord',
collapsible: true,
@@ -141,6 +150,16 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/telegram-bot/connection' },
],
},
{
text: 'Todoist',
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/todoist/triggers' },
{ text: 'Actions', link: '/apps/todoist/actions' },
{ text: 'Connection', link: '/apps/todoist/connection' },
],
},
{
text: 'Twilio',
collapsible: true,

View File

@@ -0,0 +1,51 @@
<style scoped>
.announcement-bar {
background: var(--vp-c-brand-dark);
color: #fff;
border-bottom: 2px solid rgba(194, 194, 194, 0.2);
text-align: center;
justify-content: center;
align-items: center;
display: flex;
position: fixed;
padding: 4px 40px;
z-index: 999999;
font-size: 0.875rem;
font-weight: 400;
width: 100%;
height: 50px;
}
@media only screen and (max-width: 600px) {
.announcement-bar {
font-size: 0.75rem;
}
}
</style>
<script setup>
import DefaultTheme from 'vitepress/theme';
const { Layout } = DefaultTheme;
</script>
<template>
<Layout>
<template #layout-top>
<div class="announcement-bar">
<span
> If you like what we are doing with Automatisch, please give us a
star on
<strong
><a
href="https://github.com/automatisch/automatisch"
target="_blank"
rel="noreferrer"
>GitHub</a
></strong
>.</span
>
</div>
</template>
</Layout>
</template>

View File

@@ -117,9 +117,33 @@
:root {
overflow-y: scroll;
--announcement-bar-height: 50px;
}
.VPTeamMembersItem .avatar-img {
top: 50%;
transform: translateY(-50%);
}
header.VPNav {
margin-top: 50px;
}
.VPNavScreen.VPNavScreen {
top: calc(var(--announcement-bar-height) + var(--vp-nav-height-mobile));
}
.VPLocalNav.VPLocalNav {
top: 50px;
}
aside.VPSidebar {
margin-top: 50px;
}
@media (min-width: 960px) {
#VPContent {
margin-top: 50px;
}
}

View File

@@ -1,3 +1,8 @@
import DefaultTheme from 'vitepress/theme';
import './custom.css';
export default DefaultTheme;
import CustomLayout from './CustomLayout.vue';
export default {
...DefaultTheme,
Layout: CustomLayout,
};

View File

@@ -15,11 +15,13 @@ Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment
:::
| Variable Name | Type | Default Value | Description |
| --------------------------- | ------- | ------------------ | --------------------------------------------- |
| --------------------------- | ------- | ------------------ | ---------------------------------------------------- |
| `HOST` | string | `localhost` | HTTP Host |
| `PROTOCOL` | string | `http` | HTTP Protocol |
| `PORT` | string | `3000` | HTTP Port |
| `APP_ENV` | string | `production` | Automatisch Environment |
| `WEB_APP_URL` | string | | Can be used to override connection URLs and CORS URL |
| `WEBHOOK_URL` | string | | Can be used to override webhook URL |
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
| `POSTGRES_PORT` | number | `5432` | Database Port |
| `POSTGRES_HOST` | string | `postgres` | Database Host |
@@ -30,8 +32,8 @@ Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
| `REDIS_HOST` | string | `redis` | Redis Host |
| `REDIS_PORT` | number | `6379` | Redis Port |
| `REDIS_USERNAME` | string | `` | Redis Username |
| `REDIS_PASSWORD` | string | `` | Redis Password |
| `REDIS_USERNAME` | string | | Redis Username |
| `REDIS_PASSWORD` | string | | Redis Password |
| `REDIS_TLS` | boolean | `false` | Redis TLS |
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |

View File

@@ -0,0 +1,14 @@
---
favicon: /favicons/delay.svg
items:
- name: Delay For
desc: Delays the execution of the next action by a specified amount of time.
- name: Delay Until
desc: Delays the execution of the next action until a specified date.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -0,0 +1,3 @@
# Delay
Delay is a built-in app shipped with Automatisch, and it doesn't need to talk with any other external service to run. So there are no additional steps to use the Delay app. It can be used only as an action and it delays the execution of the next action by a specified amount of time.

View File

@@ -0,0 +1,12 @@
---
favicon: /favicons/todoist.svg
items:
- name: Create Task
desc: Creates a task in Todoist.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -0,0 +1,14 @@
# Todoist
:::info
This page explains the steps you need to follow to set up the Todoist connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Go to the account [App Management page](https://developer.todoist.com/appconsole.html) to register a **new OAuth application** on Todoist.
1. Fill **App name** and **App service URL**.
1. Copy **OAuth Redirect URL** from Automatisch to **OAuth redirect URL** field on Todoist page.
1. Click on the **Save settings** button on the Todoist page.
1. Copy the **Client ID** and **Client secret** values from the Todoist page to the corresponding fields on Automatisch.
1. Enter a memorable name for your connection in the **Screen Name** field.
1. Click the **Submit** button on Automatisch.
1. Congrats! Start using your new Todoist connection within the flows.

View File

@@ -0,0 +1,12 @@
---
favicon: /favicons/todoist.svg
items:
- name: Get Tasks
desc: Finds tasks in Todoist, optionally matching specified parameters.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -4,7 +4,7 @@
This page explains the steps you need to follow to set up the Twitter connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Go to the [Twitter Developer Portal](https://developer.twitter.com/en/portal/projects-and-apps),complete the questionnaire and click the **Let's do this** button.
1. Go to the [Twitter Developer Portal](https://developer.twitter.com/en/portal/projects-and-apps), complete the questionnaire and click the **Let's do this** button.
2. Accept terms & conditions on the following page and click **Submit**.
:::warning

View File

@@ -49,6 +49,10 @@ The build integrations section is best understood when read from beginning to en
### Webhook-based triggers
:::warning
If you are developing a webhook-based trigger, you need to ensure that the webhook is publicly accessible. You can use [ngrok](https://ngrok.com) for this purpose and override the webhook URL by setting the **WEBHOOK_URL** environment variable.
:::
- [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.ts)
### Pagination with descending order

View File

@@ -7,6 +7,7 @@ We just have a few available integrations at the moment and we also know that wo
Following integrations are currently supported by Automatisch.
- [DeepL](/apps/deepl/actions)
- [Delay](/apps/delay/actions)
- [Discord](/apps/discord/actions)
- [Flickr](/apps/flickr/triggers)
- [Github](/apps/github/triggers)
@@ -18,6 +19,8 @@ Following integrations are currently supported by Automatisch.
- [SMTP](/apps/smtp/actions)
- [Stripe](/apps/stripe/triggers)
- [Telegram](/apps/telegram-bot/actions)
- [Todoist](/apps/todoist/triggers)
- [Twilio](/apps/twilio/triggers)
- [Twitter](/apps/twitter/triggers)
- [Typeform](/apps/typeform/triggers)
- [Webhooks](/apps/webhooks/triggers)

View File

@@ -1 +1,53 @@
TBD
# Create Flow
To understand how we can create a flow, it's better to start with a real use case. Let's say we want to create a flow that will fetch new submissions from Typeform and then send them to a Slack channel. To do that, we will use [Typeform](/apps/typeform/triggers) and [Slack](/apps/slack/actions) apps. Let's start with creating connections for these apps.
## Typeform connection
- Go to the **My Apps** page in Automatisch and click on **Add connection** button.
- Select the **Typeform** app from the list.
- It will ask you `Client ID` and `Client Secret` from Typeform and there is an information box above the fields.
- Click on **our documentation** link in the information box and follow the instructions to get the `Client ID` and `Client Secret` from Typeform.
:::tip
Whenever you want to create a connection for an app, you can click on **our documentation** link in the information box to learn how to create a connection for that specific app.
:::
- After you get the `Client ID` and `Client Secret` from Typeform, you can paste them to the fields in Automatisch and click on **Submit** button.
## Slack connection
- Go to the **My Apps** page in Automatisch and click on **Add connection** button.
- Select the **Slack** app from the list.
- It will ask you `API Key` and `API Secret` values from Slack and there is an information box above the fields.
- Click on **our documentation** link in the information box and follow the instructions to get the `API Key` and `API Secret` from Slack.
- After you get the `API Key` and `API Secret` from Slack, you can paste them into the fields in Automatisch and click on **Submit** button.
## Build the flow
### Trigger step
- Go to the **Flows** page in Automatisch and click on **Create flow** button.
- It will give you empty trigger and action steps.
- For the trigger step (1st step), select the **Typeform** app from `Choose an app` dropdown.
- Select the **New entry** as the trigger event and click on the **Continue** button.
- It will ask you to select the connection you created for the Typeform app. Select the connection you have just created and click on the **Continue** button.
- Select the form you want to get the new entries from and click on the **Continue** button.
- Click on **Test & Continue** button to test the trigger step. If you see the data that reflects the recent submission in the form, you can continue to the next (action) step.
### Action step
- For the action step (2nd step), select the **Slack** app from `Choose an app` dropdown.
- Select the **Send a message to channel** as the action event and click on the **Continue** button.
- It will ask you to select the connection you created for the Slack app. Select the connection you have just created and click on the **Continue** button.
- Select the channel you want to send the message to.
- Write the message you want to send to the channel. You can use variables in the message from the trigger step.
- Select `Yes` for the `Send as a bot` option.
- Give a name for the bot and click on the **Continue** button.
- Click on **Test & Continue** button to test the action step. If you see the message in the Slack channel you selected, we can say that the flow is working as expected and is ready to be published.
### Publish the flow
- Click on the **Publish** button to publish the flow.
- Published flows will be executed automatically when the trigger event happens or at intervals of 15 minutes depending on the trigger type.
- You can not change the flow after it's published. If you want to change the flow, you need to unpublish it first and then make the changes.

View File

@@ -74,6 +74,18 @@ REDIS_TLS=
:::
## Render
<a href="https://render.com/deploy?repo=https://github.com/automatisch/automatisch">
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render">
</a>
:::info
We use default values of render plans with the `render.yaml` file, if you want to use the free plan or change the plan, you can change the `render.yaml` file in your fork and use your repository URL while creating a blueprint in Render.
:::
## Production setup
If you need to change any other environment variables for your production setup, let's check out the [environment variables](/advanced/configuration#environment-variables) section of the configuration page.

View File

@@ -28,7 +28,7 @@ You need to prepare the workflow once, and it will run continuously until you st
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
The most significant advantage of having Automatisch is **keeping your data on your own servers**. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
One of the main benefits of using Automatisch is that it allows you to **store your data on your own servers**, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
🤓 Your contributions are vital to the development of Automatisch. As an **open-source software**, anyone can have an impact on how it is being developed.

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
<g>
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
</path>
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
</path>
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
</path>
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/e2e-tests",
"version": "0.3.0",
"version": "0.4.0",
"license": "AGPL-3.0",
"private": true,
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",

View File

@@ -76,6 +76,7 @@ export interface IFlow {
steps: IStep[];
createdAt: string;
updatedAt: string;
remoteWebhookId: string;
lastInternalId: () => Promise<string>;
}
@@ -279,6 +280,8 @@ export type IGlobalVariable = {
id: string;
lastInternalId: string;
isAlreadyProcessed?: (internalId: string) => boolean;
remoteWebhookId?: string;
setRemoteWebhookId?: (remoteWebhookId: string) => Promise<void>;
};
step?: {
id: string;

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/types",
"version": "0.3.0",
"version": "0.4.0",
"license": "AGPL-3.0",
"description": "Type definitions for automatisch",
"homepage": "https://github.com/automatisch/automatisch",

View File

@@ -1,11 +1,11 @@
{
"name": "@automatisch/web",
"version": "0.3.0",
"version": "0.4.0",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"dependencies": {
"@apollo/client": "^3.6.9",
"@automatisch/types": "^0.3.0",
"@automatisch/types": "^0.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@hookform/resolvers": "^2.8.8",

View File

@@ -116,7 +116,11 @@ export default function AddAppConnection(
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
>
{error.message}
<pre>{JSON.stringify(error.details, null, 2)}</pre>
{error.details && (
<pre style={{ whiteSpace: 'pre-wrap' }}>
{JSON.stringify(error.details, null, 2)}
</pre>
)}
</Alert>
)}

View File

@@ -18,6 +18,7 @@ import ListItemText from '@mui/material/ListItemText';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';
import FormControl from '@mui/material/FormControl';
import Box from '@mui/material/Box';
import type { IApp } from '@automatisch/types';
import * as URLS from 'config/urls';
@@ -76,7 +77,7 @@ export default function AddNewAppConnection(
<Dialog open={true} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
<DialogContent>
<Box px={3}>
<FormControl
variant="outlined"
fullWidth
@@ -103,7 +104,9 @@ export default function AddNewAppConnection(
data-test="search-for-app-text-field"
/>
</FormControl>
</Box>
<DialogContent>
<List sx={{ pt: 2, width: '100%' }}>
{loading && (
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />

View File

@@ -7,6 +7,7 @@ import Collapse from '@mui/material/Collapse';
import ListItem from '@mui/material/ListItem';
import TextField from '@mui/material/TextField';
import Autocomplete from '@mui/material/Autocomplete';
import Chip from '@mui/material/Chip';
import useFormatMessage from 'hooks/useFormatMessage';
import { EditorContext } from 'contexts/Editor';
@@ -38,8 +39,20 @@ const optionGenerator = (app: {
value: app.key as string,
});
const getOption = (options: Record<string, unknown>[], appKey?: IApp['key']) =>
options.find((option) => option.value === (appKey as string)) || null;
const eventOptionGenerator = (app: {
name: string;
key: string;
type?: string;
}): { label: string; value: string; type: string } => ({
label: app.name as string,
value: app.key as string,
type: app?.type as string,
});
const getOption = <T extends { value: string }>(
options: T[],
selectedOptionValue?: string
) => options.find((option) => option.value === selectedOptionValue);
function ChooseAppAndEventSubstep(
props: ChooseAppAndEventSubstepProps
@@ -72,14 +85,17 @@ function ChooseAppAndEventSubstep(
);
const actionsOrTriggers: Array<ITrigger | IAction> =
(isTrigger ? app?.triggers : app?.actions) || [];
const actionOptions = React.useMemo(
() => actionsOrTriggers.map((trigger) => optionGenerator(trigger)),
const actionOrTriggerOptions = React.useMemo(
() => actionsOrTriggers.map((trigger) => eventOptionGenerator(trigger)),
[app?.key]
);
const selectedActionOrTrigger = actionsOrTriggers.find(
(actionOrTrigger: IAction | ITrigger) => actionOrTrigger.key === step?.key
);
const isWebhook =
isTrigger && (selectedActionOrTrigger as ITrigger)?.type === 'webhook';
const { name } = substep;
const valid: boolean = !!step.key && !!step.appKey;
@@ -177,14 +193,49 @@ function ChooseAppAndEventSubstep(
disablePortal
disableClearable
disabled={editorContext.readOnly}
options={actionOptions}
options={actionOrTriggerOptions}
renderInput={(params) => (
<TextField
{...params}
label={formatMessage('flowEditor.chooseEvent')}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{isWebhook && (
<Chip
label={formatMessage(
'flowEditor.instantTriggerType'
)}
/>
)}
value={getOption(actionOptions, step.key)}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
renderOption={(optionProps, option) => (
<li
{...optionProps}
key={option.value.toString()}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<Typography>{option.label}</Typography>
{option.type === 'webhook' && (
<Chip
label={formatMessage('flowEditor.instantTriggerType')}
sx={{ mr: 3 }}
/>
)}
</li>
)}
value={getOption(actionOrTriggerOptions, step.key)}
onChange={onEventChange}
data-test="choose-event-autocomplete"
/>

View File

@@ -32,7 +32,7 @@ function serializeErrors(graphQLErrors: any) {
return {
...error,
message: (
<pre style={{ margin: 0 }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>
{JSON.stringify(JSON.parse(error.message as string), null, 2)}
</pre>
),
@@ -108,7 +108,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
{!!error?.graphQLErrors?.length && (
<Alert
severity="error"
sx={{ mb: 1, fontWeight: 500, width: '100%' }}
sx={{ mb: 2, fontWeight: 500, width: '100%' }}
>
{serializeErrors(error.graphQLErrors).map((error: any) => (
<div>{error.message}</div>
@@ -116,7 +116,9 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
</Alert>
)}
{step.webhookUrl && <WebhookUrlInfo webhookUrl={step.webhookUrl} />}
{step.webhookUrl && (
<WebhookUrlInfo webhookUrl={step.webhookUrl} sx={{ mb: 2 }} />
)}
{hasNoOutput && (
<Alert severity="warning" sx={{ mb: 1, width: '100%' }}>

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import Typography from '@mui/material/Typography';
import type { AlertProps } from '@mui/material/Alert';
import { generateExternalLink } from '../../helpers/translation-values';
import { WEBHOOK_DOCS } from '../../config/urls';
@@ -9,13 +10,13 @@ import { Alert } from './style';
type WebhookUrlInfoProps = {
webhookUrl: string;
};
} & AlertProps;
function WebhookUrlInfo(props: WebhookUrlInfoProps): React.ReactElement {
const { webhookUrl } = props;
const { webhookUrl, ...alertProps } = props;
return (
<Alert icon={false} color="info">
<Alert icon={false} color="info" {...alertProps}>
<Typography variant="body2" textAlign="center">
<FormattedMessage id="webhookUrlInfo.title" />
</Typography>

Some files were not shown because too many files have changed in this diff Show More