chore: introduce @automatisch/types

This commit is contained in:
Ali BARIN
2022-03-01 22:56:19 +01:00
committed by Ömer Faruk Aydın
parent bbb6f0b0ff
commit 3391578655
54 changed files with 377 additions and 297 deletions

View File

@@ -15,7 +15,8 @@
"nohoist": [ "nohoist": [
"**/babel-loader", "**/babel-loader",
"**/webpack", "**/webpack",
"**/@automatisch/web" "**/@automatisch/web",
"**/@automatisch/types"
] ]
}, },
"devDependencies": { "devDependencies": {

View File

@@ -18,7 +18,6 @@
"dependencies": { "dependencies": {
"@automatisch/web": "0.1.0", "@automatisch/web": "0.1.0",
"@octokit/oauth-methods": "^1.2.6", "@octokit/oauth-methods": "^1.2.6",
"@types/lodash.get": "^4.4.6",
"axios": "0.24.0", "axios": "0.24.0",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
@@ -66,11 +65,13 @@
"url": "https://github.com/automatisch/automatisch/issues" "url": "https://github.com/automatisch/automatisch/issues"
}, },
"devDependencies": { "devDependencies": {
"@automatisch/types": "0.1.0",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.0.2",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/http-errors": "^1.8.1", "@types/http-errors": "^1.8.1",
"@types/lodash.get": "^4.4.6",
"@types/morgan": "^1.9.3", "@types/morgan": "^1.9.3",
"@types/node": "^16.10.2", "@types/node": "^16.10.2",
"@types/nodemailer": "^6.4.4", "@types/nodemailer": "^6.4.4",

View File

@@ -1,6 +1,6 @@
import type { IField } from '@automatisch/types';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import Field from '../../types/field';
export default class Authentication { export default class Authentication {
appData: any; appData: any;
@@ -18,7 +18,7 @@ export default class Authentication {
get oauthRedirectUrl() { get oauthRedirectUrl() {
return this.appData.fields.find( return this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
).value; ).value;
} }

View File

@@ -1,13 +1,15 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { google as GoogleApi } from 'googleapis'; import { google as GoogleApi } from 'googleapis';
import { OAuth2Client } from 'google-auth-library'; import { OAuth2Client } from 'google-auth-library';
import Field from '../../types/field';
import AppInfo from '../../types/app-info';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: OAuth2Client; client: OAuth2Client;
scopes: string[] = [ scopes: string[] = [
@@ -17,7 +19,7 @@ export default class Authentication implements AuthenticationInterface {
'profile', 'profile',
]; ];
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData; this.appData = appData;
this.connectionData = connectionData; this.connectionData = connectionData;
@@ -32,7 +34,7 @@ export default class Authentication implements AuthenticationInterface {
get oauthRedirectUrl() { get oauthRedirectUrl() {
return this.appData.fields.find( return this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
).value; ).value;
} }

View File

@@ -1,16 +1,18 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import FlickrApi from 'flickr-sdk'; import FlickrApi from 'flickr-sdk';
import AppInfo from '../../types/app-info';
import Field from '../../types/field';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: typeof FlickrApi; client: typeof FlickrApi;
oauthClient: typeof FlickrApi; oauthClient: typeof FlickrApi;
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.oauthClient = new FlickrApi.OAuth( this.oauthClient = new FlickrApi.OAuth(
connectionData.consumerKey, connectionData.consumerKey,
connectionData.consumerSecret connectionData.consumerSecret
@@ -33,7 +35,7 @@ export default class Authentication implements AuthenticationInterface {
async createAuthData() { async createAuthData() {
const appFields = this.appData.fields.find( const appFields = this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
); );
const callbackUrl = appFields.value; const callbackUrl = appFields.value;

View File

@@ -1,16 +1,18 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { import {
getWebFlowAuthorizationUrl, getWebFlowAuthorizationUrl,
exchangeWebFlowCode, exchangeWebFlowCode,
checkToken, checkToken,
} from '@octokit/oauth-methods'; } from '@octokit/oauth-methods';
import AppInfo from '../../types/app-info';
import Field from '../../types/field';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
scopes: string[] = ['repo']; scopes: string[] = ['repo'];
client: { client: {
getWebFlowAuthorizationUrl: typeof getWebFlowAuthorizationUrl; getWebFlowAuthorizationUrl: typeof getWebFlowAuthorizationUrl;
@@ -18,7 +20,7 @@ export default class Authentication implements AuthenticationInterface {
checkToken: typeof checkToken; checkToken: typeof checkToken;
}; };
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.connectionData = connectionData; this.connectionData = connectionData;
this.appData = appData; this.appData = appData;
@@ -31,7 +33,7 @@ export default class Authentication implements AuthenticationInterface {
get oauthRedirectUrl(): string { get oauthRedirectUrl(): string {
return this.appData.fields.find( return this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
).value; ).value;
} }

View File

@@ -1,14 +1,17 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { Client } from 'pg'; import { Client } from 'pg';
import AppInfo from '../../types/app-info';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: Client; client: Client;
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.client = new Client({ this.client = new Client({
host: connectionData.host as string, host: connectionData.host as string,
port: connectionData.port as number, port: connectionData.port as number,

View File

@@ -1,13 +1,16 @@
import nodemailer, { Transporter, TransportOptions } from 'nodemailer'; import nodemailer, { Transporter, TransportOptions } from 'nodemailer';
import AppInfo from '../../types/app-info'; import type {
import JSONObject from '../../types/interfaces/json-object'; IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
export default class Authentication { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: Transporter; client: Transporter;
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.client = nodemailer.createTransport({ this.client = nodemailer.createTransport({
host: connectionData.host, host: connectionData.host,
port: connectionData.port, port: connectionData.port,

View File

@@ -1,14 +1,16 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import TwilioApi from 'twilio'; import TwilioApi from 'twilio';
import AppInfo from '../../types/app-info';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: TwilioApi.Twilio; client: TwilioApi.Twilio;
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.client = TwilioApi( this.client = TwilioApi(
connectionData.accountSid as string, connectionData.accountSid as string,
connectionData.authToken as string connectionData.authToken as string

View File

@@ -1,9 +1,11 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import TwitchApi, { TwitchJsOptions } from 'twitch-js'; import TwitchApi, { TwitchJsOptions } from 'twitch-js';
import fetchUtil from 'twitch-js/lib/utils/fetch'; import fetchUtil from 'twitch-js/lib/utils/fetch';
import AppInfo from '../../types/app-info';
import Field from '../../types/field';
import JSONObject from '../../types/interfaces/json-object';
type TwitchTokenResponse = { type TwitchTokenResponse = {
accessToken: string; accessToken: string;
@@ -12,12 +14,12 @@ type TwitchTokenResponse = {
tokenType: string; tokenType: string;
}; };
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: TwitchApi; client: TwitchApi;
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.connectionData = connectionData; this.connectionData = connectionData;
this.appData = appData; this.appData = appData;
@@ -36,7 +38,7 @@ export default class Authentication implements AuthenticationInterface {
get oauthRedirectUrl() { get oauthRedirectUrl() {
return this.appData.fields.find( return this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
).value; ).value;
} }

View File

@@ -1,15 +1,17 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2'; import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2';
import AppInfo from '../../types/app-info';
import Field from '../../types/field';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: TwitterApi; client: TwitterApi;
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData; this.appData = appData;
this.connectionData = connectionData; this.connectionData = connectionData;
@@ -25,7 +27,7 @@ export default class Authentication implements AuthenticationInterface {
async createAuthData() { async createAuthData() {
const appFields = this.appData.fields.find( const appFields = this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
); );
const callbackUrl = appFields.value; const callbackUrl = appFields.value;

View File

@@ -1,13 +1,15 @@
import AuthenticationInterface from '../../types/interfaces/authentication-interface'; import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import AppInfo from '../../types/app-info';
import Field from '../../types/field';
import JSONObject from '../../types/interfaces/json-object';
export default class Authentication implements AuthenticationInterface { export default class Authentication implements IAuthentication {
appData: AppInfo; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: AxiosInstance = axios.create({ client: AxiosInstance = axios.create({
baseURL: 'https://api.typeform.com', baseURL: 'https://api.typeform.com',
}); });
@@ -22,14 +24,14 @@ export default class Authentication implements AuthenticationInterface {
'workspaces:read', 'workspaces:read',
]; ];
constructor(appData: AppInfo, connectionData: JSONObject) { constructor(appData: IApp, connectionData: IJSONObject) {
this.connectionData = connectionData; this.connectionData = connectionData;
this.appData = appData; this.appData = appData;
} }
get oauthRedirectUrl() { get oauthRedirectUrl() {
return this.appData.fields.find( return this.appData.fields.find(
(field: Field) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'
).value; ).value;
} }

View File

@@ -1,7 +1,6 @@
import Step from '../../models/step'; import Step from '../../models/step';
import flowType, { flowInputType } from '../types/flow'; import flowType, { flowInputType } from '../types/flow';
import RequestWithCurrentUser from '../../types/express/request-with-current-user'; import RequestWithCurrentUser from '../../types/express/request-with-current-user';
import { StepType } from '../../types/step';
type Params = { type Params = {
input: { input: {
@@ -21,7 +20,7 @@ const createFlowResolver = async (
await Step.query().insert({ await Step.query().insert({
flowId: flow.id, flowId: flow.id,
type: StepType.Trigger, type: 'trigger',
position: 1, position: 1,
appKey, appKey,
}); });

View File

@@ -1,7 +1,6 @@
import { GraphQLNonNull } from 'graphql'; import { GraphQLNonNull } from 'graphql';
import stepType, { stepInputType } from '../types/step'; import stepType, { stepInputType } from '../types/step';
import RequestWithCurrentUser from '../../types/express/request-with-current-user'; import RequestWithCurrentUser from '../../types/express/request-with-current-user';
import { StepType } from '../../types/step';
type Params = { type Params = {
input: { input: {
@@ -42,7 +41,7 @@ const createStepResolver = async (
const step = await flow.$relatedQuery('steps').insertAndFetch({ const step = await flow.$relatedQuery('steps').insertAndFetch({
key: input.key, key: input.key,
appKey: input.appKey, appKey: input.appKey,
type: StepType.Action, type: 'action',
position: previousStep.position + 1, position: previousStep.position + 1,
parameters: {}, parameters: {},
}); });

View File

@@ -1,11 +1,11 @@
import type { IApp } from '@automatisch/types';
import { GraphQLEnumType } from 'graphql'; import { GraphQLEnumType } from 'graphql';
import App from '../../models/app'; import App from '../../models/app';
import appInfoType from '../../types/app-info'
const apps = App.findAll(); const apps = App.findAll();
const availableAppEnumValues: any = {} const availableAppEnumValues: any = {}
apps.forEach((app: appInfoType) => { apps.forEach((app: IApp) => {
availableAppEnumValues[app.key] = { value: app.key } availableAppEnumValues[app.key] = { value: app.key }
}) })

View File

@@ -1,11 +1,11 @@
import AppInfoType from '../types/app-info'; import type { IApp } from '@automatisch/types';
import appConfig from '../config/app'; import appConfig from '../config/app';
const appInfoConverter = (rawAppData: string) => { const appInfoConverter = (rawAppData: string) => {
let computedRawData = rawAppData.replace('{BASE_URL}', appConfig.baseUrl); let computedRawData = rawAppData.replace('{BASE_URL}', appConfig.baseUrl);
computedRawData = computedRawData.replace('{WEB_APP_URL}', appConfig.webAppUrl); computedRawData = computedRawData.replace('{WEB_APP_URL}', appConfig.webAppUrl);
const computedJSONData: AppInfoType = JSON.parse(computedRawData) const computedJSONData: IApp = JSON.parse(computedRawData)
return computedJSONData; return computedJSONData;
} }

View File

@@ -6,10 +6,10 @@ import User from './user';
import appConfig from '../config/app'; import appConfig from '../config/app';
class Connection extends Base { class Connection extends Base {
id!: number; id!: string;
key!: string; key!: string;
data!: any; data!: any;
userId!: number; userId!: string;
verified: boolean; verified: boolean;
count: number; count: number;

View File

@@ -4,8 +4,8 @@ import Step from './step';
class ExecutionStep extends Base { class ExecutionStep extends Base {
id!: string; id!: string;
executionId!: number; executionId!: string;
stepId!: number; stepId!: string;
dataIn!: any; dataIn!: any;
dataOut!: any; dataOut!: any;
status: string; status: string;
@@ -17,8 +17,8 @@ class ExecutionStep extends Base {
properties: { properties: {
id: { type: 'string' }, id: { type: 'string' },
executionId: { type: 'integer' }, executionId: { type: 'string' },
stepId: { type: 'integer' }, stepId: { type: 'string' },
dataIn: { type: 'object' }, dataIn: { type: 'object' },
dataOut: { type: 'object' }, dataOut: { type: 'object' },
status: { type: 'string', enum: ['success', 'failure'] }, status: { type: 'string', enum: ['success', 'failure'] },

View File

@@ -1,11 +1,11 @@
import { ModelOptions, QueryContext, ValidationError } from 'objection'; import { ValidationError } from 'objection';
import Base from './base'; import Base from './base';
import Step from './step'; import Step from './step';
class Flow extends Base { class Flow extends Base {
id!: string; id!: string;
name: string; name: string;
userId!: number; userId!: string;
active: boolean; active: boolean;
steps?: [Step]; steps?: [Step];
@@ -17,7 +17,7 @@ class Flow extends Base {
properties: { properties: {
id: { type: 'string' }, id: { type: 'string' },
name: { type: 'string' }, name: { type: 'string' },
userId: { type: 'integer' }, userId: { type: 'string' },
active: { type: 'boolean' }, active: { type: 'boolean' },
}, },
}; };

View File

@@ -2,14 +2,14 @@ import Base from './base';
import Flow from './flow'; import Flow from './flow';
import Connection from './connection'; import Connection from './connection';
import ExecutionStep from './execution-step'; import ExecutionStep from './execution-step';
import { StepType } from '../types/step'; import type { IStep } from '@automatisch/types';
class Step extends Base { class Step extends Base {
id!: number; id!: string;
flowId!: string; flowId!: string;
key: string; key: string;
appKey: string; appKey: string;
type!: StepType; type!: IStep["type"];
connectionId?: string; connectionId?: string;
status: string; status: string;
position: number; position: number;
@@ -25,7 +25,7 @@ class Step extends Base {
required: ['type'], required: ['type'],
properties: { properties: {
id: { type: 'integer' }, id: { type: 'string' },
flowId: { type: 'string' }, flowId: { type: 'string' },
key: { type: ['string', 'null'] }, key: { type: ['string', 'null'] },
appKey: { type: ['string', 'null'], minLength: 1, maxLength: 255 }, appKey: { type: ['string', 'null'], minLength: 1, maxLength: 255 },

View File

@@ -6,7 +6,7 @@ import Step from './step';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
class User extends Base { class User extends Base {
id!: number; id!: string;
email!: string; email!: string;
password!: string; password!: string;
connections?: [Connection]; connections?: [Connection];
@@ -20,7 +20,7 @@ class User extends Base {
required: ['email', 'password'], required: ['email', 'password'],
properties: { properties: {
id: { type: 'integer' }, id: { type: 'string' },
email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 }, email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 },
password: { type: 'string', minLength: 1, maxLength: 255 }, password: { type: 'string', minLength: 1, maxLength: 255 },
}, },

View File

@@ -4,7 +4,6 @@ import Flow from '../models/flow';
import Step from '../models/step'; import Step from '../models/step';
import Execution from '../models/execution'; import Execution from '../models/execution';
import ExecutionStep from '../models/execution-step'; import ExecutionStep from '../models/execution-step';
import { StepType } from '../types/step';
type ExecutionSteps = Record<string, ExecutionStep>; type ExecutionSteps = Record<string, ExecutionStep>;
@@ -44,7 +43,7 @@ class Processor {
parameters: rawParameters = {}, parameters: rawParameters = {},
id id
} = step; } = step;
const isTrigger = type === StepType.Trigger; const isTrigger = type === 'trigger';
const AppClass = (await import(`../apps/${appKey}`)).default; const AppClass = (await import(`../apps/${appKey}`)).default;
const computedParameters = Processor.computeParameters(rawParameters, priorExecutionSteps); const computedParameters = Processor.computeParameters(rawParameters, priorExecutionSteps);
const appInstance = new AppClass(appData, connection.data, computedParameters); const appInstance = new AppClass(appData, connection.data, computedParameters);

View File

@@ -1,10 +1,10 @@
import AuthenticationStepField from '../types/authentication-step-field'; import type { IAuthenticationStepField } from '@automatisch/types';
type AuthenticationStep = { type AuthenticationStep = {
step: number, step: number,
type: string, type: string,
name: string, name: string,
fields: AuthenticationStepField[]; fields: IAuthenticationStepField[];
} }
export default AuthenticationStep; export default AuthenticationStep;

View File

@@ -1,9 +1,9 @@
import appInfoType from '../../types/app-info'; import type { IApp } from '@automatisch/types';
import JSONObject from './json-object'; import JSONObject from './json-object';
export default interface AuthenticationInterface { export default interface AuthenticationInterface {
appData: appInfoType; appData: IApp;
connectionData: JSONObject; connectionData: IJSONObject;
client: unknown; client: unknown;
verifyCredentials(): Promise<JSONObject>; verifyCredentials(): Promise<JSONObject>;
isStillVerified(): Promise<boolean>; isStillVerified(): Promise<boolean>;

View File

@@ -9,9 +9,18 @@
"outDir": "dist", "outDir": "dist",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"*": ["node_modules/*", "src/types/*"] "*": [
"node_modules/*",
"src/types/*"
]
}, },
"typeRoots": ["node_modules/@types", "./src/types", "./src/apps/*/types"] "typeRoots": [
"node_modules/@types",
"./src/types",
"./src/apps/*/types"
]
}, },
"include": ["src/**/*"] "include": [
"src/**/*"
]
} }

11
packages/types/README.md Normal file
View File

@@ -0,0 +1,11 @@
# `@automatisch/types`
> TODO: description
## Usage
```
const types = require('@automatisch/types');
// TODO: DEMONSTRATE API
```

127
packages/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,127 @@
// Type definitions for automatisch
export type IJSONValue = string | number | boolean | JSONObject | JSONArray;
export type IJSONArray = Array<JSONValue>;
export interface IJSONObject {
[x: string]: JSONValue;
}
export interface IConnection<D extends IJSONObject | string> {
id: string;
key: string;
data: D;
userId: string;
verified: boolean;
count: number;
createdAt: string;
}
export interface IExecutionStep {
id: string;
executionId: string;
stepId: string;
dataIn: IJSONObject;
dataOut: IJSONObject;
status: string;
}
export interface IExecution {
id: string;
flowId: string;
testRun: boolean;
executionSteps: IExecutionStep[];
}
export interface IStep {
id: string;
name: string;
flowId: string;
key: string;
appKey: string;
type: 'action' | 'trigger';
connectionId: string;
status: string;
position: number;
parameters: Record<string, unknown>;
connection: IConnection;
flow: IFlow;
executionSteps: IExecutionStep[];
// FIXME: remove this property once execution steps are properly exposed via queries
output: IJSONObject;
}
export interface IFlow {
id: string;
name: string;
userId: string;
active: boolean;
steps: IStep[];
}
export interface IUser {
id: string;
email: string;
password: string;
connections: IConnection[];
flows: IFlow[];
steps: IStep[];
}
export interface IField {
key: string;
label: string;
type: string;
required: boolean;
readOnly: boolean;
value: string;
placeholder: string | null;
description: string;
docUrl: string;
clickToCopy: boolean;
name: string;
variables: boolean;
}
export interface IAuthenticationStepField {
name: string;
value: string | null;
properties: {
name: string;
value: string;
}[];
}
export interface IAuthenticationStep {
step: number;
type: 'mutation' | 'openWithPopup';
name: string;
arguments: IAuthenticationStepField[];
}
export interface IApp {
name: string;
key: string;
iconUrl: string;
docUrl: string;
primaryColor: string;
fields: IField[];
authenticationSteps: IAuthenticationStep[];
reconnectionSteps: IAuthenticationStep[];
connectionCount: number;
triggers: any[];
actions: any[];
connections: IConnection[];
}
export interface IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: unknown;
verifyCredentials(): Promise<JSONObject>;
isStillVerified(): Promise<boolean>;
}
export interface ISubstep {
name: string;
arguments: IField[];
}

View File

@@ -0,0 +1,17 @@
{
"name": "@automatisch/types",
"version": "0.1.0",
"description": "Type definitions for automatisch",
"homepage": "https://github.com/automatisch/automatisch",
"types": "./index.d.ts",
"scripts": {},
"dependencies": {},
"typeScriptVersion": "4.1",
"repository": {
"type": "git",
"url": "git+https://github.com/automatisch/automatisch.git"
},
"bugs": {
"url": "https://github.com/automatisch/automatisch/issues"
}
}

View File

@@ -4,6 +4,7 @@
"description": "> TODO: description", "description": "> TODO: description",
"dependencies": { "dependencies": {
"@apollo/client": "^3.4.15", "@apollo/client": "^3.4.15",
"@automatisch/types": "0.1.0",
"@emotion/react": "^11.4.1", "@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.3.0",
"@mui/icons-material": "^5.0.1", "@mui/icons-material": "^5.0.1",

View File

@@ -10,12 +10,12 @@ import useFormatMessage from 'hooks/useFormatMessage';
import computeAuthStepVariables from 'helpers/computeAuthStepVariables'; import computeAuthStepVariables from 'helpers/computeAuthStepVariables';
import { processStep } from 'helpers/authenticationSteps'; import { processStep } from 'helpers/authenticationSteps';
import InputCreator from 'components/InputCreator'; import InputCreator from 'components/InputCreator';
import type { App } from 'types/app'; import type { IApp, IField } from '@automatisch/types';
import { Form } from './style'; import { Form } from './style';
type AddAppConnectionProps = { type AddAppConnectionProps = {
onClose: () => void; onClose: () => void;
application: App; application: IApp;
connectionId?: string; connectionId?: string;
}; };
@@ -73,7 +73,7 @@ export default function AddAppConnection(props: AddAppConnectionProps): React.Re
<DialogContent> <DialogContent>
<DialogContentText tabIndex={-1} component="div"> <DialogContentText tabIndex={-1} component="div">
<Form onSubmit={submitHandler}> <Form onSubmit={submitHandler}>
{fields?.map(field => (<InputCreator key={field.key} schema={field} />))} {fields?.map((field: IField) => (<InputCreator key={field.key} schema={field} />))}
<LoadingButton <LoadingButton
type="submit" type="submit"

View File

@@ -16,10 +16,10 @@ import ListItemText from '@mui/material/ListItemText';
import InputLabel from '@mui/material/InputLabel'; 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 type { IApp } from '@automatisch/types';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import AppIcon from 'components/AppIcon'; import AppIcon from 'components/AppIcon';
import type { App } from 'types/app';
import { GET_APPS } from 'graphql/queries/get-apps'; import { GET_APPS } from 'graphql/queries/get-apps';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
@@ -68,7 +68,7 @@ export default function AddNewAppConnection(props: AddNewAppConnectionProps): Re
</FormControl> </FormControl>
<List sx={{ pt: 2 }}> <List sx={{ pt: 2 }}>
{data?.getApps?.map((app: App) => ( {data?.getApps?.map((app: IApp) => (
<ListItem disablePadding key={app.name}> <ListItem disablePadding key={app.name}>
<ListItemButton component={Link} to={URLS.APP_ADD_CONNECTION(app.name.toLowerCase())}> <ListItemButton component={Link} to={URLS.APP_ADD_CONNECTION(app.name.toLowerCase())}>
<ListItemIcon sx={{ minWidth: 74 }}> <ListItemIcon sx={{ minWidth: 74 }}>

View File

@@ -2,15 +2,15 @@ import { useQuery } from '@apollo/client';
import { GET_FLOWS } from 'graphql/queries/get-flows'; import { GET_FLOWS } from 'graphql/queries/get-flows';
import AppFlowRow from 'components/AppFlowRow'; import AppFlowRow from 'components/AppFlowRow';
import type { Flow } from 'types/flow'; import type { IFlow } from '@automatisch/types';
export default function AppFlows(): React.ReactElement { export default function AppFlows(): React.ReactElement {
const { data } = useQuery(GET_FLOWS); const { data } = useQuery(GET_FLOWS);
const appFlows: Flow[] = data?.getFlows || []; const appFlows: IFlow[] = data?.getFlows || [];
return ( return (
<> <>
{appFlows.map((appFlow: Flow) => ( {appFlows.map((appFlow: IFlow) => (
<AppFlowRow key={appFlow.id} flow={appFlow} /> <AppFlowRow key={appFlow.id} flow={appFlow} />
))} ))}
</> </>

View File

@@ -8,11 +8,12 @@ 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 * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import type { App } from 'types/app'; import type { IApp } from '@automatisch/types';
import { CardContent, Typography } from './style'; import { CardContent, Typography } from './style';
type AppRowProps = { type AppRowProps = {
application: App; application: IApp;
} }
const countTranslation = (value: React.ReactNode) => ( const countTranslation = (value: React.ReactNode) => (

View File

@@ -7,22 +7,21 @@ import ListItem from '@mui/material/ListItem';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import type { App, AppConnection } from 'types/app'; import type { IApp, IConnection, IStep, ISubstep, IJSONObject } from '@automatisch/types';
import type { Step, Substep } from 'types/step';
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections'; import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
import { TEST_CONNECTION } from 'graphql/queries/test-connection'; import { TEST_CONNECTION } from 'graphql/queries/test-connection';
type ChooseAccountSubstepProps = { type ChooseAccountSubstepProps = {
substep: Substep, substep: ISubstep,
expanded?: boolean; expanded?: boolean;
onExpand: () => void; onExpand: () => void;
onCollapse: () => void; onCollapse: () => void;
onChange: ({ step }: { step: Step}) => void; onChange: ({ step }: { step: IStep }) => void;
onSubmit: () => void; onSubmit: () => void;
step: Step; step: IStep;
}; };
const optionGenerator = (connection: AppConnection): { label: string; value: string; } => ({ const optionGenerator = (connection: IConnection<IJSONObject>): { label: string; value: string; } => ({
label: connection?.data?.screenName as string ?? 'Unnamed', label: connection?.data?.screenName as string ?? 'Unnamed',
value: connection?.id as string, value: connection?.id as string,
}); });
@@ -62,7 +61,7 @@ function ChooseAccountSubstep(props: ChooseAccountSubstepProps): React.ReactElem
// intentionally no dependencies for initial test // intentionally no dependencies for initial test
}, []); }, []);
const connectionOptions = React.useMemo(() => (data?.getApp as App)?.connections?.map((connection) => optionGenerator(connection)) || [], [data]); const connectionOptions = React.useMemo(() => (data?.getApp as IApp)?.connections?.map((connection) => optionGenerator(connection)) || [], [data]);
const { name } = substep; const { name } = substep;

View File

@@ -10,26 +10,24 @@ import Autocomplete from '@mui/material/Autocomplete';
import { GET_APPS } from 'graphql/queries/get-apps'; import { GET_APPS } from 'graphql/queries/get-apps';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import type { App } from 'types/app'; import type { IApp, IStep, ISubstep } from '@automatisch/types';
import type { Step, Substep } from 'types/step';
import { StepType } from 'types/step';
type ChooseAppAndEventSubstepProps = { type ChooseAppAndEventSubstepProps = {
substep: Substep, substep: ISubstep,
expanded?: boolean; expanded?: boolean;
onExpand: () => void; onExpand: () => void;
onCollapse: () => void; onCollapse: () => void;
onChange: ({ step }: { step: Step}) => void; onChange: ({ step }: { step: IStep }) => void;
onSubmit: () => void; onSubmit: () => void;
step: Step; step: IStep;
}; };
const optionGenerator = (app: Record<string, unknown>): { label: string; value: string; } => ({ const optionGenerator = (app: IApp): { label: string; value: string; } => ({
label: app.name as string, label: app.name as string,
value: app.key as string, value: app.key as string,
}); });
const getOption = (options: Record<string, unknown>[], appKey: unknown) => options.find(app => app.value === appKey as string) || null; const getOption = (options: Record<string, unknown>[], appKey: IApp["key"]) => options.find(option => option.value === appKey as string) || null;
function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.ReactElement { function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.ReactElement {
const { const {
@@ -42,11 +40,11 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
onChange, onChange,
} = props; } = props;
const isTrigger = step.type === StepType.Trigger; const isTrigger = step.type === 'trigger';
const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }}); const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }});
const apps: App[] = data?.getApps; const apps: IApp[] = data?.getApps;
const app = apps?.find((currentApp: App) => currentApp.key === step.appKey); const app = apps?.find((currentApp: IApp) => currentApp.key === step.appKey);
const appOptions = React.useMemo(() => apps?.map((app) => optionGenerator(app)), [apps]); const appOptions = React.useMemo(() => apps?.map((app) => optionGenerator(app)), [apps]);
const actionsOrTriggers = isTrigger ? app?.triggers : app?.actions; const actionsOrTriggers = isTrigger ? app?.triggers : app?.actions;
@@ -88,7 +86,7 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R
onChange({ onChange({
step: { step: {
...step, ...step,
key: null, key: '',
appKey, appKey,
}, },
}); });

View File

@@ -8,10 +8,10 @@ import { GET_FLOW } from 'graphql/queries/get-flow';
import { CREATE_STEP } from 'graphql/mutations/create-step'; import { CREATE_STEP } from 'graphql/mutations/create-step';
import { UPDATE_STEP } from 'graphql/mutations/update-step'; import { UPDATE_STEP } from 'graphql/mutations/update-step';
import FlowStep from 'components/FlowStep'; import FlowStep from 'components/FlowStep';
import type { Flow } from 'types/flow'; import type { IFlow } from '@automatisch/types';
type EditorProps = { type EditorProps = {
flow: Flow; flow: IFlow;
}; };
function updateHandlerFactory(flowId: string, previousStepId: string) { function updateHandlerFactory(flowId: string, previousStepId: string) {

View File

@@ -14,7 +14,7 @@ import Editor from 'components/Editor';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
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 { Flow } from 'types/flow'; import type { IFlow } from '@automatisch/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 {
@@ -22,7 +22,7 @@ export default function EditorLayout(): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const [updateFlow] = useMutation(UPDATE_FLOW); const [updateFlow] = useMutation(UPDATE_FLOW);
const { data, loading } = useQuery(GET_FLOW, { variables: { id: flowId }}); const { data, loading } = useQuery(GET_FLOW, { variables: { id: flowId }});
const flow: Flow = data?.getFlow; const flow: IFlow = data?.getFlow;
const onFlowNameUpdate = React.useCallback(async (name: string) => { const onFlowNameUpdate = React.useCallback(async (name: string) => {
await updateFlow({ await updateFlow({

View File

@@ -5,12 +5,12 @@ import Box from '@mui/material/Box';
import CardActionArea from '@mui/material/CardActionArea'; import CardActionArea from '@mui/material/CardActionArea';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import type { Flow } from 'types/flow'; import type { IFlow } from '@automatisch/types';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import { CardContent, Typography } from './style'; import { CardContent, Typography } from './style';
type FlowRowProps = { type FlowRowProps = {
flow: Flow; flow: IFlow;
} }
export default function FlowRow(props: FlowRowProps): React.ReactElement { export default function FlowRow(props: FlowRowProps): React.ReactElement {

View File

@@ -10,6 +10,7 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import ErrorIcon from '@mui/icons-material/Error'; import ErrorIcon from '@mui/icons-material/Error';
import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import type { IApp, IField, IStep } from '@automatisch/types';
import { StepExecutionsProvider } from 'contexts/StepExecutions'; import { StepExecutionsProvider } from 'contexts/StepExecutions';
import TestSubstep from 'components/TestSubstep'; import TestSubstep from 'components/TestSubstep';
@@ -22,18 +23,15 @@ import AppIcon from 'components/AppIcon';
import { GET_APPS } from 'graphql/queries/get-apps'; import { GET_APPS } from 'graphql/queries/get-apps';
import { GET_STEP_WITH_TEST_EXECUTIONS } from 'graphql/queries/get-step-with-test-executions'; import { GET_STEP_WITH_TEST_EXECUTIONS } from 'graphql/queries/get-step-with-test-executions';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import type { App, AppFields } from 'types/app';
import type { Step } from 'types/step';
import { StepType } from 'types/step';
import { AppIconWrapper, AppIconStatusIconWrapper, Content, Header, Wrapper } from './style'; import { AppIconWrapper, AppIconStatusIconWrapper, Content, Header, Wrapper } from './style';
type FlowStepProps = { type FlowStepProps = {
collapsed?: boolean; collapsed?: boolean;
step: Step; step: IStep;
index?: number; index?: number;
onOpen?: () => void; onOpen?: () => void;
onClose?: () => void; onClose?: () => void;
onChange: (step: Step) => void; onChange: (step: IStep) => void;
} }
const validIcon = <CheckCircleIcon color="success" />; const validIcon = <CheckCircleIcon color="success" />;
@@ -42,9 +40,9 @@ const errorIcon = <ErrorIcon color="error" />;
export default function FlowStep(props: FlowStepProps): React.ReactElement | null { export default function FlowStep(props: FlowStepProps): React.ReactElement | null {
const { collapsed, index, onChange } = props; const { collapsed, index, onChange } = props;
const contextButtonRef = React.useRef<HTMLButtonElement | null>(null); const contextButtonRef = React.useRef<HTMLButtonElement | null>(null);
const step: Step = props.step; const step: IStep = props.step;
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
const isTrigger = step.type === StepType.Trigger; const isTrigger = step.type === 'trigger';
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const [currentSubstep, setCurrentSubstep] = React.useState<number | null>(2); const [currentSubstep, setCurrentSubstep] = React.useState<number | null>(2);
const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }}); const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }});
@@ -70,13 +68,13 @@ export default function FlowStep(props: FlowStepProps): React.ReactElement | nul
} }
}, [collapsed, stepWithTestExecutionsCalled, getStepWithTestExecutions, step.id, isTrigger]); }, [collapsed, stepWithTestExecutionsCalled, getStepWithTestExecutions, step.id, isTrigger]);
const apps: App[] = data?.getApps; const apps: IApp[] = data?.getApps;
const app = apps?.find((currentApp: App) => currentApp.key === step.appKey); const app = apps?.find((currentApp: IApp) => currentApp.key === step.appKey);
const actionsOrTriggers = isTrigger ? app?.triggers : app?.actions; const actionsOrTriggers = isTrigger ? app?.triggers : app?.actions;
const substeps = React.useMemo(() => actionsOrTriggers?.find(({ key }) => key === step.key)?.subSteps || [], [actionsOrTriggers, step?.key]); const substeps = React.useMemo(() => actionsOrTriggers?.find(({ key }) => key === step.key)?.subSteps || [], [actionsOrTriggers, step?.key]);
const handleChange = React.useCallback(({ step }: { step: Step }) => { const handleChange = React.useCallback(({ step }: { step: IStep }) => {
onChange(step); onChange(step);
}, []) }, [])
@@ -85,7 +83,7 @@ export default function FlowStep(props: FlowStepProps): React.ReactElement | nul
}, []); }, []);
const handleSubmit = (val: any) => { const handleSubmit = (val: any) => {
handleChange({ step: val as Step }); handleChange({ step: val as IStep });
} }
if (!apps) return null; if (!apps) return null;
@@ -143,7 +141,7 @@ export default function FlowStep(props: FlowStepProps): React.ReactElement | nul
<Collapse in={!collapsed} unmountOnExit> <Collapse in={!collapsed} unmountOnExit>
<Content> <Content>
<List> <List>
<StepExecutionsProvider value={stepWithTestExecutionsData?.getStepWithTestExecutions as Step[]}> <StepExecutionsProvider value={stepWithTestExecutionsData?.getStepWithTestExecutions as IStep[]}>
<Form defaultValues={step} onSubmit={handleSubmit}> <Form defaultValues={step} onSubmit={handleSubmit}>
<ChooseAppAndEventSubstep <ChooseAppAndEventSubstep
expanded={currentSubstep === 0} expanded={currentSubstep === 0}
@@ -155,7 +153,7 @@ export default function FlowStep(props: FlowStepProps): React.ReactElement | nul
step={step} step={step}
/> />
{substeps?.length > 0 && substeps.map((substep: { name: string, key: string, arguments: AppFields[] }, index: number) => ( {substeps?.length > 0 && substeps.map((substep: { name: string, key: string, arguments: IField[] }, index: number) => (
<React.Fragment key={`${substep?.name}-${index}`}> <React.Fragment key={`${substep?.name}-${index}`}>
{substep.key === 'chooseAccount' && ( {substep.key === 'chooseAccount' && (
<ChooseAccountSubstep <ChooseAccountSubstep

View File

@@ -6,23 +6,22 @@ import Button from '@mui/material/Button';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import InputCreator from 'components/InputCreator'; import InputCreator from 'components/InputCreator';
import type { Step, Substep } from 'types/step'; import type { IField, IStep, ISubstep } from '@automatisch/types';
import type { AppFields } from 'types/app';
type FlowSubstepProps = { type FlowSubstepProps = {
substep: Substep, substep: ISubstep,
expanded?: boolean; expanded?: boolean;
onExpand: () => void; onExpand: () => void;
onCollapse: () => void; onCollapse: () => void;
onChange: ({ step }: { step: Step }) => void; onChange: ({ step }: { step: IStep }) => void;
onSubmit: () => void; onSubmit: () => void;
step: Step; step: IStep;
}; };
const validateSubstep = (substep: Substep, step: Step) => { const validateSubstep = (substep: ISubstep, step: IStep) => {
if (!substep) return true; if (!substep) return true;
const args: AppFields[] = substep.arguments || []; const args: IField[] = substep.arguments || [];
return args.every(arg => { return args.every(arg => {
if (arg.required === false) { return true; } if (arg.required === false) { return true; }
@@ -50,7 +49,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
} = substep; } = substep;
const formContext = useFormContext(); const formContext = useFormContext();
const [validationStatus, setValidationStatus] = React.useState<boolean | null>(validateSubstep(substep, formContext.getValues() as Step)); const [validationStatus, setValidationStatus] = React.useState<boolean | null>(validateSubstep(substep, formContext.getValues() as IStep));
const handleChangeOnBlur = React.useCallback((key: string) => { const handleChangeOnBlur = React.useCallback((key: string) => {
@@ -73,7 +72,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
React.useEffect(() => { React.useEffect(() => {
function validate (step: unknown) { function validate (step: unknown) {
const validationResult = validateSubstep(substep, step as Step); const validationResult = validateSubstep(substep, step as IStep);
setValidationStatus(validationResult); setValidationStatus(validationResult);
}; };
const subscription = formContext.watch(validate); const subscription = formContext.watch(validate);

View File

@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import type { AppFields } from 'types/app'; import type { IField } from '@automatisch/types';
import PowerInput from 'components/PowerInput'; import PowerInput from 'components/PowerInput';
import TextField from 'components/TextField'; import TextField from 'components/TextField';
@@ -8,7 +8,7 @@ import TextField from 'components/TextField';
type InputCreatorProps = { type InputCreatorProps = {
onChange?: React.ChangeEventHandler; onChange?: React.ChangeEventHandler;
onBlur?: React.FocusEventHandler; onBlur?: React.FocusEventHandler;
schema: AppFields; schema: IField;
namePrefix?: string; namePrefix?: string;
}; };

View File

@@ -10,7 +10,7 @@ import Collapse from '@mui/material/Collapse';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
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 { Step } from 'types/step'; import type { IStep } from '@automatisch/types';
const ListItemText = styled(MuiListItemText)``; const ListItemText = styled(MuiListItemText)``;
@@ -61,7 +61,7 @@ const Suggestions = (props: SuggestionsProps) => {
<List <List
disablePadding disablePadding
> >
{data.map((option: Step, index: number) => ( {data.map((option: IStep, index: number) => (
<> <>
<ListItemButton <ListItemButton
divider divider

View File

@@ -1,4 +1,4 @@
import { Step } from 'types/step'; import type { IStep } from '@automatisch/types';
const joinBy = (delimiter = '.', ...args: string[]) => args.filter(Boolean).join(delimiter); const joinBy = (delimiter = '.', ...args: string[]) => args.filter(Boolean).join(delimiter);
@@ -32,10 +32,10 @@ const process = (data: any, parentKey?: any, index?: number): any[] => {
}); });
}; };
export const processStepWithExecutions = (steps: Step[]): any[] => { export const processStepWithExecutions = (steps: IStep[]): any[] => {
if (!steps) return []; if (!steps) return [];
return steps.map((step: Step, index: number) => ({ return steps.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}`, name: `${index + 1}. ${step.appKey}`,

View File

@@ -6,17 +6,16 @@ import Button from '@mui/material/Button';
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow'; import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
import FlowSubstepTitle from 'components/FlowSubstepTitle'; import FlowSubstepTitle from 'components/FlowSubstepTitle';
import type { Step, Substep } from 'types/step'; import type { IStep, ISubstep } from '@automatisch/types';
import type { AppFields } from 'types/app';
type TestSubstepProps = { type TestSubstepProps = {
substep: Substep, substep: ISubstep,
expanded?: boolean; expanded?: boolean;
onExpand: () => void; onExpand: () => void;
onCollapse: () => void; onCollapse: () => void;
onChange?: ({ step }: { step: Step }) => void; onChange?: ({ step }: { step: IStep }) => void;
onSubmit?: () => void; onSubmit?: () => void;
step: Step; step: IStep;
}; };
function TestSubstep(props: TestSubstepProps): React.ReactElement { function TestSubstep(props: TestSubstepProps): React.ReactElement {

View File

@@ -1,11 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { Step } from 'types/step'; import type { IStep } from '@automatisch/types';
export const StepExecutionsContext = React.createContext<Step[]>([]); export const StepExecutionsContext = React.createContext<IStep[]>([]);
type StepExecutionsProviderProps = { type StepExecutionsProviderProps = {
children: React.ReactNode; children: React.ReactNode;
value: Step[]; value: IStep[];
} }
export const StepExecutionsProvider = (props: StepExecutionsProviderProps): React.ReactElement => { export const StepExecutionsProvider = (props: StepExecutionsProviderProps): React.ReactElement => {

View File

@@ -8,6 +8,7 @@ export const CREATE_STEP = gql`
key key
appKey appKey
parameters parameters
status
connection { connection {
id id
} }

View File

@@ -1,3 +1,4 @@
import type { IAuthenticationStep } from '@automatisch/types';
import apolloClient from 'graphql/client'; import apolloClient from 'graphql/client';
import MUTATIONS from 'graphql/mutations'; import MUTATIONS from 'graphql/mutations';
import appConfig from 'config/app'; import appConfig from 'config/app';
@@ -7,14 +8,7 @@ enum AuthenticationSteps {
OpenWithPopup = 'openWithPopup', OpenWithPopup = 'openWithPopup',
} }
type Step = { const processMutation = async (step: IAuthenticationStep, variables: Record<string, unknown>) => {
name: string;
variables: Record<string, unknown>;
process: (step: any, variables: Record<string, unknown>) => Promise<any>;
type: AuthenticationSteps.Mutation | AuthenticationSteps.OpenWithPopup;
};
const processMutation = async (step: Step, variables: Record<string, unknown>) => {
const mutation = MUTATIONS[step.name]; const mutation = MUTATIONS[step.name];
const mutationResponse = await apolloClient.mutate({ mutation, variables }); const mutationResponse = await apolloClient.mutate({ mutation, variables });
const responseData = mutationResponse.data[step.name]; const responseData = mutationResponse.data[step.name];
@@ -38,7 +32,7 @@ function getObjectOfEntries(iterator: any) {
return result; return result;
} }
const processOpenWithPopup = (step: Step, variables: Record<string, string>) => { const processOpenWithPopup = (step: IAuthenticationStep, variables: Record<string, string>) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const windowFeatures = 'toolbar=no, titlebar=no, menubar=no, width=500, height=700, top=100, left=100'; const windowFeatures = 'toolbar=no, titlebar=no, menubar=no, width=500, height=700, top=100, left=100';
const url = variables.url; const url = variables.url;
@@ -62,7 +56,7 @@ const processOpenWithPopup = (step: Step, variables: Record<string, string>) =>
}); });
}; };
export const processStep = async (step: Step, variables: Record<string, string>): Promise<any> => { export const processStep = async (step: IAuthenticationStep, variables: Record<string, string>): Promise<any> => {
if (step.type === AuthenticationSteps.Mutation) { if (step.type === AuthenticationSteps.Mutation) {
return processMutation(step, variables); return processMutation(step, variables);
} else if (step.type === AuthenticationSteps.OpenWithPopup) { } else if (step.type === AuthenticationSteps.OpenWithPopup) {

View File

@@ -1,4 +1,5 @@
import template from 'lodash.template'; import template from 'lodash.template';
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types';
const interpolate = /{([\s\S]+?)}/g; const interpolate = /{([\s\S]+?)}/g;
@@ -6,18 +7,9 @@ type Variables = {
[key: string]: any [key: string]: any
} }
type VariableSchema = { type IVariable = Omit<IAuthenticationStepField, "properties"> & Partial<Pick<IAuthenticationStepField, "properties">>;
properties: VariableSchema[];
name: string;
type: 'string' | 'integer';
value: string;
}
type AggregatedData = { const computeAuthStepVariables = (variableSchema: IVariable[], aggregatedData: IJSONObject): IJSONObject => {
[key: string]: Record<string, unknown> | string;
}
const computeAuthStepVariables = (variableSchema: VariableSchema[], aggregatedData: AggregatedData): Variables => {
const variables: Variables = {}; const variables: Variables = {};
for (const variable of variableSchema) { for (const variable of variableSchema) {
@@ -27,11 +19,9 @@ const computeAuthStepVariables = (variableSchema: VariableSchema[], aggregatedDa
continue; continue;
} }
const computedVariable = template(variable.value, { interpolate })(aggregatedData); if (variable.value) {
const computedVariable = template(variable.value, { interpolate })(aggregatedData);
if (variable.type === 'integer') {
variables[variable.name] = parseInt(computedVariable, 10);
} else {
variables[variable.name] = computedVariable; variables[variable.name] = computedVariable;
} }
} }

View File

@@ -5,6 +5,7 @@ import { useQuery } from '@apollo/client';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import type { IApp } from '@automatisch/types';
import ConditionalIconButton from 'components/ConditionalIconButton'; import ConditionalIconButton from 'components/ConditionalIconButton';
import Container from 'components/Container'; import Container from 'components/Container';
@@ -15,7 +16,6 @@ import SearchInput from 'components/SearchInput';
import useFormatMessage from 'hooks/useFormatMessage' import useFormatMessage from 'hooks/useFormatMessage'
import { GET_CONNECTED_APPS } from 'graphql/queries/get-connected-apps'; import { GET_CONNECTED_APPS } from 'graphql/queries/get-connected-apps';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import type { App } from 'types/app';
export default function Applications(): React.ReactElement { export default function Applications(): React.ReactElement {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -69,7 +69,7 @@ export default function Applications(): React.ReactElement {
</Grid> </Grid>
</Grid> </Grid>
{data?.getConnectedApps?.map((app: App) => ( {data?.getConnectedApps?.map((app: IApp) => (
<AppRow key={app.name} application={app} /> <AppRow key={app.name} application={app} />
))} ))}

View File

@@ -5,6 +5,7 @@ import { useQuery } from '@apollo/client';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import type { IFlow } from '@automatisch/types';
import FlowRow from 'components/FlowRow'; import FlowRow from 'components/FlowRow';
import ConditionalIconButton from 'components/ConditionalIconButton'; import ConditionalIconButton from 'components/ConditionalIconButton';
@@ -14,14 +15,13 @@ import SearchInput from 'components/SearchInput';
import useFormatMessage from 'hooks/useFormatMessage' import useFormatMessage from 'hooks/useFormatMessage'
import { GET_FLOWS } from 'graphql/queries/get-flows'; import { GET_FLOWS } from 'graphql/queries/get-flows';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import type { Flow } from 'types/flow';
export default function Flows(): React.ReactElement { export default function Flows(): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const [flowName, setFlowName] = React.useState(''); const [flowName, setFlowName] = React.useState('');
const { data } = useQuery(GET_FLOWS); const { data } = useQuery(GET_FLOWS);
const flows: Flow[] = data?.getFlows?.filter((flow: Flow) => flow.name?.toLowerCase().includes(flowName.toLowerCase())); const flows: IFlow[] = data?.getFlows?.filter((flow: IFlow) => flow.name?.toLowerCase().includes(flowName.toLowerCase()));
const onSearchChange = React.useCallback((event) => { const onSearchChange = React.useCallback((event) => {
setFlowName(event.target.value); setFlowName(event.target.value);

View File

@@ -1,40 +0,0 @@
type AppFields = {
key: string;
name: string;
label: string;
type: string;
required: boolean;
readOnly: boolean;
value: string;
description: string;
docUrl: string;
clickToCopy: boolean;
variables?: boolean;
};
type AppConnection = {
id: string;
key: string;
verified: boolean;
createdAt: string;
data: {
[key: string]: any;
};
};
type App = {
key: string;
name: string;
connectionCount: number;
iconUrl: string;
docUrl: string;
primaryColor: string;
fields: AppFields[];
authenticationSteps: any[];
reconnectionSteps: any[];
triggers: any[];
actions: any[];
connections: AppConnection[];
};
export type { App, AppFields, AppConnection };

View File

@@ -1,13 +1,3 @@
type ConnectionData = { import type { IConnection, IJSONObject } from '@automatisch/types';
screenName: string;
}
type Connection = { export type Connection = IConnection<IJSONObject>;
id: string;
key: string;
data: ConnectionData;
verified?: boolean;
createdAt: string;
};
export type { Connection, ConnectionData };

View File

@@ -1,8 +0,0 @@
import type { Step } from './step';
export type Flow = {
id: string;
name: string;
steps: Step[];
active: boolean;
};

View File

@@ -1,25 +0,0 @@
import type { AppFields } from './app';
import type { Connection } from './connection';
export enum StepType {
Trigger = 'trigger',
Action = 'action',
}
export type Step = {
id: string;
key: string | null;
name: string;
appKey: string | null;
type: StepType;
previousStepId: string | null;
parameters: Record<string, unknown>;
connection: Pick<Connection, 'id' | 'verified'>;
status: 'completed' | 'incomplete';
output: Record<string, unknown>;
};
export type Substep = {
name: string;
arguments: AppFields[];
};