Compare commits

...

15 Commits

Author SHA1 Message Date
Rıdvan Akca
465dc8ba38 feat(notion): add update database item action 2024-01-15 18:20:13 +03:00
Ömer Faruk Aydın
1e3ab75bb7 Merge pull request #1538 from automatisch/remove-types-package
chore: Remove types package
2024-01-15 13:57:01 +01:00
Faruk AYDIN
5f6dd12a73 chore: Remove types package from web dependencies 2024-01-15 13:41:54 +01:00
Faruk AYDIN
d18c06d2c4 chore: Remove types package 2024-01-15 13:37:58 +01:00
Ömer Faruk Aydın
baf99a9cfe Merge pull request #1537 from automatisch/web/types
chore: Use types from the web package
2024-01-15 13:37:43 +01:00
Faruk AYDIN
159931a6ea chore: Use types from the web package 2024-01-15 13:30:48 +01:00
Ömer Faruk Aydın
7831f2925b Merge pull request #1536 from automatisch/docs/remove-typescript
Use JS for the documentation examples
2024-01-15 13:17:48 +01:00
Faruk AYDIN
8fcb7840de docs: Convert code examples to JS 2024-01-15 13:13:48 +01:00
Faruk AYDIN
9ece9461dc docs: Convert all file imports to ES modules 2024-01-15 13:10:26 +01:00
Faruk AYDIN
b304acaaba docs: Use .js file extension 2024-01-15 13:02:06 +01:00
Faruk AYDIN
5a1960609a docs: Use JS highlighter instead of TS 2024-01-15 12:56:43 +01:00
Faruk AYDIN
476aa6e3aa docs: Remove imports of automatisch types 2024-01-15 12:55:55 +01:00
Ömer Faruk Aydın
aa76007fd0 Merge pull request #1533 from automatisch/remove-cli
chore: Remove cli package
2024-01-14 21:06:46 +01:00
Faruk AYDIN
17a8813c4b chore: Remove build step from CI for cli 2024-01-14 20:13:23 +01:00
Faruk AYDIN
fe79fc9003 chore: Remove cli package 2024-01-14 20:10:23 +01:00
112 changed files with 874 additions and 18689 deletions

View File

@@ -83,20 +83,3 @@ jobs:
env: env:
CI: false CI: false
- run: echo "🍏 This job's status is ${{ job.status }}." - run: echo "🍏 This job's status is ${{ job.status }}."
build-cli:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '18'
cache: 'yarn'
cache-dependency-path: yarn.lock
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile && yarn lerna bootstrap
- run: cd packages/cli && yarn build
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@@ -18,7 +18,6 @@
"**/babel-loader", "**/babel-loader",
"**/webpack", "**/webpack",
"**/@automatisch/web", "**/@automatisch/web",
"**/@automatisch/types",
"**/ajv" "**/ajv"
] ]
}, },

View File

@@ -1,5 +1,11 @@
import createDatabaseItem from './create-database-item/index.js'; import createDatabaseItem from './create-database-item/index.js';
import createPage from './create-page/index.js'; import createPage from './create-page/index.js';
import findDatabaseItem from './find-database-item/index.js'; import findDatabaseItem from './find-database-item/index.js';
import updateDatabaseItem from './update-database-item/index.js';
export default [createDatabaseItem, createPage, findDatabaseItem]; export default [
createDatabaseItem,
createPage,
findDatabaseItem,
updateDatabaseItem,
];

View File

@@ -0,0 +1,157 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Update database item',
key: 'updateDatabaseItem',
description: 'Updates a database item.',
arguments: [
{
label: 'Database',
key: 'databaseId',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDatabases',
},
],
},
},
{
label: 'Item',
key: 'itemId',
type: 'dropdown',
required: true,
variables: true,
dependsOn: ['parameters.databaseId'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDatabaseItems',
},
{
name: 'parameters.databaseId',
value: '{parameters.databaseId}',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string',
required: false,
description:
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
variables: true,
},
{
label: 'Tags',
key: 'tags',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'Tag',
key: 'tag',
type: 'dropdown',
required: true,
variables: true,
dependsOn: ['parameters.databaseId'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTags',
},
{
name: 'parameters.databaseId',
value: '{parameters.databaseId}',
},
],
},
},
],
},
{
label: 'Content',
key: 'content',
type: 'string',
required: false,
description:
'You can choose to add extra text to the database item, with a limit of up to 2000 characters if desired.',
variables: true,
},
],
async run($) {
const itemId = $.step.parameters.itemId;
const name = $.step.parameters.name;
const truncatedName = name.slice(0, 2000);
const content = $.step.parameters.content;
const truncatedContent = content.slice(0, 2000);
const tags = $.step.parameters.tags;
const formattedTags = tags
.filter((tag) => tag.tag !== '')
.map((tag) => tag.tag);
const body = {
properties: {},
};
if (truncatedName) {
body.properties.Name = {
title: [
{
text: {
content: truncatedName,
},
},
],
};
}
if (formattedTags?.length) {
body.properties.Tags = {
multi_select: formattedTags.map((tag) => ({ name: tag })),
};
}
if (truncatedContent) {
const response = await $.http.get(`/v1/blocks/${itemId}/children`);
const firstBlockId = response.data.results[0].id;
const body = {
paragraph: {
rich_text: [
{
type: 'text',
text: {
content: truncatedContent,
},
},
],
},
};
await $.http.patch(`/v1/blocks/${firstBlockId}`, body);
}
const { data } = await $.http.patch(`/v1/pages/${itemId}`, body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,4 +1,6 @@
import listDatabaseItems from './list-database-items/index.js';
import listDatabases from './list-databases/index.js'; import listDatabases from './list-databases/index.js';
import listParentPages from './list-parent-pages/index.js'; import listParentPages from './list-parent-pages/index.js';
import listTags from './list-tags/index.js';
export default [listDatabases, listParentPages]; export default [listDatabaseItems, listDatabases, listParentPages, listTags];

View File

@@ -0,0 +1,38 @@
export default {
name: 'List database items',
key: 'listDatabaseItems',
async run($) {
const databases = {
data: [],
error: null,
};
const payload = {
start_cursor: undefined,
};
const databaseId = $.step.parameters.databaseId;
if (!databaseId) {
return databases;
}
do {
const response = await $.http.post(
`/v1/databases/${databaseId}/query`,
payload
);
payload.start_cursor = response.data.next_cursor;
for (const database of response.data.results) {
databases.data.push({
value: database.id,
name:
database.properties.Name?.title?.[0]?.plain_text || 'Untitled Page',
});
}
} while (payload.start_cursor);
return databases;
},
};

View File

@@ -22,7 +22,7 @@ export default {
for (const database of response.data.results) { for (const database of response.data.results) {
databases.data.push({ databases.data.push({
value: database.id, value: database.id,
name: database.title[0].plain_text, name: database.title?.[0]?.plain_text || 'Untitled Database',
}); });
} }
} while (payload.start_cursor); } while (payload.start_cursor);

View File

@@ -0,0 +1,38 @@
export default {
name: 'List tags',
key: 'listTags',
async run($) {
const tags = {
data: [],
error: null,
};
const databaseId = $.step.parameters.databaseId;
let allTags;
if (!databaseId) {
return tags;
}
const response = await $.http.get(`/v1/databases/${databaseId}`);
const tagsExist =
response.data.properties.Tags.multi_select.options.length !== 0;
if (tagsExist) {
allTags = response.data.properties.Tags.multi_select.options.map(
(tag) => tag.name
);
} else {
return tags;
}
for (const tag of allTags) {
tags.data.push({
value: tag,
name: tag,
});
}
return tags;
},
};

View File

@@ -1,11 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -1 +0,0 @@
/dist

View File

@@ -1,16 +0,0 @@
version: 2
updates:
- package-ecosystem: 'npm'
versioning-strategy: increase
directory: '/'
schedule:
interval: 'monthly'
labels:
- 'dependencies'
open-pull-requests-limit: 100
pull-request-branch-name:
separator: '-'
ignore:
- dependency-name: 'fs-extra'
- dependency-name: '*'
update-types: ['version-update:semver-major']

View File

@@ -1,9 +0,0 @@
*-debug.log
*-error.log
/.nyc_output
/dist
/lib
/package-lock.json
/tmp
node_modules
oclif.manifest.json

View File

@@ -1,4 +0,0 @@
# `@automatisch/cli`
The open source Zapier alternative. Build workflow automation without spending
time and money.

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env node
const oclif = require('@oclif/core')
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))

View File

@@ -1,3 +0,0 @@
@echo off
node "%~dp0\automatisch" %*

View File

@@ -1,73 +0,0 @@
{
"name": "@automatisch/cli",
"version": "0.10.0",
"license": "See LICENSE file",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"contributors": [
{
"name": "automatisch contributors",
"url": "https://github.com/automatisch/automatisch/graphs/contributors"
}
],
"homepage": "https://github.com/automatisch/automatisch#readme",
"main": "dist/index.js",
"bin": {
"automatisch": "./bin/automatisch"
},
"files": [
"/bin",
"/dist",
"oclif.manifest.json"
],
"repository": {
"type": "git",
"url": "git+https://github.com/automatisch/automatisch.git"
},
"scripts": {
"build": "shx rm -rf dist && tsc -b",
"build:watch": "nodemon --watch 'src/**/*.ts' --exec 'shx rm -rf dist && tsc -b' --ext 'ts'",
"lint": "eslint . --ext .js --ignore-path ../../.eslintignore",
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme",
"version": "oclif readme && git add README.md"
},
"dependencies": {
"@automatisch/backend": "^0.10.0",
"@oclif/core": "^1",
"@oclif/plugin-help": "^5",
"@oclif/plugin-plugins": "^2.0.1",
"dotenv": "^10.0.0"
},
"devDependencies": {
"@oclif/test": "^2",
"@types/node": "^16.9.4",
"eslint-config-oclif": "^4",
"eslint-config-oclif-typescript": "^1.0.2",
"globby": "^11",
"oclif": "^2",
"shx": "^0.3.3",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typescript": "^4.6.3"
},
"oclif": {
"bin": "automatisch",
"dirname": "automatisch",
"commands": "./dist/commands",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
]
},
"engines": {
"node": ">=12.0.0"
},
"bugs": {
"url": "https://github.com/automatisch/automatisch/issues"
},
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
}
}

View File

@@ -1,50 +0,0 @@
import { readFileSync } from 'fs';
import { Command, Flags } from '@oclif/core';
import * as dotenv from 'dotenv';
import process from 'process';
export default class StartWorker extends Command {
static description = 'Run automatisch worker';
static flags = {
env: Flags.string({
multiple: true,
char: 'e',
}),
'env-file': Flags.string(),
};
async prepareEnvVars() {
const { flags } = await this.parse(StartWorker);
if (flags['env-file']) {
const envFile = readFileSync(flags['env-file'], 'utf8');
const envConfig = dotenv.parse(envFile);
for (const key in envConfig) {
const value = envConfig[key];
process.env[key] = value;
}
}
if (flags.env) {
for (const env of flags.env) {
const [key, value] = env.split('=');
process.env[key] = value;
}
}
// must serve until more customization is introduced
delete process.env.SERVE_WEB_APP_SEPARATELY;
}
async runWorker() {
await import('@automatisch/backend/worker');
}
async run() {
await this.prepareEnvVars();
await this.runWorker();
}
}

View File

@@ -1,96 +0,0 @@
import { readFileSync } from 'fs';
import { Command, Flags } from '@oclif/core';
import * as dotenv from 'dotenv';
import process from 'process';
export default class Start extends Command {
static description = 'Run automatisch';
static flags = {
env: Flags.string({
multiple: true,
char: 'e',
}),
'env-file': Flags.string(),
};
get isProduction() {
return process.env.APP_ENV === 'production';
}
async prepareEnvVars() {
const { flags } = await this.parse(Start);
if (flags['env-file']) {
const envFile = readFileSync(flags['env-file'], 'utf8');
const envConfig = dotenv.parse(envFile);
for (const key in envConfig) {
const value = envConfig[key];
process.env[key] = value;
}
}
if (flags.env) {
for (const env of flags.env) {
const [key, value] = env.split('=');
process.env[key] = value;
}
}
// must serve until more customization is introduced
delete process.env.SERVE_WEB_APP_SEPARATELY;
}
async createDatabaseAndUser() {
const utils = await import('@automatisch/backend/database-utils');
await utils.createDatabaseAndUser(
process.env.POSTGRES_DATABASE,
process.env.POSTGRES_USERNAME
);
}
async runMigrationsIfNeeded() {
const { logger } = await import('@automatisch/backend/logger');
const database = await import('@automatisch/backend/database');
const migrator = database.client.migrate;
const [, pendingMigrations] = await migrator.list();
const pendingMigrationsCount = pendingMigrations.length;
const needsToMigrate = pendingMigrationsCount > 0;
if (needsToMigrate) {
logger.info(`Processing ${pendingMigrationsCount} migrations.`);
await migrator.latest();
logger.info(`Completed ${pendingMigrationsCount} migrations.`);
} else {
logger.info('No migrations needed.');
}
}
async seedUser() {
const utils = await import('@automatisch/backend/database-utils');
await utils.createUser();
}
async runApp() {
await import('@automatisch/backend/server');
}
async run() {
await this.prepareEnvVars();
if (!this.isProduction) {
await this.createDatabaseAndUser();
}
await this.runMigrationsIfNeeded();
await this.seedUser();
await this.runApp();
}
}

View File

@@ -1 +0,0 @@
export { run } from '@oclif/core';

View File

@@ -1,19 +0,0 @@
{
"compilerOptions": {
"declaration": true,
"allowJs": true,
"esModuleInterop": true,
"importHelpers": true,
"lib": ["es2021"],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"outDir": "dist",
"rootDir": "src",
"skipLibCheck": true,
"strict": true,
"target": "es2021",
"typeRoots": ["node_modules/@types", "./src/types"]
},
"include": ["src/**/*"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
# Telemetry # Telemetry
:::info :::info
We want to be very transparent about the data we collect and how we use it. Therefore, we have abstracted all of the code we use with our telemetry system into a single, easily accessible place. You can check the code [here](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/helpers/telemetry/index.ts) and let us know if you have any suggestions for changes. We want to be very transparent about the data we collect and how we use it. Therefore, we have abstracted all of the code we use with our telemetry system into a single, easily accessible place. You can check the code [here](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/helpers/telemetry/index.js) and let us know if you have any suggestions for changes.
::: :::
Automatisch comes with a built-in telemetry system that collects anonymous usage data. This data is used to help us improve the product and to make sure we are focusing on the right features. While we're doing it, we don't collect any personal information. You can also disable the telemetry system by setting the `TELEMETRY_ENABLED` environment variable. See the [environment variables](/advanced/configuration#environment-variables) section for more information. Automatisch comes with a built-in telemetry system that collects anonymous usage data. This data is used to help us improve the product and to make sure we are focusing on the right features. While we're doing it, we don't collect any personal information. You can also disable the telemetry system by setting the `TELEMETRY_ENABLED` environment variable. See the [environment variables](/advanced/configuration#environment-variables) section for more information.

View File

@@ -7,6 +7,8 @@ items:
desc: Creates a page inside a parent page. desc: Creates a page inside a parent page.
- name: Find database item - name: Find database item
desc: Searches for an item in a database by property. desc: Searches for an item in a database by property.
- name: Update database item
desc: Updates a database item.
--- ---
<script setup> <script setup>

View File

@@ -16,13 +16,13 @@ The build integrations section is best understood when read from beginning to en
## Add actions to the app. ## Add actions to the app.
Open the `thecatapi/index.ts` file and add the highlighted lines for actions. Open the `thecatapi/index.js` file and add the highlighted lines for actions.
```typescript{4,17} ```javascript{4,17}
import defineApp from '../../helpers/define-app'; import defineApp from '../../helpers/define-app.js';
import auth from './auth'; import auth from './auth/index.js';
import triggers from './triggers'; import triggers from './triggers/index.js';
import actions from './actions'; import actions from './actions/index.js';
export default defineApp({ export default defineApp({
name: 'The cat API', name: 'The cat API',
@@ -41,24 +41,24 @@ export default defineApp({
## Define actions ## Define actions
Create the `actions/index.ts` file inside of the `thecatapi` folder. Create the `actions/index.js` file inside of the `thecatapi` folder.
```typescript ```javascript
import markCatImageAsFavorite from './mark-cat-image-as-favorite'; import markCatImageAsFavorite from './mark-cat-image-as-favorite/index.js';
export default [markCatImageAsFavorite]; export default [markCatImageAsFavorite];
``` ```
:::tip :::tip
If you add new actions, you need to add them to the actions/index.ts file and export all actions as an array. If you add new actions, you need to add them to the actions/index.js file and export all actions as an array.
::: :::
## Add metadata ## Add metadata
Create the `actions/mark-cat-image-as-favorite/index.ts` file inside the `thecatapi` folder. Create the `actions/mark-cat-image-as-favorite/index.js` file inside the `thecatapi` folder.
```typescript ```javascript
import defineAction from '../../../../helpers/define-action'; import defineAction from '../../../../helpers/define-action.js';
export default defineAction({ export default defineAction({
name: 'Mark the cat image as favorite', name: 'Mark the cat image as favorite',
@@ -68,7 +68,7 @@ export default defineAction({
{ {
label: 'Image ID', label: 'Image ID',
key: 'imageId', key: 'imageId',
type: 'string' as const, type: 'string',
required: true, required: true,
description: 'The ID of the cat image you want to mark as favorite.', description: 'The ID of the cat image you want to mark as favorite.',
variables: true, variables: true,
@@ -91,10 +91,10 @@ Let's briefly explain what we defined here.
## Implement the action ## Implement the action
Open the `actions/mark-cat-image-as-favorite.ts` file and add the highlighted lines. Open the `actions/mark-cat-image-as-favorite.js` file and add the highlighted lines.
```typescript{7-20} ```javascript{7-20}
import defineAction from '../../../../helpers/define-action'; import defineAction from '../../../../helpers/define-action.js';
export default defineAction({ export default defineAction({
// ... // ...
@@ -104,7 +104,7 @@ export default defineAction({
const imageId = $.step.parameters.imageId; const imageId = $.step.parameters.imageId;
const headers = { const headers = {
'x-api-key': $.auth.data.apiKey as string, 'x-api-key': $.auth.data.apiKey,
}; };
const response = await $.http.post( const response = await $.http.post(

View File

@@ -27,17 +27,17 @@ cd packages/backend/src/apps
mkdir thecatapi mkdir thecatapi
``` ```
We need to create an `index.ts` file inside of the `thecatapi` folder. We need to create an `index.js` file inside of the `thecatapi` folder.
```bash ```bash
cd thecatapi cd thecatapi
touch index.ts touch index.js
``` ```
Then let's define the app inside of the `index.ts` file as follows: Then let's define the app inside of the `index.js` file as follows:
```typescript ```javascript
import defineApp from '../../helpers/define-app'; import defineApp from '../../helpers/define-app.js';
export default defineApp({ export default defineApp({
name: 'The cat API', name: 'The cat API',

View File

@@ -24,11 +24,11 @@ You can find detailed documentation of the cat API [here](https://docs.thecatapi
## Add auth to the app ## Add auth to the app
Open the `thecatapi/index.ts` file and add the highlighted lines for authentication. Open the `thecatapi/index.js` file and add the highlighted lines for authentication.
```typescript{2,13} ```javascript{2,13}
import defineApp from '../../helpers/define-app'; import defineApp from '../../helpers/define-app.js';
import auth from './auth'; import auth from './auth/index.js';
export default defineApp({ export default defineApp({
name: 'The cat API', name: 'The cat API',
@@ -45,22 +45,22 @@ export default defineApp({
## Define auth fields ## Define auth fields
Let's create the `auth/index.ts` file inside of the `thecatapi` folder. Let's create the `auth/index.js` file inside of the `thecatapi` folder.
```bash ```bash
mkdir auth mkdir auth
touch auth/index.ts touch auth/index.js
``` ```
Then let's start with defining fields the auth inside of the `auth/index.ts` file as follows: Then let's start with defining fields the auth inside of the `auth/index.js` file as follows:
```typescript ```javascript
export default { export default {
fields: [ fields: [
{ {
key: 'screenName', key: 'screenName',
label: 'Screen Name', label: 'Screen Name',
type: 'string' as const, type: 'string',
required: true, required: true,
readOnly: false, readOnly: false,
value: null, value: null,
@@ -72,7 +72,7 @@ export default {
{ {
key: 'apiKey', key: 'apiKey',
label: 'API Key', label: 'API Key',
type: 'string' as const, type: 'string',
required: true, required: true,
readOnly: false, readOnly: false,
value: null, value: null,
@@ -101,10 +101,10 @@ If the third-party service you use provides both an API key and OAuth for the au
So until now, we integrated auth folder with the app definition and defined the auth fields. Now we need to verify the credentials that the user entered. We will do that by defining the `verifyCredentials` method. So until now, we integrated auth folder with the app definition and defined the auth fields. Now we need to verify the credentials that the user entered. We will do that by defining the `verifyCredentials` method.
Start with adding the `verifyCredentials` method to the `auth/index.ts` file. Start with adding the `verifyCredentials` method to the `auth/index.js` file.
```typescript{1,8} ```javascript{1,8}
import verifyCredentials from './verify-credentials'; import verifyCredentials from './verify-credentials.js';
export default { export default {
fields: [ fields: [
@@ -115,12 +115,10 @@ export default {
}; };
``` ```
Let's create the `verify-credentials.ts` file inside the `auth` folder. Let's create the `verify-credentials.js` file inside the `auth` folder.
```typescript ```javascript
import { IGlobalVariable } from '@automatisch/types'; const verifyCredentials = async ($) => {
const verifyCredentials = async ($: IGlobalVariable) => {
// TODO: Implement verification of the credentials // TODO: Implement verification of the credentials
}; };
@@ -129,12 +127,10 @@ export default verifyCredentials;
We generally use the `users/me` endpoint or any other endpoint that we can validate the API key or any other credentials that the user provides. For our example, we don't have a specific API endpoint to check whether the credentials are correct or not. So we will randomly pick one of the API endpoints, which will be the `GET /v1/images/search` endpoint. We will send a request to this endpoint with the API key. If the API key is correct, we will get a response from the API. If the API key is incorrect, we will get an error response from the API. We generally use the `users/me` endpoint or any other endpoint that we can validate the API key or any other credentials that the user provides. For our example, we don't have a specific API endpoint to check whether the credentials are correct or not. So we will randomly pick one of the API endpoints, which will be the `GET /v1/images/search` endpoint. We will send a request to this endpoint with the API key. If the API key is correct, we will get a response from the API. If the API key is incorrect, we will get an error response from the API.
Let's implement the authentication logic that we mentioned above in the `verify-credentials.ts` file. Let's implement the authentication logic that we mentioned above in the `verify-credentials.js` file.
```typescript ```javascript
import { IGlobalVariable } from '@automatisch/types'; const verifyCredentials = async ($) => {
const verifyCredentials = async ($: IGlobalVariable) => {
await $.http.get('/v1/images/search'); await $.http.get('/v1/images/search');
await $.auth.set({ await $.auth.set({
@@ -155,11 +151,11 @@ You must always provide a `screenName` field to auth data in the `verifyCredenti
We have implemented the `verifyCredentials` method. Now we need to check whether the credentials are still valid or not for the test connection functionality in Automatisch. We will do that by defining the `isStillVerified` method. We have implemented the `verifyCredentials` method. Now we need to check whether the credentials are still valid or not for the test connection functionality in Automatisch. We will do that by defining the `isStillVerified` method.
Start with adding the `isStillVerified` method to the `auth/index.ts` file. Start with adding the `isStillVerified` method to the `auth/index.js` file.
```typescript{2,10} ```javascript{2,10}
import verifyCredentials from './verify-credentials'; import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified'; import isStillVerified from './is-still-verified.js';
export default { export default {
fields: [ fields: [
@@ -171,13 +167,12 @@ export default {
}; };
``` ```
Let's create the `is-still-verified.ts` file inside the `auth` folder. Let's create the `is-still-verified.js` file inside the `auth` folder.
```typescript ```javascript
import { IGlobalVariable } from '@automatisch/types'; import verifyCredentials from './verify-credentials.js';
import verifyCredentials from './verify-credentials';
const isStillVerified = async ($: IGlobalVariable) => { const isStillVerified = async ($) => {
await verifyCredentials($); await verifyCredentials($);
return true; return true;
}; };

View File

@@ -18,35 +18,35 @@ The build integrations section is best understood when read from beginning to en
### 3-legged OAuth ### 3-legged OAuth
- [Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/auth/index.ts) - [Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/auth/index.js)
- [Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/auth/index.ts) - [Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/auth/index.js)
- [Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/auth/index.ts) - [Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/auth/index.js)
- [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.ts) - [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.js)
- [Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/auth/index.ts) - [Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/auth/index.js)
- [Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/auth/index.ts) - [Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/auth/index.js)
### OAuth with the refresh token ### OAuth with the refresh token
- [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.ts) - [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.js)
### API key ### API key
- [DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/auth/index.ts) - [DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/auth/index.js)
- [Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/auth/index.ts) - [Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/auth/index.js)
- [SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/auth/index.ts) - [SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/auth/index.js)
- [SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/auth/index.ts) - [SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/auth/index.js)
### Without authentication ### Without authentication
- [RSS](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/rss/index.ts) - [RSS](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/rss/index.js)
- [Scheduler](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/scheduler/index.ts) - [Scheduler](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/scheduler/index.js)
## Triggers ## Triggers
### Polling-based triggers ### Polling-based triggers
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts) - [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.js)
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.ts) - [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.js)
### Webhook-based triggers ### Webhook-based triggers
@@ -54,27 +54,27 @@ The build integrations section is best understood when read from beginning to en
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. 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) - [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.js)
### Pagination with descending order ### Pagination with descending order
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts) - [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.js)
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.ts) - [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.js)
- [Receive SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/triggers/receive-sms/index.ts) - [Receive SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/triggers/receive-sms/index.js)
- [Receive SMS - SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts) - [Receive SMS - SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/triggers/receive-sms/index.js)
- [New photos - Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/triggers/new-photos/index.ts) - [New photos - Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/triggers/new-photos/index.js)
### Pagination with ascending order ### Pagination with ascending order
- [New stargazers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-stargazers/index.ts) - [New stargazers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-stargazers/index.js)
- [New watchers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-watchers/index.ts) - [New watchers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-watchers/index.js)
## Actions ## Actions
- [Send a message to channel - Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/index.ts) - [Send a message to channel - Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/index.js)
- [Send SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/actions/send-sms/index.ts) - [Send SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/actions/send-sms/index.js)
- [Send a message to channel - Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/actions/send-message-to-channel/index.ts) - [Send a message to channel - Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/actions/send-message-to-channel/index.js)
- [Create issue - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/actions/create-issue/index.ts) - [Create issue - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/actions/create-issue/index.js)
- [Send an email - SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/actions/send-email/index.ts) - [Send an email - SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/actions/send-email/index.js)
- [Create tweet - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/actions/create-tweet/index.ts) - [Create tweet - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/actions/create-tweet/index.js)
- [Translate text - DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/actions/translate-text/index.ts) - [Translate text - DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/actions/translate-text/index.js)

View File

@@ -35,13 +35,13 @@ Here, you can see the folder structure of an example app. We will briefly walk t
├── auth ├── auth
├── common ├── common
├── dynamic-data ├── dynamic-data
├── index.ts ├── index.js
└── triggers └── triggers
``` ```
## App ## App
The `index.ts` file is the entry point of the app. It contains the definition of the app and the app's metadata. It also includes the list of triggers, actions, and data sources that the app provides. So, whatever you build inside the app, you need to associate it within the `index.ts` file. The `index.js` file is the entry point of the app. It contains the definition of the app and the app's metadata. It also includes the list of triggers, actions, and data sources that the app provides. So, whatever you build inside the app, you need to associate it within the `index.js` file.
## Auth ## Auth

View File

@@ -16,11 +16,11 @@ The build integrations section is best understood when read from beginning to en
Before handling authentication and building a trigger and an action, it's better to explain the `global variable` concept in Automatisch. Automatisch provides you the global variable that you need to use with authentication, triggers, actions, and basically all the stuff you will build for the integration. Before handling authentication and building a trigger and an action, it's better to explain the `global variable` concept in Automatisch. Automatisch provides you the global variable that you need to use with authentication, triggers, actions, and basically all the stuff you will build for the integration.
The global variable is represented as `$` variable in the codebase, and it's a JSON object that contains the following properties: The global variable is represented as `$` variable in the codebase, and it's a JS object that contains the following properties:
## $.auth.set ## $.auth.set
```typescript ```javascript
await $.auth.set({ await $.auth.set({
key: 'value', key: 'value',
}); });
@@ -30,7 +30,7 @@ It's used to set the authentication data, and you can use this method with multi
## $.auth.data ## $.auth.data
```typescript ```javascript
$.auth.data; // { key: 'value' } $.auth.data; // { key: 'value' }
``` ```
@@ -38,7 +38,7 @@ It's used to retrieve the authentication data that we set with `$.auth.set()`. T
## $.app.baseUrl ## $.app.baseUrl
```typescript ```javascript
$.app.baseUrl; // https://thecatapi.com $.app.baseUrl; // https://thecatapi.com
``` ```
@@ -46,7 +46,7 @@ It's used to retrieve the base URL of the app that we defined previously. In our
## $.app.apiBaseUrl ## $.app.apiBaseUrl
```typescript ```javascript
$.app.apiBaseUrl; // https://api.thecatapi.com $.app.apiBaseUrl; // https://api.thecatapi.com
``` ```
@@ -54,7 +54,7 @@ It's used to retrieve the API base URL of the app that we defined previously. In
## $.app.auth.fields ## $.app.auth.fields
```typescript ```javascript
$.app.auth.fields; $.app.auth.fields;
``` ```
@@ -64,7 +64,7 @@ It's used to retrieve the fields that we defined in the `auth` section of the ap
It's an HTTP client to be used for making HTTP requests. It's a wrapper around the [axios](https://axios-http.com) library. We use this property when we need to make HTTP requests to the third-party service. The `apiBaseUrl` field we set up in the app will be used as the base URL for the HTTP requests. For example, to search the cat images, we can use the following code: It's an HTTP client to be used for making HTTP requests. It's a wrapper around the [axios](https://axios-http.com) library. We use this property when we need to make HTTP requests to the third-party service. The `apiBaseUrl` field we set up in the app will be used as the base URL for the HTTP requests. For example, to search the cat images, we can use the following code:
```typescript ```javascript
await $.http.get('/v1/images/search?order=DESC', { await $.http.get('/v1/images/search?order=DESC', {
headers: { headers: {
'x-api-key': $.auth.data.apiKey, 'x-api-key': $.auth.data.apiKey,
@@ -76,15 +76,15 @@ Keep in mind that the HTTP client handles the error with the status code that fa
## $.step.parameters ## $.step.parameters
```typescript ```javascript
$.step.parameters; // { key: 'value' } $.step.parameters; // { key: 'value' }
``` ```
It refers to the parameters that are set by users in the UI. We use this property when we need to get the parameters for corresponding triggers and actions. For example [Send a message to channel](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.ts) action from Slack integration, we have a step parameter called `message` that we need to use in the action. We can use `$.step.parameters.message` to get the value of the message to send a message to the Slack channel. It refers to the parameters that are set by users in the UI. We use this property when we need to get the parameters for corresponding triggers and actions. For example [Send a message to channel](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.js) action from Slack integration, we have a step parameter called `message` that we need to use in the action. We can use `$.step.parameters.message` to get the value of the message to send a message to the Slack channel.
## $.pushTriggerItem ## $.pushTriggerItem
```typescript ```javascript
$.pushTriggerItem({ $.pushTriggerItem({
raw: resourceData, raw: resourceData,
meta: { meta: {
@@ -97,7 +97,7 @@ It's used to push trigger data to be processed by Automatisch. It must reflect t
## $.setActionItem ## $.setActionItem
```typescript ```javascript
$.setActionItem({ $.setActionItem({
raw: resourceData, raw: resourceData,
}); });

View File

@@ -20,12 +20,12 @@ We used a polling-based HTTP trigger in our example but if you need to use a web
## Add triggers to the app ## Add triggers to the app
Open the `thecatapi/index.ts` file and add the highlighted lines for triggers. Open the `thecatapi/index.js` file and add the highlighted lines for triggers.
```typescript{3,15} ```javascript{3,15}
import defineApp from '../../helpers/define-app'; import defineApp from '../../helpers/define-app.js';
import auth from './auth'; import auth from './auth/index.js';
import triggers from './triggers'; import triggers from './triggers/index.js';
export default defineApp({ export default defineApp({
name: 'The cat API', name: 'The cat API',
@@ -43,24 +43,24 @@ export default defineApp({
## Define triggers ## Define triggers
Create the `triggers/index.ts` file inside of the `thecatapi` folder. Create the `triggers/index.js` file inside of the `thecatapi` folder.
```typescript ```javascript
import searchCatImages from './search-cat-images'; import searchCatImages from './search-cat-images/index.js';
export default [searchCatImages]; export default [searchCatImages];
``` ```
:::tip :::tip
If you add new triggers, you need to add them to the `triggers/index.ts` file and export all triggers as an array. The order of triggers in this array will be reflected in the Automatisch user interface. If you add new triggers, you need to add them to the `triggers/index.js` file and export all triggers as an array. The order of triggers in this array will be reflected in the Automatisch user interface.
::: :::
## Add metadata ## Add metadata
Create the `triggers/search-cat-images/index.ts` file inside of the `thecatapi` folder. Create the `triggers/search-cat-images/index.js` file inside of the `thecatapi` folder.
```typescript ```javascript
import defineTrigger from '../../../../helpers/define-trigger'; import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({ export default defineTrigger({
name: 'Search cat images', name: 'Search cat images',
@@ -93,9 +93,8 @@ Let's briefly explain what we defined here.
Implement the `run` function by adding highlighted lines. Implement the `run` function by adding highlighted lines.
```typescript{1,7-30} ```javascript{1,7-30}
import { IJSONObject } from '@automatisch/types'; import defineTrigger from '../../../../helpers/define-trigger.js';
import defineTrigger from '../../../../helpers/define-trigger';
export default defineTrigger({ export default defineTrigger({
// ... // ...
@@ -104,18 +103,18 @@ export default defineTrigger({
let response; let response;
const headers = { const headers = {
'x-api-key': $.auth.data.apiKey as string, 'x-api-key': $.auth.data.apiKey,
}; };
do { do {
let requestPath = `/v1/images/search?page=${page}&limit=10&order=DESC`; let requestPath = `/v1/images/search?page=${page}&limit=10&order=DESC`;
response = await $.http.get(requestPath, { headers }); response = await $.http.get(requestPath, { headers });
response.data.forEach((image: IJSONObject) => { response.data.forEach((image) => {
const dataItem = { const dataItem = {
raw: image, raw: image,
meta: { meta: {
internalId: image.id as string internalId: image.id
}, },
}; };

View File

@@ -35,7 +35,7 @@ yarn db:create
``` ```
:::warning :::warning
`yarn db:create` commands expect that you have the `postgres` superuser. If not, you can create a superuser called `postgres` manually or you can create the database manually by checking PostgreSQL-related default values from the [app config](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/config/app.ts). `yarn db:create` commands expect that you have the `postgres` superuser. If not, you can create a superuser called `postgres` manually or you can create the database manually by checking PostgreSQL-related default values from the [app config](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/config/app.js).
::: :::
Run the database migrations in the backend folder. Run the database migrations in the backend folder.

View File

@@ -1,4 +0,0 @@
# `@automatisch/types`
The open source Zapier alternative. Build workflow automation without spending
time and money.

View File

@@ -1,20 +0,0 @@
{
"name": "@automatisch/types",
"version": "0.10.0",
"license": "See LICENSE file",
"description": "Type definitions for automatisch",
"homepage": "https://github.com/automatisch/automatisch",
"types": "./index.d.ts",
"scripts": {},
"typeScriptVersion": "4.1",
"repository": {
"type": "git",
"url": "git+https://github.com/automatisch/automatisch.git"
},
"bugs": {
"url": "https://github.com/automatisch/automatisch/issues"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -5,7 +5,6 @@
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.", "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"dependencies": { "dependencies": {
"@apollo/client": "^3.6.9", "@apollo/client": "^3.6.9",
"@automatisch/types": "^0.10.0",
"@casl/ability": "^6.5.0", "@casl/ability": "^6.5.0",
"@casl/react": "^3.1.0", "@casl/react": "^3.1.0",
"@emotion/react": "^11.4.1", "@emotion/react": "^11.4.1",

View File

@@ -1,4 +1,4 @@
import type { IApp, IField, IJSONObject } from '@automatisch/types'; import type { IApp, IField, IJSONObject } from 'types';
import LoadingButton from '@mui/lab/LoadingButton'; import LoadingButton from '@mui/lab/LoadingButton';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';

View File

@@ -19,7 +19,7 @@ import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput'; import OutlinedInput from '@mui/material/OutlinedInput';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import type { IApp } from '@automatisch/types'; import type { IApp } from 'types';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import AppIcon from 'components/AppIcon'; import AppIcon from 'components/AppIcon';

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type { IField } from '@automatisch/types'; import type { IField } from 'types';
import LoadingButton from '@mui/lab/LoadingButton'; import LoadingButton from '@mui/lab/LoadingButton';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { IApp } from '@automatisch/types'; import type { IApp } from 'types';
import { FieldValues, SubmitHandler } from 'react-hook-form'; import { FieldValues, SubmitHandler } from 'react-hook-form';
import { useMutation } from '@apollo/client'; import { useMutation } from '@apollo/client';
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config'; import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import type { IApp } from '@automatisch/types'; import type { IApp } from 'types';
import { FieldValues, SubmitHandler } from 'react-hook-form'; import { FieldValues, SubmitHandler } from 'react-hook-form';
import { useMutation } from '@apollo/client'; import { useMutation } from '@apollo/client';
import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client'; import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client';

View File

@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
import Menu from '@mui/material/Menu'; import Menu from '@mui/material/Menu';
import type { PopoverProps } from '@mui/material/Popover'; import type { PopoverProps } from '@mui/material/Popover';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import type { IConnection } from '@automatisch/types'; import type { IConnection } from 'types';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';

View File

@@ -11,7 +11,7 @@ import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import * as React from 'react'; import * as React from 'react';
import type { IConnection } from '@automatisch/types'; import type { IConnection } from 'types';
import ConnectionContextMenu from 'components/AppConnectionContextMenu'; import ConnectionContextMenu from 'components/AppConnectionContextMenu';
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection'; import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
import { TEST_CONNECTION } from 'graphql/queries/test-connection'; import { TEST_CONNECTION } from 'graphql/queries/test-connection';
@@ -83,8 +83,8 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
enqueueSnackbar(formatMessage('connection.deletedMessage'), { enqueueSnackbar(formatMessage('connection.deletedMessage'), {
variant: 'success', variant: 'success',
SnackbarProps: { SnackbarProps: {
'data-test': 'snackbar-delete-connection-success' 'data-test': 'snackbar-delete-connection-success',
} },
}); });
} else if (action.type === 'test') { } else if (action.type === 'test') {
setVerificationVisible(true); setVerificationVisible(true);

View File

@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import type { IConnection } from '@automatisch/types'; import type { IConnection } from 'types';
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections'; import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
import AppConnectionRow from 'components/AppConnectionRow'; import AppConnectionRow from 'components/AppConnectionRow';
import NoResultFound from 'components/NoResultFound'; import NoResultFound from 'components/NoResultFound';

View File

@@ -8,7 +8,7 @@ import * as URLS from 'config/urls';
import AppFlowRow from 'components/FlowRow'; import AppFlowRow from 'components/FlowRow';
import NoResultFound from 'components/NoResultFound'; import NoResultFound from 'components/NoResultFound';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import type { IFlow } from '@automatisch/types'; import type { IFlow } from 'types';
type AppFlowsProps = { type AppFlowsProps = {
appKey: string; appKey: string;

View File

@@ -7,7 +7,7 @@ import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import AppIcon from 'components/AppIcon'; import AppIcon from 'components/AppIcon';
import type { IApp } from '@automatisch/types'; import type { IApp } from 'types';
import { CardContent, Typography } from './style'; import { CardContent, Typography } from './style';

View File

@@ -7,13 +7,7 @@ import ListItem from '@mui/material/ListItem';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
import type { import type { IApp, IStep, ISubstep, ITrigger, IAction } from 'types';
IApp,
IStep,
ISubstep,
ITrigger,
IAction,
} from '@automatisch/types';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import useApps from 'hooks/useApps'; import useApps from 'hooks/useApps';

View File

@@ -6,7 +6,7 @@ import ListItem from '@mui/material/ListItem';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import * as React from 'react'; import * as React from 'react';
import type { IApp, IConnection, IStep, ISubstep } from '@automatisch/types'; import type { IApp, IConnection, IStep, ISubstep } from 'types';
import AddAppConnection from 'components/AddAppConnection'; import AddAppConnection from 'components/AddAppConnection';
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee'; import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';

View File

@@ -1,9 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import FormHelperText from '@mui/material/FormHelperText'; import FormHelperText from '@mui/material/FormHelperText';
import Autocomplete, { AutocompleteProps, createFilterOptions } from '@mui/material/Autocomplete'; import Autocomplete, {
AutocompleteProps,
createFilterOptions,
} from '@mui/material/Autocomplete';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import type { IFieldDropdownOption } from '@automatisch/types'; import type { IFieldDropdownOption } from 'types';
interface ControlledAutocompleteProps interface ControlledAutocompleteProps
extends AutocompleteProps<IFieldDropdownOption, boolean, boolean, boolean> { extends AutocompleteProps<IFieldDropdownOption, boolean, boolean, boolean> {
@@ -23,8 +26,8 @@ const filterOptions = createFilterOptions<IFieldDropdownOption>({
stringify: ({ label, value }) => ` stringify: ({ label, value }) => `
${label} ${label}
${value} ${value}
` `,
}) });
function ControlledAutocomplete( function ControlledAutocomplete(
props: ControlledAutocompleteProps props: ControlledAutocompleteProps

View File

@@ -3,7 +3,7 @@ import Popper from '@mui/material/Popper';
import Tab from '@mui/material/Tab'; import Tab from '@mui/material/Tab';
import * as React from 'react'; import * as React from 'react';
import type { IFieldDropdownOption } from '@automatisch/types'; import type { IFieldDropdownOption } from 'types';
import Suggestions from 'components/PowerInput/Suggestions'; import Suggestions from 'components/PowerInput/Suggestions';
import TabPanel from 'components/TabPanel'; import TabPanel from 'components/TabPanel';

View File

@@ -1,4 +1,4 @@
import type { IFieldDropdownOption } from '@automatisch/types'; import type { IFieldDropdownOption } from 'types';
import ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText'; import ListItemText from '@mui/material/ListItemText';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
@@ -13,7 +13,7 @@ import { SearchInputWrapper } from './style';
interface OptionsProps { interface OptionsProps {
data: readonly IFieldDropdownOption[]; data: readonly IFieldDropdownOption[];
onOptionClick: (event: React.MouseEvent, option: any) => void; onOptionClick: (event: React.MouseEvent, option: any) => void;
}; }
const SHORT_LIST_LENGTH = 4; const SHORT_LIST_LENGTH = 4;
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
@@ -21,80 +21,89 @@ const LIST_ITEM_HEIGHT = 64;
const computeListHeight = (currentLength: number) => { const computeListHeight = (currentLength: number) => {
const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength); const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength);
return LIST_ITEM_HEIGHT * numberOfRenderedItems; return LIST_ITEM_HEIGHT * numberOfRenderedItems;
}
const renderItemFactory = ({ onOptionClick }: Pick<OptionsProps, 'onOptionClick'>) => (props: ListChildComponentProps) => {
const { index, style, data } = props;
const suboption = data[index];
return (
<ListItemButton
sx={{ pl: 4 }}
divider
onClick={(event) => onOptionClick(event, suboption)}
data-test="power-input-suggestion-item"
key={index}
style={style}
>
<ListItemText
primary={suboption.label}
primaryTypographyProps={{
variant: 'subtitle1',
title: 'Property name',
sx: { fontWeight: 700 },
}}
secondary={suboption.value}
secondaryTypographyProps={{
variant: 'subtitle2',
title: 'Sample value',
noWrap: true,
}}
/>
</ListItemButton>
);
}; };
const renderItemFactory =
({ onOptionClick }: Pick<OptionsProps, 'onOptionClick'>) =>
(props: ListChildComponentProps) => {
const { index, style, data } = props;
const suboption = data[index];
return (
<ListItemButton
sx={{ pl: 4 }}
divider
onClick={(event) => onOptionClick(event, suboption)}
data-test="power-input-suggestion-item"
key={index}
style={style}
>
<ListItemText
primary={suboption.label}
primaryTypographyProps={{
variant: 'subtitle1',
title: 'Property name',
sx: { fontWeight: 700 },
}}
secondary={suboption.value}
secondaryTypographyProps={{
variant: 'subtitle2',
title: 'Sample value',
noWrap: true,
}}
/>
</ListItemButton>
);
};
const Options = (props: OptionsProps) => { const Options = (props: OptionsProps) => {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { const { data, onOptionClick } = props;
data, const [filteredData, setFilteredData] =
onOptionClick React.useState<readonly IFieldDropdownOption[]>(data);
} = props;
const [filteredData, setFilteredData] = React.useState<readonly IFieldDropdownOption[]>( React.useEffect(
data function syncOptions() {
setFilteredData((filteredData) => {
if (filteredData.length === 0 && filteredData.length !== data.length) {
return data;
}
return filteredData;
});
},
[data]
); );
React.useEffect(function syncOptions() { const renderItem = React.useMemo(
setFilteredData((filteredData) => { () =>
if (filteredData.length === 0 && filteredData.length !== data.length) { renderItemFactory({
return data; onOptionClick,
} }),
[onOptionClick]
return filteredData; );
})
}, [data]);
const renderItem = React.useMemo(() => renderItemFactory({
onOptionClick
}), [onOptionClick]);
const onSearchChange = React.useMemo( const onSearchChange = React.useMemo(
() => () =>
throttle((event: React.ChangeEvent) => { throttle((event: React.ChangeEvent) => {
const search = (event.target as HTMLInputElement).value.toLowerCase(); const search = (event.target as HTMLInputElement).value.toLowerCase();
if (!search) { if (!search) {
setFilteredData(data); setFilteredData(data);
return; return;
} }
const newFilteredData = data.filter(option => `${option.label}\n${option.value}`.toLowerCase().includes(search.toLowerCase())); const newFilteredData = data.filter((option) =>
`${option.label}\n${option.value}`
.toLowerCase()
.includes(search.toLowerCase())
);
setFilteredData(newFilteredData); setFilteredData(newFilteredData);
}, 400), }, 400),
[data] [data]
); );
return ( return (
<> <>
@@ -122,5 +131,4 @@ const Options = (props: OptionsProps) => {
); );
}; };
export default Options; export default Options;

View File

@@ -5,7 +5,7 @@ import FormHelperText from '@mui/material/FormHelperText';
import { AutocompleteProps } from '@mui/material/Autocomplete'; import { AutocompleteProps } from '@mui/material/Autocomplete';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ClearIcon from '@mui/icons-material/Clear'; import ClearIcon from '@mui/icons-material/Clear';
import type { IFieldDropdownOption } from '@automatisch/types'; import type { IFieldDropdownOption } from 'types';
import { ActionButtonsWrapper } from './style'; import { ActionButtonsWrapper } from './style';
import ClickAwayListener from '@mui/base/ClickAwayListener'; import ClickAwayListener from '@mui/base/ClickAwayListener';

View File

@@ -8,7 +8,7 @@ import IconButton from '@mui/material/IconButton';
import RemoveIcon from '@mui/icons-material/Remove'; import RemoveIcon from '@mui/icons-material/Remove';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import { IFieldDynamic } from '@automatisch/types'; import { IFieldDynamic } from 'types';
import InputCreator from 'components/InputCreator'; import InputCreator from 'components/InputCreator';
import { EditorContext } from 'contexts/Editor'; import { EditorContext } from 'contexts/Editor';

View File

@@ -3,7 +3,7 @@ import { useMutation } from '@apollo/client';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import type { IFlow, IStep } from '@automatisch/types'; import type { IFlow, IStep } from 'types';
import { GET_FLOW } from 'graphql/queries/get-flow'; import { GET_FLOW } from 'graphql/queries/get-flow';
import { CREATE_STEP } from 'graphql/mutations/create-step'; import { CREATE_STEP } from 'graphql/mutations/create-step';

View File

@@ -17,7 +17,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status'; import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status';
import { UPDATE_FLOW } from 'graphql/mutations/update-flow'; import { UPDATE_FLOW } from 'graphql/mutations/update-flow';
import { GET_FLOW } from 'graphql/queries/get-flow'; import { GET_FLOW } from 'graphql/queries/get-flow';
import type { IFlow } from '@automatisch/types'; import type { IFlow } from 'types';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
export default function EditorLayout(): React.ReactElement { export default function EditorLayout(): React.ReactElement {

View File

@@ -4,7 +4,7 @@ import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import type { IExecution } from '@automatisch/types'; import type { IExecution } from 'types';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';

View File

@@ -5,7 +5,7 @@ import CardActionArea from '@mui/material/CardActionArea';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { IExecution } from '@automatisch/types'; import type { IExecution } from 'types';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';

View File

@@ -8,7 +8,7 @@ import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import type { IApp, IExecutionStep, IStep } from '@automatisch/types'; import type { IApp, IExecutionStep, IStep } from 'types';
import TabPanel from 'components/TabPanel'; import TabPanel from 'components/TabPanel';
import SearchableJSONViewer from 'components/SearchableJSONViewer'; import SearchableJSONViewer from 'components/SearchableJSONViewer';

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import type { IStep } from '@automatisch/types'; import type { IStep } from 'types';
import AppIcon from 'components/AppIcon'; import AppIcon from 'components/AppIcon';
import IntermediateStepCount from 'components/IntermediateStepCount'; import IntermediateStepCount from 'components/IntermediateStepCount';

View File

@@ -7,7 +7,7 @@ import Chip from '@mui/material/Chip';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { IFlow } from '@automatisch/types'; import type { IFlow } from 'types';
import FlowAppIcons from 'components/FlowAppIcons'; import FlowAppIcons from 'components/FlowAppIcons';
import FlowContextMenu from 'components/FlowContextMenu'; import FlowContextMenu from 'components/FlowContextMenu';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';

View File

@@ -14,13 +14,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup'; import * as yup from 'yup';
import type { BaseSchema } from 'yup'; import type { BaseSchema } from 'yup';
import type { import type { IApp, ITrigger, IAction, IStep, ISubstep } from 'types';
IApp,
ITrigger,
IAction,
IStep,
ISubstep,
} from '@automatisch/types';
import { EditorContext } from 'contexts/Editor'; import { EditorContext } from 'contexts/Editor';
import { StepExecutionsProvider } from 'contexts/StepExecutions'; import { StepExecutionsProvider } from 'contexts/StepExecutions';

View File

@@ -8,7 +8,7 @@ import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import RemoveIcon from '@mui/icons-material/Remove'; import RemoveIcon from '@mui/icons-material/Remove';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import type { IField, IFieldText, IFieldDropdown } from '@automatisch/types'; import type { IField, IFieldText, IFieldDropdown } from 'types';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import InputCreator from 'components/InputCreator'; import InputCreator from 'components/InputCreator';
@@ -19,7 +19,7 @@ type TGroupItem = {
operator: string; operator: string;
value: string; value: string;
id: string; id: string;
} };
type TGroup = Record<'and', TGroupItem[]>; type TGroup = Record<'and', TGroupItem[]>;
@@ -31,7 +31,7 @@ const createGroupItem = (): TGroupItem => ({
}); });
const createGroup = (): TGroup => ({ const createGroup = (): TGroup => ({
and: [createGroupItem()] and: [createGroupItem()],
}); });
const operators = [ const operators = [
@@ -69,7 +69,9 @@ const operators = [
}, },
]; ];
const createStringArgument = (argumentOptions: Omit<IFieldText, 'type' | 'required' | 'variables'>): IField => { const createStringArgument = (
argumentOptions: Omit<IFieldText, 'type' | 'required' | 'variables'>
): IField => {
return { return {
...argumentOptions, ...argumentOptions,
type: 'string', type: 'string',
@@ -78,7 +80,9 @@ const createStringArgument = (argumentOptions: Omit<IFieldText, 'type' | 'requir
}; };
}; };
const createDropdownArgument = (argumentOptions: Omit<IFieldDropdown, 'type' | 'required'>): IField => { const createDropdownArgument = (
argumentOptions: Omit<IFieldDropdown, 'type' | 'required'>
): IField => {
return { return {
...argumentOptions, ...argumentOptions,
required: true, required: true,
@@ -91,9 +95,7 @@ type FilterConditionsProps = {
}; };
function FilterConditions(props: FilterConditionsProps): React.ReactElement { function FilterConditions(props: FilterConditionsProps): React.ReactElement {
const { const { stepId } = props;
stepId
} = props;
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { control, setValue, getValues } = useFormContext(); const { control, setValue, getValues } = useFormContext();
const groups = useWatch({ control, name: 'parameters.or' }); const groups = useWatch({ control, name: 'parameters.or' });
@@ -110,7 +112,7 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
const appendGroup = React.useCallback(() => { const appendGroup = React.useCallback(() => {
const values = getValues('parameters.or'); const values = getValues('parameters.or');
setValue('parameters.or', values.concat(createGroup())) setValue('parameters.or', values.concat(createGroup()));
}, []); }, []);
const appendGroupItem = React.useCallback((index) => { const appendGroupItem = React.useCallback((index) => {
@@ -124,65 +126,107 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
if (group.length === 1) { if (group.length === 1) {
const groups: TGroup[] = getValues('parameters.or'); const groups: TGroup[] = getValues('parameters.or');
setValue('parameters.or', groups.filter((group, index) => index !== groupIndex)); setValue(
'parameters.or',
groups.filter((group, index) => index !== groupIndex)
);
} else { } else {
setValue(`parameters.or.${groupIndex}.and`, group.filter((groupItem, index) => index !== groupItemIndex)); setValue(
`parameters.or.${groupIndex}.and`,
group.filter((groupItem, index) => index !== groupItemIndex)
);
} }
}, []); }, []);
return ( return (
<React.Fragment> <React.Fragment>
<Stack sx={{ width: "100%" }} direction="column" spacing={2} mt={2}> <Stack sx={{ width: '100%' }} direction="column" spacing={2} mt={2}>
{groups?.map((group: TGroup, groupIndex: number) => ( {groups?.map((group: TGroup, groupIndex: number) => (
<> <>
{groupIndex !== 0 && <Divider />} {groupIndex !== 0 && <Divider />}
<Typography variant="subtitle2" gutterBottom> <Typography variant="subtitle2" gutterBottom>
{groupIndex === 0 && formatMessage('filterConditions.onlyContinueIf')} {groupIndex === 0 &&
{groupIndex !== 0 && formatMessage('filterConditions.orContinueIf')} formatMessage('filterConditions.onlyContinueIf')}
{groupIndex !== 0 &&
formatMessage('filterConditions.orContinueIf')}
</Typography> </Typography>
{group?.and?.map((groupItem: TGroupItem, groupItemIndex: number) => ( {group?.and?.map(
<Stack direction="row" spacing={2} key={`item-${groupItem.id}`}> (groupItem: TGroupItem, groupItemIndex: number) => (
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 2 }} sx={{ display: 'flex', flex: 1 }}> <Stack direction="row" spacing={2} key={`item-${groupItem.id}`}>
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}> <Stack
<InputCreator direction={{ xs: 'column', sm: 'row' }}
schema={createStringArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.key`, label: 'Choose field' })} spacing={{ xs: 2 }}
namePrefix="parameters" sx={{ display: 'flex', flex: 1 }}
stepId={stepId} >
disabled={editorContext.readOnly} <Box
/> sx={{
</Box> display: 'flex',
flex: '1 0 0px',
maxWidth: ['100%', '33%'],
}}
>
<InputCreator
schema={createStringArgument({
key: `or.${groupIndex}.and.${groupItemIndex}.key`,
label: 'Choose field',
})}
namePrefix="parameters"
stepId={stepId}
disabled={editorContext.readOnly}
/>
</Box>
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}> <Box
<InputCreator sx={{
schema={createDropdownArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.operator`, options: operators, label: 'Choose condition' })} display: 'flex',
namePrefix="parameters" flex: '1 0 0px',
stepId={stepId} maxWidth: ['100%', '33%'],
disabled={editorContext.readOnly} }}
/> >
</Box> <InputCreator
schema={createDropdownArgument({
key: `or.${groupIndex}.and.${groupItemIndex}.operator`,
options: operators,
label: 'Choose condition',
})}
namePrefix="parameters"
stepId={stepId}
disabled={editorContext.readOnly}
/>
</Box>
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}> <Box
<InputCreator sx={{
schema={createStringArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.value`, label: 'Enter text' })} display: 'flex',
namePrefix="parameters" flex: '1 0 0px',
stepId={stepId} maxWidth: ['100%', '33%'],
disabled={editorContext.readOnly} }}
/> >
</Box> <InputCreator
schema={createStringArgument({
key: `or.${groupIndex}.and.${groupItemIndex}.value`,
label: 'Enter text',
})}
namePrefix="parameters"
stepId={stepId}
disabled={editorContext.readOnly}
/>
</Box>
</Stack>
<IconButton
size="small"
edge="start"
onClick={() => removeGroupItem(groupIndex, groupItemIndex)}
sx={{ width: 61, height: 61 }}
>
<RemoveIcon />
</IconButton>
</Stack> </Stack>
)
<IconButton )}
size="small"
edge="start"
onClick={() => removeGroupItem(groupIndex, groupItemIndex)}
sx={{ width: 61, height: 61 }}
>
<RemoveIcon />
</IconButton>
</Stack>
))}
<Stack spacing={1} direction="row"> <Stack spacing={1} direction="row">
<IconButton <IconButton
@@ -194,14 +238,16 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
<AddIcon /> And <AddIcon /> And
</IconButton> </IconButton>
{(groups.length - 1) === groupIndex && <IconButton {groups.length - 1 === groupIndex && (
size="small" <IconButton
edge="start" size="small"
onClick={appendGroup} edge="start"
sx={{ width: 61, height: 61 }} onClick={appendGroup}
> sx={{ width: 61, height: 61 }}
<AddIcon /> Or >
</IconButton>} <AddIcon /> Or
</IconButton>
)}
</Stack> </Stack>
</> </>
))} ))}

View File

@@ -4,7 +4,7 @@ import Collapse from '@mui/material/Collapse';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import type { IStep, ISubstep } from '@automatisch/types'; import type { IStep, ISubstep } from 'types';
import { EditorContext } from 'contexts/Editor'; import { EditorContext } from 'contexts/Editor';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
@@ -54,7 +54,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
pb: 3, pb: 3,
flexDirection: 'column', flexDirection: 'column',
alignItems: 'flex-start', alignItems: 'flex-start',
position: 'relative' position: 'relative',
}} }}
> >
{!!args?.length && ( {!!args?.length && (

View File

@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import MuiTextField from '@mui/material/TextField'; import MuiTextField from '@mui/material/TextField';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import type { IField, IFieldDropdownOption } from '@automatisch/types'; import type { IField, IFieldDropdownOption } from 'types';
import useDynamicFields from 'hooks/useDynamicFields'; import useDynamicFields from 'hooks/useDynamicFields';
import useDynamicData from 'hooks/useDynamicData'; import useDynamicData from 'hooks/useDynamicData';

View File

@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { JSONTree } from 'react-json-tree'; import { JSONTree } from 'react-json-tree';
import type { IJSONObject } from '@automatisch/types'; import type { IJSONObject } from 'types';
type JSONViewerProps = { type JSONViewerProps = {
data: IJSONObject; data: IJSONObject;

View File

@@ -14,7 +14,7 @@ import Typography from '@mui/material/Typography';
import * as React from 'react'; import * as React from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { IPermissionCatalog } from '@automatisch/types'; import { IPermissionCatalog } from 'types';
import ControlledCheckbox from 'components/ControlledCheckbox'; import ControlledCheckbox from 'components/ControlledCheckbox';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
@@ -118,7 +118,9 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
{action.subjects.includes(subject) && ( {action.subjects.includes(subject) && (
<ControlledCheckbox <ControlledCheckbox
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`} name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
dataTest={`${condition.key}-${action.key.toLowerCase()}-checkbox`} dataTest={`${
condition.key
}-${action.key.toLowerCase()}-checkbox`}
defaultValue={defaultChecked} defaultValue={defaultChecked}
disabled={ disabled={
getValues( getValues(

View File

@@ -1,4 +1,4 @@
import type { IStep } from '@automatisch/types'; import type { IStep } from 'types';
import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore'; import ExpandMore from '@mui/icons-material/ExpandMore';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
@@ -20,7 +20,7 @@ type SuggestionsProps = {
data: { data: {
id: string; id: string;
name: string; name: string;
output: Record<string, unknown>[] output: Record<string, unknown>[];
}[]; }[];
onSuggestionClick: (variable: any) => void; onSuggestionClick: (variable: any) => void;
}; };
@@ -31,69 +31,73 @@ const LIST_ITEM_HEIGHT = 64;
const computeListHeight = (currentLength: number) => { const computeListHeight = (currentLength: number) => {
const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength); const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength);
return LIST_ITEM_HEIGHT * numberOfRenderedItems; return LIST_ITEM_HEIGHT * numberOfRenderedItems;
} };
const getPartialArray = (array: any[], length = array.length) => { const getPartialArray = (array: any[], length = array.length) => {
return array.slice(0, length); return array.slice(0, length);
}; };
const renderItemFactory = ({ onSuggestionClick }: Pick<SuggestionsProps, 'onSuggestionClick'>) => (props: ListChildComponentProps) => { const renderItemFactory =
const { index, style, data } = props; ({ onSuggestionClick }: Pick<SuggestionsProps, 'onSuggestionClick'>) =>
(props: ListChildComponentProps) => {
const { index, style, data } = props;
const suboption = data[index]; const suboption = data[index];
return ( return (
<ListItemButton <ListItemButton
sx={{ pl: 4 }} sx={{ pl: 4 }}
divider divider
onClick={() => onSuggestionClick(suboption)} onClick={() => onSuggestionClick(suboption)}
data-test="power-input-suggestion-item" data-test="power-input-suggestion-item"
key={index} key={index}
style={style} style={style}
> >
<ListItemText <ListItemText
primary={suboption.label} primary={suboption.label}
primaryTypographyProps={{ primaryTypographyProps={{
variant: 'subtitle1', variant: 'subtitle1',
title: 'Property name', title: 'Property name',
sx: { fontWeight: 700 }, sx: { fontWeight: 700 },
}} }}
secondary={suboption.sampleValue || ''} secondary={suboption.sampleValue || ''}
secondaryTypographyProps={{ secondaryTypographyProps={{
variant: 'subtitle2', variant: 'subtitle2',
title: 'Sample value', title: 'Sample value',
noWrap: true, noWrap: true,
}} }}
/> />
</ListItemButton> </ListItemButton>
); );
} };
const Suggestions = (props: SuggestionsProps) => { const Suggestions = (props: SuggestionsProps) => {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { const { data, onSuggestionClick = () => null } = props;
data,
onSuggestionClick = () => null
} = props;
const [current, setCurrent] = React.useState<number | null>(0); const [current, setCurrent] = React.useState<number | null>(0);
const [listLength, setListLength] = React.useState<number>(SHORT_LIST_LENGTH); const [listLength, setListLength] = React.useState<number>(SHORT_LIST_LENGTH);
const [filteredData, setFilteredData] = React.useState<any[]>( const [filteredData, setFilteredData] = React.useState<any[]>(data);
data
React.useEffect(
function syncOptions() {
setFilteredData((filteredData) => {
if (filteredData.length === 0 && filteredData.length !== data.length) {
return data;
}
return filteredData;
});
},
[data]
); );
React.useEffect(function syncOptions() { const renderItem = React.useMemo(
setFilteredData((filteredData) => { () =>
if (filteredData.length === 0 && filteredData.length !== data.length) { renderItemFactory({
return data; onSuggestionClick,
} }),
[onSuggestionClick]
return filteredData; );
})
}, [data]);
const renderItem = React.useMemo(() => renderItemFactory({
onSuggestionClick
}), [onSuggestionClick]);
const expandList = () => { const expandList = () => {
setListLength(Infinity); setListLength(Infinity);
@@ -122,12 +126,12 @@ const Suggestions = (props: SuggestionsProps) => {
return { return {
id: stepWithOutput.id, id: stepWithOutput.id,
name: stepWithOutput.name, name: stepWithOutput.name,
output: stepWithOutput.output output: stepWithOutput.output.filter((option) =>
.filter(option => `${option.label}\n${option.sampleValue}` `${option.label}\n${option.sampleValue}`
.toLowerCase() .toLowerCase()
.includes(search.toLowerCase()) .includes(search.toLowerCase())
) ),
} };
}) })
.filter((stepWithOutput) => stepWithOutput.output.length); .filter((stepWithOutput) => stepWithOutput.output.length);
@@ -161,14 +165,27 @@ const Suggestions = (props: SuggestionsProps) => {
(current === index ? <ExpandLess /> : <ExpandMore />)} (current === index ? <ExpandLess /> : <ExpandMore />)}
</ListItemButton> </ListItemButton>
<Collapse in={current === index || filteredData.length === 1} timeout="auto" unmountOnExit> <Collapse
in={current === index || filteredData.length === 1}
timeout="auto"
unmountOnExit
>
<FixedSizeList <FixedSizeList
height={computeListHeight(getPartialArray((option.output as any) || [], listLength).length)} height={computeListHeight(
getPartialArray((option.output as any) || [], listLength)
.length
)}
width="100%" width="100%"
itemSize={LIST_ITEM_HEIGHT} itemSize={LIST_ITEM_HEIGHT}
itemCount={getPartialArray((option.output as any) || [], listLength).length} itemCount={
getPartialArray((option.output as any) || [], listLength)
.length
}
overscanCount={2} overscanCount={2}
itemData={getPartialArray((option.output as any) || [], listLength)} itemData={getPartialArray(
(option.output as any) || [],
listLength
)}
data-test="power-input-suggestion-group" data-test="power-input-suggestion-group"
> >
{renderItem} {renderItem}

View File

@@ -1,4 +1,4 @@
import type { IStep } from '@automatisch/types'; import type { IStep } from 'types';
const joinBy = (delimiter = '.', ...args: string[]) => const joinBy = (delimiter = '.', ...args: string[]) =>
args.filter(Boolean).join(delimiter); args.filter(Boolean).join(delimiter);
@@ -10,7 +10,12 @@ type TProcessPayload = {
parentLabel?: string; parentLabel?: string;
}; };
const process = ({ data, parentKey, index, parentLabel = '' }: TProcessPayload): any[] => { const process = ({
data,
parentKey,
index,
parentLabel = '',
}: TProcessPayload): any[] => {
if (typeof data !== 'object') { if (typeof data !== 'object') {
return [ return [
{ {
@@ -24,27 +29,19 @@ const process = ({ data, parentKey, index, parentLabel = '' }: TProcessPayload):
const entries = Object.entries(data); const entries = Object.entries(data);
return entries.flatMap(([name, sampleValue]) => { return entries.flatMap(([name, sampleValue]) => {
const label = joinBy( const label = joinBy('.', parentLabel, (index as number)?.toString(), name);
'.',
parentLabel,
(index as number)?.toString(),
name
);
const value = joinBy( const value = joinBy('.', parentKey, (index as number)?.toString(), name);
'.',
parentKey,
(index as number)?.toString(),
name
);
if (Array.isArray(sampleValue)) { if (Array.isArray(sampleValue)) {
return sampleValue.flatMap((item, index) => process({ return sampleValue.flatMap((item, index) =>
data: item, process({
parentKey: value, data: item,
index, parentKey: value,
parentLabel: label index,
})); parentLabel: label,
})
);
} }
if (typeof sampleValue === 'object' && sampleValue !== null) { if (typeof sampleValue === 'object' && sampleValue !== null) {
@@ -77,8 +74,9 @@ export const processStepWithExecutions = (steps: IStep[]): any[] => {
.map((step: IStep, index: number) => ({ .map((step: IStep, index: number) => ({
id: step.id, id: step.id,
// TODO: replace with step.name once introduced // TODO: replace with step.name once introduced
name: `${index + 1}. ${(step.appKey || '').charAt(0)?.toUpperCase() + step.appKey?.slice(1) name: `${index + 1}. ${
}`, (step.appKey || '').charAt(0)?.toUpperCase() + step.appKey?.slice(1)
}`,
output: process({ output: process({
data: step.executionSteps?.[0]?.dataOut || {}, data: step.executionSteps?.[0]?.dataOut || {},
parentKey: `step.${step.id}`, parentKey: `step.${step.id}`,

View File

@@ -3,7 +3,7 @@ import throttle from 'lodash/throttle';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { IJSONObject } from '@automatisch/types'; import { IJSONObject } from 'types';
import JSONViewer from 'components/JSONViewer'; import JSONViewer from 'components/JSONViewer';
import SearchInput from 'components/SearchInput'; import SearchInput from 'components/SearchInput';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';

View File

@@ -1,7 +1,7 @@
import { Text, Descendant } from 'slate'; import { Text, Descendant } from 'slate';
import { withHistory } from 'slate-history'; import { withHistory } from 'slate-history';
import { ReactEditor, withReact } from 'slate-react'; import { ReactEditor, withReact } from 'slate-react';
import { IFieldDropdownOption } from '@automatisch/types'; import { IFieldDropdownOption } from 'types';
import type { import type {
CustomEditor, CustomEditor,

View File

@@ -13,7 +13,7 @@ import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
import JSONViewer from 'components/JSONViewer'; import JSONViewer from 'components/JSONViewer';
import WebhookUrlInfo from 'components/WebhookUrlInfo'; import WebhookUrlInfo from 'components/WebhookUrlInfo';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import type { IStep, ISubstep } from '@automatisch/types'; import type { IStep, ISubstep } from 'types';
type TestSubstepProps = { type TestSubstepProps = {
substep: ISubstep; substep: ISubstep;
@@ -62,7 +62,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
EXECUTE_FLOW, EXECUTE_FLOW,
{ {
refetchQueries: ['GetStepWithTestExecutions'], refetchQueries: ['GetStepWithTestExecutions'],
context: { autoSnackbar: false } context: { autoSnackbar: false },
} }
); );
const response = data?.executeFlow?.data; const response = data?.executeFlow?.data;

View File

@@ -5,7 +5,7 @@ import get from 'lodash/get';
import set from 'lodash/set'; import set from 'lodash/set';
import * as React from 'react'; import * as React from 'react';
import { IJSONObject } from '@automatisch/types'; import { IJSONObject } from 'types';
import useConfig from 'hooks/useConfig'; import useConfig from 'hooks/useConfig';
import useAutomatischInfo from 'hooks/useAutomatischInfo'; import useAutomatischInfo from 'hooks/useAutomatischInfo';
import { defaultTheme, mationTheme } from 'styles/theme'; import { defaultTheme, mationTheme } from 'styles/theme';

View File

@@ -11,7 +11,7 @@ import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { TBillingCardAction } from '@automatisch/types'; import { TBillingCardAction } from 'types';
import TrialOverAlert from 'components/TrialOverAlert/index.ee'; import TrialOverAlert from 'components/TrialOverAlert/index.ee';
import SubscriptionCancelledAlert from 'components/SubscriptionCancelledAlert/index.ee'; import SubscriptionCancelledAlert from 'components/SubscriptionCancelledAlert/index.ee';
import CheckoutCompletedAlert from 'components/CheckoutCompletedAlert/index.ee'; import CheckoutCompletedAlert from 'components/CheckoutCompletedAlert/index.ee';

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import type { IStep } from '@automatisch/types'; import type { IStep } from 'types';
export const StepExecutionsContext = React.createContext<IStep[]>([]); export const StepExecutionsContext = React.createContext<IStep[]>([]);

View File

@@ -1,4 +1,4 @@
import type { IAuthenticationStep, IJSONObject } from '@automatisch/types'; import type { IAuthenticationStep, IJSONObject } from 'types';
import apolloClient from 'graphql/client'; import apolloClient from 'graphql/client';
import MUTATIONS from 'graphql/mutations'; import MUTATIONS from 'graphql/mutations';

View File

@@ -1,5 +1,5 @@
import template from 'lodash/template'; import template from 'lodash/template';
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types'; import type { IAuthenticationStepField, IJSONObject } from 'types';
const interpolate = /{([\s\S]+?)}/g; const interpolate = /{([\s\S]+?)}/g;

View File

@@ -1,4 +1,4 @@
import { IRole, IPermission } from '@automatisch/types'; import { IRole, IPermission } from 'types';
type ComputeAction = { type ComputeAction = {
conditions: Record<string, boolean>; conditions: Record<string, boolean>;

View File

@@ -1,5 +1,5 @@
import template from 'lodash/template'; import template from 'lodash/template';
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types'; import type { IAuthenticationStepField, IJSONObject } from 'types';
const interpolate = /{([\s\S]+?)}/g; const interpolate = /{([\s\S]+?)}/g;

View File

@@ -1,4 +1,4 @@
import { IJSONObject } from '@automatisch/types'; import { IJSONObject } from 'types';
import set from 'lodash/set'; import set from 'lodash/set';
export default function nestObject<T = IJSONObject>( export default function nestObject<T = IJSONObject>(

View File

@@ -1,5 +1,9 @@
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability'; import {
import { IUser } from '@automatisch/types'; PureAbility,
fieldPatternMatcher,
mongoQueryMatcher,
} from '@casl/ability';
import { IUser } from 'types';
// Must be kept in sync with `packages/backend/src/helpers/user-ability.ts`! // Must be kept in sync with `packages/backend/src/helpers/user-ability.ts`!
export default function userAbility(user: IUser) { export default function userAbility(user: IUser) {
@@ -9,7 +13,7 @@ export default function userAbility(user: IUser) {
// We're not using mongo, but our fields, conditions match // We're not using mongo, but our fields, conditions match
const options = { const options = {
conditionsMatcher: mongoQueryMatcher, conditionsMatcher: mongoQueryMatcher,
fieldMatcher: fieldPatternMatcher fieldMatcher: fieldPatternMatcher,
}; };
if (!role || !permissions) { if (!role || !permissions) {

View File

@@ -1,22 +1,16 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IApp } from '@automatisch/types'; import { IApp } from 'types';
import { GET_APP } from 'graphql/queries/get-app'; import { GET_APP } from 'graphql/queries/get-app';
type QueryResponse = { type QueryResponse = {
getApp: IApp; getApp: IApp;
} };
export default function useApp(key: string) { export default function useApp(key: string) {
const { const { data, loading } = useQuery<QueryResponse>(GET_APP, {
data, variables: { key },
loading });
} = useQuery<QueryResponse>(
GET_APP,
{
variables: { key }
}
);
const app = data?.getApp; const app = data?.getApp;
return { return {

View File

@@ -1,5 +1,5 @@
import { useLazyQuery } from '@apollo/client'; import { useLazyQuery } from '@apollo/client';
import { AppAuthClient } from '@automatisch/types'; import { AppAuthClient } from 'types';
import * as React from 'react'; import * as React from 'react';
import { GET_APP_AUTH_CLIENT } from 'graphql/queries/get-app-auth-client.ee'; import { GET_APP_AUTH_CLIENT } from 'graphql/queries/get-app-auth-client.ee';

View File

@@ -1,5 +1,5 @@
import { useLazyQuery } from '@apollo/client'; import { useLazyQuery } from '@apollo/client';
import { AppAuthClient } from '@automatisch/types'; import { AppAuthClient } from 'types';
import * as React from 'react'; import * as React from 'react';
import { GET_APP_AUTH_CLIENTS } from 'graphql/queries/get-app-auth-clients.ee'; import { GET_APP_AUTH_CLIENTS } from 'graphql/queries/get-app-auth-clients.ee';

View File

@@ -1,23 +1,17 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { AppConfig } from '@automatisch/types'; import { AppConfig } from 'types';
import { GET_APP_CONFIG } from 'graphql/queries/get-app-config.ee'; import { GET_APP_CONFIG } from 'graphql/queries/get-app-config.ee';
type QueryResponse = { type QueryResponse = {
getAppConfig: AppConfig; getAppConfig: AppConfig;
} };
export default function useAppConfig(key: string) { export default function useAppConfig(key: string) {
const { const { data, loading } = useQuery<QueryResponse>(GET_APP_CONFIG, {
data, variables: { key },
loading context: { autoSnackbar: false },
} = useQuery<QueryResponse>( });
GET_APP_CONFIG,
{
variables: { key },
context: { autoSnackbar: false }
}
);
const appConfig = data?.getAppConfig; const appConfig = data?.getAppConfig;
return { return {

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IApp } from '@automatisch/types'; import { IApp } from 'types';
import { GET_APPS } from 'graphql/queries/get-apps'; import { GET_APPS } from 'graphql/queries/get-apps';

View File

@@ -1,4 +1,4 @@
import { IApp } from '@automatisch/types'; import { IApp } from 'types';
import * as React from 'react'; import * as React from 'react';
import { processStep } from 'helpers/authenticationSteps'; import { processStep } from 'helpers/authenticationSteps';
@@ -10,14 +10,18 @@ type UseAuthenticateAppParams = {
appAuthClientId?: string; appAuthClientId?: string;
useShared?: boolean; useShared?: boolean;
connectionId?: string; connectionId?: string;
} };
type AuthenticatePayload = { type AuthenticatePayload = {
fields?: Record<string, string>; fields?: Record<string, string>;
appAuthClientId?: string; appAuthClientId?: string;
} };
function getSteps(auth: IApp['auth'], hasConnection: boolean, useShared: boolean) { function getSteps(
auth: IApp['auth'],
hasConnection: boolean,
useShared: boolean
) {
if (hasConnection) { if (hasConnection) {
if (useShared) { if (useShared) {
return auth?.sharedReconnectionSteps; return auth?.sharedReconnectionSteps;
@@ -34,26 +38,17 @@ function getSteps(auth: IApp['auth'], hasConnection: boolean, useShared: boolean
} }
export default function useAuthenticateApp(payload: UseAuthenticateAppParams) { export default function useAuthenticateApp(payload: UseAuthenticateAppParams) {
const { const { appKey, appAuthClientId, connectionId, useShared = false } = payload;
appKey,
appAuthClientId,
connectionId,
useShared = false,
} = payload;
const { app } = useApp(appKey); const { app } = useApp(appKey);
const [ const [authenticationInProgress, setAuthenticationInProgress] =
authenticationInProgress, React.useState(false);
setAuthenticationInProgress
] = React.useState(false);
const steps = getSteps(app?.auth, !!connectionId, useShared); const steps = getSteps(app?.auth, !!connectionId, useShared);
const authenticate = React.useMemo(() => { const authenticate = React.useMemo(() => {
if (!steps?.length) return; if (!steps?.length) return;
return async function authenticate(payload: AuthenticatePayload = {}) { return async function authenticate(payload: AuthenticatePayload = {}) {
const { const { fields } = payload;
fields,
} = payload;
setAuthenticationInProgress(true); setAuthenticationInProgress(true);
const response: Record<string, any> = { const response: Record<string, any> = {
@@ -62,7 +57,7 @@ export default function useAuthenticateApp(payload: UseAuthenticateAppParams) {
connection: { connection: {
id: connectionId, id: connectionId,
}, },
fields fields,
}; };
let stepIndex = 0; let stepIndex = 0;
@@ -90,7 +85,7 @@ export default function useAuthenticateApp(payload: UseAuthenticateAppParams) {
setAuthenticationInProgress(false); setAuthenticationInProgress(false);
} }
} };
}, [steps, appKey, appAuthClientId, connectionId]); }, [steps, appKey, appAuthClientId, connectionId]);
return { return {

View File

@@ -2,15 +2,21 @@ import * as React from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { TSubscription } from '@automatisch/types'; import { TSubscription } from 'types';
import { GET_BILLING_AND_USAGE } from 'graphql/queries/get-billing-and-usage.ee'; import { GET_BILLING_AND_USAGE } from 'graphql/queries/get-billing-and-usage.ee';
function transform(billingAndUsageData: NonNullable<UseBillingAndUsageDataReturn>) { function transform(
billingAndUsageData: NonNullable<UseBillingAndUsageDataReturn>
) {
const nextBillDate = billingAndUsageData.subscription.nextBillDate; const nextBillDate = billingAndUsageData.subscription.nextBillDate;
const nextBillDateTitle = nextBillDate.title; const nextBillDateTitle = nextBillDate.title;
const nextBillDateTitleDateObject = DateTime.fromMillis(Number(nextBillDateTitle)); const nextBillDateTitleDateObject = DateTime.fromMillis(
const formattedNextBillDateTitle = nextBillDateTitleDateObject.isValid ? nextBillDateTitleDateObject.toFormat('LLL dd, yyyy') : nextBillDateTitle; Number(nextBillDateTitle)
);
const formattedNextBillDateTitle = nextBillDateTitleDateObject.isValid
? nextBillDateTitleDateObject.toFormat('LLL dd, yyyy')
: nextBillDateTitle;
return { return {
...billingAndUsageData, ...billingAndUsageData,
@@ -19,8 +25,8 @@ function transform(billingAndUsageData: NonNullable<UseBillingAndUsageDataReturn
nextBillDate: { nextBillDate: {
...billingAndUsageData.subscription.nextBillDate, ...billingAndUsageData.subscription.nextBillDate,
title: formattedNextBillDateTitle, title: formattedNextBillDateTitle,
} },
} },
}; };
} }
@@ -28,27 +34,35 @@ type UseBillingAndUsageDataReturn = {
subscription: TSubscription; subscription: TSubscription;
usage: { usage: {
task: number; task: number;
} };
} | null; } | null;
export default function useBillingAndUsageData(): UseBillingAndUsageDataReturn { export default function useBillingAndUsageData(): UseBillingAndUsageDataReturn {
const location = useLocation(); const location = useLocation();
const state = location.state as { checkoutCompleted: boolean }; const state = location.state as { checkoutCompleted: boolean };
const { data, loading, startPolling, stopPolling } = useQuery(GET_BILLING_AND_USAGE); const { data, loading, startPolling, stopPolling } = useQuery(
GET_BILLING_AND_USAGE
);
const checkoutCompleted = state?.checkoutCompleted; const checkoutCompleted = state?.checkoutCompleted;
const hasSubscription = !!data?.getBillingAndUsage?.subscription?.status; const hasSubscription = !!data?.getBillingAndUsage?.subscription?.status;
React.useEffect(function pollDataUntilSubscriptionIsCreated() { React.useEffect(
if (checkoutCompleted && !hasSubscription) { function pollDataUntilSubscriptionIsCreated() {
startPolling(1000); if (checkoutCompleted && !hasSubscription) {
} startPolling(1000);
}, [checkoutCompleted, hasSubscription, startPolling]); }
},
[checkoutCompleted, hasSubscription, startPolling]
);
React.useEffect(function stopPollingWhenSubscriptionIsCreated() { React.useEffect(
if (checkoutCompleted && hasSubscription) { function stopPollingWhenSubscriptionIsCreated() {
stopPolling(); if (checkoutCompleted && hasSubscription) {
} stopPolling();
}, [checkoutCompleted, hasSubscription, stopPolling]); }
},
[checkoutCompleted, hasSubscription, stopPolling]
);
if (loading) return null; if (loading) return null;

View File

@@ -1,14 +1,16 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IJSONObject } from '@automatisch/types'; import { IJSONObject } from 'types';
import { GET_CONFIG } from 'graphql/queries/get-config.ee'; import { GET_CONFIG } from 'graphql/queries/get-config.ee';
type QueryResponse = { type QueryResponse = {
getConfig: IJSONObject; getConfig: IJSONObject;
} };
export default function useConfig(keys?: string[]) { export default function useConfig(keys?: string[]) {
const { data, loading } = useQuery<QueryResponse>(GET_CONFIG, { variables: { keys } }); const { data, loading } = useQuery<QueryResponse>(GET_CONFIG, {
variables: { keys },
});
return { return {
config: data?.getConfig, config: data?.getConfig,

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IUser } from '@automatisch/types'; import { IUser } from 'types';
import { GET_CURRENT_USER } from 'graphql/queries/get-current-user'; import { GET_CURRENT_USER } from 'graphql/queries/get-current-user';

View File

@@ -4,11 +4,7 @@ import { useFormContext } from 'react-hook-form';
import set from 'lodash/set'; import set from 'lodash/set';
import type { UseFormReturn } from 'react-hook-form'; import type { UseFormReturn } from 'react-hook-form';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import type { import type { IField, IFieldDropdownSource, IJSONObject } from 'types';
IField,
IFieldDropdownSource,
IJSONObject,
} from '@automatisch/types';
import { GET_DYNAMIC_DATA } from 'graphql/queries/get-dynamic-data'; import { GET_DYNAMIC_DATA } from 'graphql/queries/get-dynamic-data';

View File

@@ -8,7 +8,7 @@ import type {
IField, IField,
IFieldDropdownAdditionalFields, IFieldDropdownAdditionalFields,
IJSONObject, IJSONObject,
} from '@automatisch/types'; } from 'types';
import { GET_DYNAMIC_FIELDS } from 'graphql/queries/get-dynamic-fields'; import { GET_DYNAMIC_FIELDS } from 'graphql/queries/get-dynamic-fields';

View File

@@ -1,10 +1,10 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { TInvoice } from '@automatisch/types'; import { TInvoice } from 'types';
import { GET_INVOICES } from 'graphql/queries/get-invoices.ee'; import { GET_INVOICES } from 'graphql/queries/get-invoices.ee';
type UseInvoicesReturn = { type UseInvoicesReturn = {
invoices: TInvoice[], invoices: TInvoice[];
loading: boolean; loading: boolean;
}; };
@@ -13,6 +13,6 @@ export default function useInvoices(): UseInvoicesReturn {
return { return {
invoices: data?.getInvoices || [], invoices: data?.getInvoices || [],
loading: loading loading: loading,
}; };
} }

View File

@@ -1,12 +1,12 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import type { Notification } from '@automatisch/types'; import type { Notification } from 'types';
import { GET_NOTIFICATIONS } from 'graphql/queries/get-notifications'; import { GET_NOTIFICATIONS } from 'graphql/queries/get-notifications';
type UseNotificationsReturn = { type UseNotificationsReturn = {
notifications: Notification[]; notifications: Notification[];
loading: boolean; loading: boolean;
} };
export default function useNotifications(): UseNotificationsReturn { export default function useNotifications(): UseNotificationsReturn {
const { data, loading } = useQuery(GET_NOTIFICATIONS); const { data, loading } = useQuery(GET_NOTIFICATIONS);

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { TPaymentPlan } from '@automatisch/types'; import { TPaymentPlan } from 'types';
import { GET_PAYMENT_PLANS } from 'graphql/queries/get-payment-plans.ee'; import { GET_PAYMENT_PLANS } from 'graphql/queries/get-payment-plans.ee';
type UsePaymentPlansReturn = { type UsePaymentPlansReturn = {
@@ -13,6 +13,6 @@ export default function usePaymentPlans(): UsePaymentPlansReturn {
return { return {
plans: data?.getPaymentPlans || [], plans: data?.getPaymentPlans || [],
loading loading,
}; };
} }

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IPermissionCatalog } from '@automatisch/types'; import { IPermissionCatalog } from 'types';
import { GET_PERMISSION_CATALOG } from 'graphql/queries/get-permission-catalog.ee'; import { GET_PERMISSION_CATALOG } from 'graphql/queries/get-permission-catalog.ee';

View File

@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { useLazyQuery } from '@apollo/client'; import { useLazyQuery } from '@apollo/client';
import { IRole } from '@automatisch/types'; import { IRole } from 'types';
import { GET_ROLE } from 'graphql/queries/get-role.ee'; import { GET_ROLE } from 'graphql/queries/get-role.ee';

View File

@@ -1,17 +1,19 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IRole } from '@automatisch/types'; import { IRole } from 'types';
import { GET_ROLES } from 'graphql/queries/get-roles.ee'; import { GET_ROLES } from 'graphql/queries/get-roles.ee';
type QueryResponse = { type QueryResponse = {
getRoles: IRole[]; getRoles: IRole[];
} };
export default function useRoles() { export default function useRoles() {
const { data, loading } = useQuery<QueryResponse>(GET_ROLES, { context: { autoSnackbar: false } }); const { data, loading } = useQuery<QueryResponse>(GET_ROLES, {
context: { autoSnackbar: false },
});
return { return {
roles: data?.getRoles || [], roles: data?.getRoles || [],
loading loading,
}; };
} }

View File

@@ -1,6 +1,6 @@
import { QueryResult, useQuery } from '@apollo/client'; import { QueryResult, useQuery } from '@apollo/client';
import { TSamlAuthProvider } from '@automatisch/types'; import { TSamlAuthProvider } from 'types';
import { GET_SAML_AUTH_PROVIDER } from 'graphql/queries/get-saml-auth-provider'; import { GET_SAML_AUTH_PROVIDER } from 'graphql/queries/get-saml-auth-provider';
type UseSamlAuthProviderReturn = { type UseSamlAuthProviderReturn = {

View File

@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { useLazyQuery } from '@apollo/client'; import { useLazyQuery } from '@apollo/client';
import { TSamlAuthProviderRole } from '@automatisch/types'; import { TSamlAuthProviderRole } from 'types';
import { GET_SAML_AUTH_PROVIDER_ROLE_MAPPINGS } from 'graphql/queries/get-saml-auth-provider-role-mappings'; import { GET_SAML_AUTH_PROVIDER_ROLE_MAPPINGS } from 'graphql/queries/get-saml-auth-provider-role-mappings';

View File

@@ -1,6 +1,6 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { TSamlAuthProvider } from '@automatisch/types'; import { TSamlAuthProvider } from 'types';
import { LIST_SAML_AUTH_PROVIDERS } from 'graphql/queries/list-saml-auth-providers.ee'; import { LIST_SAML_AUTH_PROVIDERS } from 'graphql/queries/list-saml-auth-providers.ee';
type UseSamlAuthProvidersReturn = { type UseSamlAuthProvidersReturn = {

View File

@@ -1,12 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { useLazyQuery } from '@apollo/client'; import { useLazyQuery } from '@apollo/client';
import { IUser } from '@automatisch/types'; import { IUser } from 'types';
import { GET_USER } from 'graphql/queries/get-user'; import { GET_USER } from 'graphql/queries/get-user';
type QueryResponse = { type QueryResponse = {
getUser: IUser; getUser: IUser;
} };
export default function useUser(userId?: string) { export default function useUser(userId?: string) {
const [getUser, { data, loading }] = useLazyQuery<QueryResponse>(GET_USER); const [getUser, { data, loading }] = useLazyQuery<QueryResponse>(GET_USER);
@@ -15,14 +15,14 @@ export default function useUser(userId?: string) {
if (userId) { if (userId) {
getUser({ getUser({
variables: { variables: {
id: userId id: userId,
} },
}); });
} }
}, [userId]); }, [userId]);
return { return {
user: data?.getUser, user: data?.getUser,
loading loading,
}; };
} }

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { IUser } from '@automatisch/types'; import { IUser } from 'types';
import { GET_USERS } from 'graphql/queries/get-users'; import { GET_USERS } from 'graphql/queries/get-users';

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