Merge pull request #686 from automatisch/lint

Auto format whole project
This commit is contained in:
Ömer Faruk Aydın
2022-11-06 00:06:05 +01:00
committed by GitHub
200 changed files with 2423 additions and 1840 deletions

View File

@@ -1,3 +1,4 @@
{ {
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
} }

View File

@@ -1,11 +1,11 @@
version: "3.9" version: '3.9'
services: services:
main: main:
build: build:
context: ../images/wait-for-postgres context: ../images/wait-for-postgres
network: host network: host
ports: ports:
- "3000:3000" - '3000:3000'
depends_on: depends_on:
- postgres - postgres
- redis - redis
@@ -36,12 +36,12 @@ services:
volumes: volumes:
- automatisch_storage:/automatisch/storage - automatisch_storage:/automatisch/storage
postgres: postgres:
image: "postgres:14.5" image: 'postgres:14.5'
environment: environment:
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: automatisch POSTGRES_DB: automatisch
POSTGRES_USER: automatisch_user POSTGRES_USER: automatisch_user
redis: redis:
image: "redis:7.0.4" image: 'redis:7.0.4'
volumes: volumes:
automatisch_storage: automatisch_storage:

View File

@@ -1,7 +1,5 @@
{ {
"packages": [ "packages": ["packages/*"],
"packages/*"
],
"version": "0.1.5", "version": "0.1.5",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,

View File

@@ -4,6 +4,6 @@ const client = new Client({
host: 'localhost', host: 'localhost',
user: 'postgres', user: 'postgres',
port: 5432, port: 5432,
}) });
export default client; export default client;

View File

@@ -4,7 +4,10 @@ import client from './client';
import User from '../../src/models/user'; import User from '../../src/models/user';
import '../../src/config/orm'; import '../../src/config/orm';
export async function createUser(email = 'user@automatisch.io', password = 'sample') { export async function createUser(
email = 'user@automatisch.io',
password = 'sample'
) {
const UNIQUE_VIOLATION_CODE = '23505'; const UNIQUE_VIOLATION_CODE = '23505';
const userParams = { const userParams = {
email, email,
@@ -29,14 +32,17 @@ export async function createUser(email = 'user@automatisch.io', password = 'samp
} }
} }
export const createDatabaseAndUser = async (database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => { export const createDatabaseAndUser = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
await client.connect(); await client.connect();
await createDatabase(database); await createDatabase(database);
await createDatabaseUser(user); await createDatabaseUser(user);
await grantPrivileges(database, user); await grantPrivileges(database, user);
await client.end(); await client.end();
} };
export const createDatabase = async (database = appConfig.postgresDatabase) => { export const createDatabase = async (database = appConfig.postgresDatabase) => {
const DUPLICATE_DB_CODE = '42P04'; const DUPLICATE_DB_CODE = '42P04';
@@ -51,7 +57,7 @@ export const createDatabase = async (database = appConfig.postgresDatabase) => {
logger.info(`Database: ${database} already exists!`); logger.info(`Database: ${database} already exists!`);
} }
} };
export const createDatabaseUser = async (user = appConfig.postgresUsername) => { export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
const DUPLICATE_OBJECT_CODE = '42710'; const DUPLICATE_OBJECT_CODE = '42710';
@@ -68,25 +74,25 @@ export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
logger.info(`Database User: ${user} already exists!`); logger.info(`Database User: ${user} already exists!`);
} }
} };
export const grantPrivileges = async ( export const grantPrivileges = async (
database = appConfig.postgresDatabase, user = appConfig.postgresUsername database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => { ) => {
await client.query( await client.query(
`GRANT ALL PRIVILEGES ON DATABASE ${database} TO ${user};` `GRANT ALL PRIVILEGES ON DATABASE ${database} TO ${user};`
); );
logger.info( logger.info(`${user} has granted all privileges on ${database}!`);
`${user} has granted all privileges on ${database}!` };
);
}
export const dropDatabase = async () => { export const dropDatabase = async () => {
if (appConfig.appEnv != 'development' && appConfig.appEnv != 'test') { if (appConfig.appEnv != 'development' && appConfig.appEnv != 'test') {
const errorMessage = 'Drop database command can be used only with development or test environments!' const errorMessage =
'Drop database command can be used only with development or test environments!';
logger.error(errorMessage) logger.error(errorMessage);
return; return;
} }
@@ -94,13 +100,15 @@ export const dropDatabase = async () => {
await dropDatabaseAndUser(); await dropDatabaseAndUser();
await client.end(); await client.end();
} };
export const dropDatabaseAndUser = async(database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => { export const dropDatabaseAndUser = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
await client.query(`DROP DATABASE IF EXISTS ${database}`); await client.query(`DROP DATABASE IF EXISTS ${database}`);
logger.info(`Database: ${database} removed!`); logger.info(`Database: ${database} removed!`);
await client.query(`DROP USER IF EXISTS ${user}`); await client.query(`DROP USER IF EXISTS ${user}`);
logger.info(`Database User: ${user} removed!`); logger.info(`Database User: ${user} removed!`);
} };

View File

@@ -10,7 +10,7 @@ const knexConfig = {
user: appConfig.postgresUsername, user: appConfig.postgresUsername,
password: appConfig.postgresPassword, password: appConfig.postgresPassword,
database: appConfig.postgresDatabase, database: appConfig.postgresDatabase,
ssl: appConfig.postgresEnableSsl ssl: appConfig.postgresEnableSsl,
}, },
pool: { min: 0, max: 20 }, pool: { min: 0, max: 20 },
migrations: { migrations: {
@@ -20,7 +20,7 @@ const knexConfig = {
}, },
seeds: { seeds: {
directory: __dirname + '/src/db/seeds', directory: __dirname + '/src/db/seeds',
} },
} };
export default knexConfig; export default knexConfig;

View File

@@ -1,5 +1,3 @@
import sendMessageToChannel from "./send-message-to-channel"; import sendMessageToChannel from './send-message-to-channel';
export default [ export default [sendMessageToChannel];
sendMessageToChannel
];

View File

@@ -17,12 +17,12 @@ export default async function createAuthData($: IGlobalVariable) {
scope: scopes.join(' '), scope: scopes.join(' '),
}); });
const url = `${$.app.apiBaseUrl}/oauth2/authorize?${searchParams.toString()}`; const url = `${
$.app.apiBaseUrl
}/oauth2/authorize?${searchParams.toString()}`;
await $.auth.set({ url }); await $.auth.set({ url });
} catch (error) { } catch (error) {
throw new Error( throw new Error(`Error occured while verifying credentials: ${error}`);
`Error occured while verifying credentials: ${error}`
);
} }
} }

View File

@@ -12,9 +12,10 @@ export default {
readOnly: true, readOnly: true,
value: '{WEB_APP_URL}/app/discord/connections/add', value: '{WEB_APP_URL}/app/discord/connections/add',
placeholder: null, placeholder: null,
description: 'When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.', description:
'When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/discord#oauth-redirect-url', docUrl: 'https://automatisch.io/docs/discord#oauth-redirect-url',
clickToCopy: true clickToCopy: true,
}, },
{ {
key: 'consumerKey', key: 'consumerKey',
@@ -26,7 +27,7 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/discord#consumer-key', docUrl: 'https://automatisch.io/docs/discord#consumer-key',
clickToCopy: false clickToCopy: false,
}, },
{ {
key: 'consumerSecret', key: 'consumerSecret',
@@ -38,7 +39,7 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/discord#consumer-secret', docUrl: 'https://automatisch.io/docs/discord#consumer-secret',
clickToCopy: false clickToCopy: false,
}, },
{ {
key: 'botToken', key: 'botToken',
@@ -50,8 +51,8 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/discord#bot-token', docUrl: 'https://automatisch.io/docs/discord#bot-token',
clickToCopy: false clickToCopy: false,
} },
], ],
authenticationSteps: [ authenticationSteps: [
{ {
@@ -60,7 +61,7 @@ export default {
arguments: [ arguments: [
{ {
name: 'key', name: 'key',
value: '{key}' value: '{key}',
}, },
{ {
name: 'formattedData', name: 'formattedData',
@@ -68,19 +69,19 @@ export default {
properties: [ properties: [
{ {
name: 'consumerKey', name: 'consumerKey',
value: '{fields.consumerKey}' value: '{fields.consumerKey}',
}, },
{ {
name: 'consumerSecret', name: 'consumerSecret',
value: '{fields.consumerSecret}' value: '{fields.consumerSecret}',
}, },
{ {
name: 'botToken', name: 'botToken',
value: '{fields.botToken}' value: '{fields.botToken}',
} },
] ],
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -88,9 +89,9 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
} },
] ],
}, },
{ {
type: 'openWithPopup' as const, type: 'openWithPopup' as const,
@@ -98,9 +99,9 @@ export default {
arguments: [ arguments: [
{ {
name: 'url', name: 'url',
value: '{createAuthData.url}' value: '{createAuthData.url}',
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -108,7 +109,7 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
}, },
{ {
name: 'formattedData', name: 'formattedData',
@@ -116,11 +117,11 @@ export default {
properties: [ properties: [
{ {
name: 'oauthVerifier', name: 'oauthVerifier',
value: '{openAuthPopup.code}' value: '{openAuthPopup.code}',
} },
] ],
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -128,10 +129,10 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
} },
] ],
} },
], ],
createAuthData, createAuthData,

View File

@@ -28,10 +28,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
expires_in: expiresIn, expires_in: expiresIn,
scope: scope, scope: scope,
token_type: tokenType, token_type: tokenType,
guild: { guild: { id: guildId, name: guildName },
id: guildId,
name: guildName,
}
} = verifiedCredentials; } = verifiedCredentials;
await $.auth.set({ await $.auth.set({
@@ -40,7 +37,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
expiresIn, expiresIn,
scope, scope,
tokenType, tokenType,
}) });
const user = await getCurrentUser($); const user = await getCurrentUser($);

View File

@@ -1,5 +1,3 @@
import listChannels from "./list-channels"; import listChannels from './list-channels';
export default [ export default [listChannels];
listChannels,
];

View File

@@ -13,7 +13,9 @@ export default {
error: null, error: null,
}; };
const response = await $.http.get(`/guilds/${$.auth.data.guildId}/channels`); const response = await $.http.get(
`/guilds/${$.auth.data.guildId}/channels`
);
channels.data = response.data channels.data = response.data
.filter((channel: IJSONObject) => { .filter((channel: IJSONObject) => {

View File

@@ -12,9 +12,10 @@ export default {
readOnly: true, readOnly: true,
value: '{WEB_APP_URL}/app/flickr/connections/add', value: '{WEB_APP_URL}/app/flickr/connections/add',
placeholder: null, placeholder: null,
description: 'When asked to input an OAuth callback or redirect URL in Flickr OAuth, enter the URL above.', description:
'When asked to input an OAuth callback or redirect URL in Flickr OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/flickr#oauth-redirect-url', docUrl: 'https://automatisch.io/docs/flickr#oauth-redirect-url',
clickToCopy: true clickToCopy: true,
}, },
{ {
key: 'consumerKey', key: 'consumerKey',
@@ -26,7 +27,7 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/flickr#consumer-key', docUrl: 'https://automatisch.io/docs/flickr#consumer-key',
clickToCopy: false clickToCopy: false,
}, },
{ {
key: 'consumerSecret', key: 'consumerSecret',
@@ -38,8 +39,8 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/flickr#consumer-secret', docUrl: 'https://automatisch.io/docs/flickr#consumer-secret',
clickToCopy: false clickToCopy: false,
} },
], ],
authenticationSteps: [ authenticationSteps: [
{ {
@@ -48,7 +49,7 @@ export default {
arguments: [ arguments: [
{ {
name: 'key', name: 'key',
value: '{key}' value: '{key}',
}, },
{ {
name: 'formattedData', name: 'formattedData',
@@ -56,15 +57,15 @@ export default {
properties: [ properties: [
{ {
name: 'consumerKey', name: 'consumerKey',
value: '{fields.consumerKey}' value: '{fields.consumerKey}',
}, },
{ {
name: 'consumerSecret', name: 'consumerSecret',
value: '{fields.consumerSecret}' value: '{fields.consumerSecret}',
} },
] ],
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -72,9 +73,9 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
} },
] ],
}, },
{ {
type: 'openWithPopup' as const, type: 'openWithPopup' as const,
@@ -82,9 +83,9 @@ export default {
arguments: [ arguments: [
{ {
name: 'url', name: 'url',
value: '{createAuthData.url}' value: '{createAuthData.url}',
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -92,7 +93,7 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
}, },
{ {
name: 'formattedData', name: 'formattedData',
@@ -100,11 +101,11 @@ export default {
properties: [ properties: [
{ {
name: 'oauthVerifier', name: 'oauthVerifier',
value: '{openAuthPopup.oauth_verifier}' value: '{openAuthPopup.oauth_verifier}',
} },
] ],
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -112,10 +113,10 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
} },
] ],
} },
], ],
createAuthData, createAuthData,

View File

@@ -6,7 +6,7 @@ const isStillVerified = async ($: IGlobalVariable) => {
method: 'flickr.test.login', method: 'flickr.test.login',
format: 'json', format: 'json',
nojsoncallback: 1, nojsoncallback: 1,
} };
const response = await $.http.get('/rest', { params }); const response = await $.http.get('/rest', { params });
return !!response.data.user.id; return !!response.data.user.id;
} catch (error) { } catch (error) {

View File

@@ -10,7 +10,7 @@ type TPhotoset = {
title: { title: {
_content: string; _content: string;
}; };
} };
export default { export default {
name: 'List albums', name: 'List albums',
@@ -25,7 +25,7 @@ export default {
format: 'json', format: 'json',
nojsoncallback: 1, nojsoncallback: 1,
}; };
let response = await $.http.get('/rest', { params, }); let response = await $.http.get('/rest', { params });
const aggregatedResponse: TResponse = { const aggregatedResponse: TResponse = {
data: [...response.data.photosets.photoset], data: [...response.data.photosets.photoset],
@@ -35,19 +35,21 @@ export default {
response = await $.http.get('/rest', { response = await $.http.get('/rest', {
params: { params: {
...params, ...params,
page: response.data.photosets.page page: response.data.photosets.page,
} },
}); });
aggregatedResponse.data.push(...response.data.photosets.photoset); aggregatedResponse.data.push(...response.data.photosets.photoset);
} }
aggregatedResponse.data = aggregatedResponse.data.map((photoset: TPhotoset) => { aggregatedResponse.data = aggregatedResponse.data.map(
(photoset: TPhotoset) => {
return { return {
value: photoset.id, value: photoset.id,
name: photoset.title._content, name: photoset.title._content,
} as IJSONObject; } as IJSONObject;
}); }
);
return aggregatedResponse; return aggregatedResponse;
}, },

View File

@@ -3,9 +3,4 @@ import newFavoritePhotos from './new-favorite-photos';
import newPhotos from './new-photos'; import newPhotos from './new-photos';
import newPhotosInAlbums from './new-photos-in-album'; import newPhotosInAlbums from './new-photos-in-album';
export default [ export default [newAlbums, newFavoritePhotos, newPhotos, newPhotosInAlbums];
newAlbums,
newFavoritePhotos,
newPhotos,
newPhotosInAlbums,
];

View File

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

View File

@@ -12,9 +12,10 @@ export default {
readOnly: true, readOnly: true,
value: '{WEB_APP_URL}/app/github/connections/add', value: '{WEB_APP_URL}/app/github/connections/add',
placeholder: null, placeholder: null,
description: 'When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.', description:
'When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/github#oauth-redirect-url', docUrl: 'https://automatisch.io/docs/github#oauth-redirect-url',
clickToCopy: true clickToCopy: true,
}, },
{ {
key: 'consumerKey', key: 'consumerKey',
@@ -26,7 +27,7 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/github#client-id', docUrl: 'https://automatisch.io/docs/github#client-id',
clickToCopy: false clickToCopy: false,
}, },
{ {
key: 'consumerSecret', key: 'consumerSecret',
@@ -38,8 +39,8 @@ export default {
placeholder: null, placeholder: null,
description: null, description: null,
docUrl: 'https://automatisch.io/docs/github#client-secret', docUrl: 'https://automatisch.io/docs/github#client-secret',
clickToCopy: false clickToCopy: false,
} },
], ],
authenticationSteps: [ authenticationSteps: [
{ {
@@ -48,7 +49,7 @@ export default {
arguments: [ arguments: [
{ {
name: 'key', name: 'key',
value: '{key}' value: '{key}',
}, },
{ {
name: 'formattedData', name: 'formattedData',
@@ -56,15 +57,15 @@ export default {
properties: [ properties: [
{ {
name: 'consumerKey', name: 'consumerKey',
value: '{fields.consumerKey}' value: '{fields.consumerKey}',
}, },
{ {
name: 'consumerSecret', name: 'consumerSecret',
value: '{fields.consumerSecret}' value: '{fields.consumerSecret}',
} },
] ],
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -72,9 +73,9 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
} },
] ],
}, },
{ {
type: 'openWithPopup' as const, type: 'openWithPopup' as const,
@@ -82,9 +83,9 @@ export default {
arguments: [ arguments: [
{ {
name: 'url', name: 'url',
value: '{createAuthData.url}' value: '{createAuthData.url}',
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -92,7 +93,7 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
}, },
{ {
name: 'formattedData', name: 'formattedData',
@@ -100,11 +101,11 @@ export default {
properties: [ properties: [
{ {
name: 'oauthVerifier', name: 'oauthVerifier',
value: '{openAuthPopup.code}' value: '{openAuthPopup.code}',
} },
] ],
} },
] ],
}, },
{ {
type: 'mutation' as const, type: 'mutation' as const,
@@ -112,10 +113,10 @@ export default {
arguments: [ arguments: [
{ {
name: 'id', name: 'id',
value: '{createConnection.id}' value: '{createConnection.id}',
} },
] ],
} },
], ],
createAuthData, createAuthData,

View File

@@ -12,9 +12,10 @@ const verifyCredentials = async ($: IGlobalVariable) => {
}, },
{ {
headers: { headers: {
Accept: 'application/json' Accept: 'application/json',
},
} }
}); );
const data = response.data; const data = response.data;

View File

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

View File

@@ -1,15 +1,17 @@
type TRepoOwnerAndRepo = { type TRepoOwnerAndRepo = {
repoOwner?: string; repoOwner?: string;
repo?: string; repo?: string;
} };
export default function getRepoOwnerAndRepo(repoFullName: string): TRepoOwnerAndRepo { export default function getRepoOwnerAndRepo(
repoFullName: string
): TRepoOwnerAndRepo {
if (!repoFullName) return {}; if (!repoFullName) return {};
const [repoOwner, repo] = repoFullName.split('/'); const [repoOwner, repo] = repoFullName.split('/');
return { return {
repoOwner, repoOwner,
repo repo,
}; };
} }

View File

@@ -1,7 +1,4 @@
import listLabels from './list-labels'; import listLabels from './list-labels';
import listRepos from './list-repos'; import listRepos from './list-repos';
export default [ export default [listLabels, listRepos];
listLabels,
listRepos,
];

View File

@@ -7,10 +7,9 @@ export default {
key: 'listLabels', key: 'listLabels',
async run($: IGlobalVariable) { async run($: IGlobalVariable) {
const { const { repoOwner, repo } = getRepoOwnerAndRepo(
repoOwner, $.step.parameters.repo as string
repo, );
} = getRepoOwnerAndRepo($.step.parameters.repo as string);
if (!repo) return { data: [] }; if (!repo) return { data: [] };

View File

@@ -3,9 +3,4 @@ import newPullRequests from './new-pull-requests';
import newStargazers from './new-stargazers'; import newStargazers from './new-stargazers';
import newWatchers from './new-watchers'; import newWatchers from './new-watchers';
export default [ export default [newIssues, newPullRequests, newStargazers, newWatchers];
newIssues,
newPullRequests,
newStargazers,
newWatchers,
];

View File

@@ -10,15 +10,13 @@ export default async function createAuthData($: IGlobalVariable) {
const searchParams = qs.stringify({ const searchParams = qs.stringify({
client_id: $.auth.data.consumerKey as string, client_id: $.auth.data.consumerKey as string,
redirect_uri: redirectUri, redirect_uri: redirectUri,
response_type: 'code' response_type: 'code',
}) });
await $.auth.set({ await $.auth.set({
url: `${$.auth.data.oauth2Url}/authorize?${searchParams}`, url: `${$.auth.data.oauth2Url}/authorize?${searchParams}`,
}); });
} catch (error) { } catch (error) {
throw new Error( throw new Error(`Error occured while verifying credentials: ${error}`);
`Error occured while verifying credentials: ${error}`
);
} }
} }

View File

@@ -34,8 +34,8 @@ export default {
{ {
label: 'sandbox', label: 'sandbox',
value: 'https://test.salesforce.com/services/oauth2', value: 'https://test.salesforce.com/services/oauth2',
} },
] ],
}, },
{ {
key: 'consumerKey', key: 'consumerKey',
@@ -76,7 +76,7 @@ export default {
properties: [ properties: [
{ {
name: 'oauth2Url', name: 'oauth2Url',
value: '{fields.oauth2Url}' value: '{fields.oauth2Url}',
}, },
{ {
name: 'consumerKey', name: 'consumerKey',

View File

@@ -13,10 +13,10 @@ const verifyCredentials = async ($: IGlobalVariable) => {
grant_type: 'authorization_code', grant_type: 'authorization_code',
client_id: $.auth.data.consumerKey as string, client_id: $.auth.data.consumerKey as string,
client_secret: $.auth.data.consumerSecret as string, client_secret: $.auth.data.consumerSecret as string,
redirect_uri: redirectUri redirect_uri: redirectUri,
}); });
const { data } = await $.http.post( const { data } = await $.http.post(
`${$.auth.data.oauth2Url}/token?${searchParams}`, `${$.auth.data.oauth2Url}/token?${searchParams}`
); );
await $.auth.set({ await $.auth.set({

View File

@@ -1,7 +1,4 @@
import listObjects from './list-objects'; import listObjects from './list-objects';
import listFields from './list-fields'; import listFields from './list-fields';
export default [ export default [listObjects, listFields];
listObjects,
listFields,
];

View File

@@ -2,19 +2,21 @@ import { IGlobalVariable } from '@automatisch/types';
type TResponse = { type TResponse = {
sobjects: TObject[]; sobjects: TObject[];
} };
type TObject = { type TObject = {
name: string; name: string;
label: string; label: string;
} };
export default { export default {
name: 'List objects', name: 'List objects',
key: 'listObjects', key: 'listObjects',
async run($: IGlobalVariable) { async run($: IGlobalVariable) {
const response = await $.http.get<TResponse>('/services/data/v56.0/sobjects'); const response = await $.http.get<TResponse>(
'/services/data/v56.0/sobjects'
);
const objects = response.data.sobjects.map((object) => { const objects = response.data.sobjects.map((object) => {
return { return {

View File

@@ -1,5 +1,3 @@
import updatedFieldInRecords from "./updated-field-in-records"; import updatedFieldInRecords from './updated-field-in-records';
export default [ export default [updatedFieldInRecords];
updatedFieldInRecords
];

View File

@@ -23,7 +23,7 @@ const updatedFieldInRecord = async ($: IGlobalVariable): Promise<void> => {
const options = { const options = {
params: { params: {
q: getQuery(object, limit, offset), q: getQuery(object, limit, offset),
} },
}; };
response = await $.http.get('/services/data/v56.0/query', options); response = await $.http.get('/services/data/v56.0/query', options);
@@ -34,7 +34,7 @@ const updatedFieldInRecord = async ($: IGlobalVariable): Promise<void> => {
raw: record, raw: record,
meta: { meta: {
internalId: `${record.Id}-${record[field]}`, internalId: `${record.Id}-${record[field]}`,
} },
}); });
} }

View File

@@ -3,7 +3,8 @@ const cronTimes = {
everyHourExcludingWeekends: '0 * * * 1-5', everyHourExcludingWeekends: '0 * * * 1-5',
everyDayAt: (hour: number) => `0 ${hour} * * *`, everyDayAt: (hour: number) => `0 ${hour} * * *`,
everyDayExcludingWeekendsAt: (hour: number) => `0 ${hour} * * 1-5`, everyDayExcludingWeekendsAt: (hour: number) => `0 ${hour} * * 1-5`,
everyWeekOnAndAt: (weekday: number, hour: number) => `0 ${hour} * * ${weekday}`, everyWeekOnAndAt: (weekday: number, hour: number) =>
`0 ${hour} * * ${weekday}`,
everyMonthOnAndAt: (day: number, hour: number) => `0 ${hour} ${day} * *`, everyMonthOnAndAt: (day: number, hour: number) => `0 ${hour} ${day} * *`,
}; };

View File

@@ -4,7 +4,9 @@ import cronParser from 'cron-parser';
export default function getNextCronDateTime(cronString: string) { export default function getNextCronDateTime(cronString: string) {
const cronDate = cronParser.parseExpression(cronString); const cronDate = cronParser.parseExpression(cronString);
const matchingNextCronDateTime = cronDate.next(); const matchingNextCronDateTime = cronDate.next();
const matchingNextDateTime = DateTime.fromJSDate(matchingNextCronDateTime.toDate()); const matchingNextDateTime = DateTime.fromJSDate(
matchingNextCronDateTime.toDate()
);
return matchingNextDateTime; return matchingNextDateTime;
}; }

View File

@@ -3,9 +3,4 @@ import everyDay from './every-day';
import everyWeek from './every-week'; import everyWeek from './every-week';
import everyMonth from './every-month'; import everyMonth from './every-month';
export default [ export default [everyHour, everyDay, everyWeek, everyMonth];
everyHour,
everyDay,
everyWeek,
everyMonth,
];

View File

@@ -1,7 +1,4 @@
import findMessage from './find-message'; import findMessage from './find-message';
import sendMessageToChannel from './send-a-message-to-channel'; import sendMessageToChannel from './send-a-message-to-channel';
export default [ export default [findMessage, sendMessageToChannel];
findMessage,
sendMessageToChannel,
];

View File

@@ -38,7 +38,8 @@ export default defineAction({
type: 'dropdown' as const, type: 'dropdown' as const,
required: false, required: false,
value: false, value: false,
description: 'If you choose no, this message will appear to come from you. Direct messages are always sent by bots.', description:
'If you choose no, this message will appear to come from you. Direct messages are always sent by bots.',
variables: false, variables: false,
options: [ options: [
{ {
@@ -48,8 +49,8 @@ export default defineAction({
{ {
label: 'No', label: 'No',
value: false, value: false,
} },
] ],
}, },
{ {
label: 'Bot name', label: 'Bot name',
@@ -57,7 +58,8 @@ export default defineAction({
type: 'string' as const, type: 'string' as const,
required: true, required: true,
value: 'Automatisch', value: 'Automatisch',
description: 'Specify the bot name which appears as a bold username above the message inside Slack. Defaults to Automatisch.', description:
'Specify the bot name which appears as a bold username above the message inside Slack. Defaults to Automatisch.',
variables: true, variables: true,
}, },
{ {
@@ -65,7 +67,8 @@ export default defineAction({
key: 'botIcon', key: 'botIcon',
type: 'string' as const, type: 'string' as const,
required: false, required: false,
description: 'Either an image url or an emoji available to your team (surrounded by :). For example, https://example.com/icon_256.png or :robot_face:', description:
'Either an image url or an emoji available to your team (surrounded by :). For example, https://example.com/icon_256.png or :robot_face:',
variables: true, variables: true,
}, },
], ],

View File

@@ -7,7 +7,7 @@ type TData = {
username?: string; username?: string;
icon_url?: string; icon_url?: string;
icon_emoji?: string; icon_emoji?: string;
} };
const postMessage = async ($: IGlobalVariable) => { const postMessage = async ($: IGlobalVariable) => {
const { parameters } = $.step; const { parameters } = $.step;
@@ -37,11 +37,9 @@ const postMessage = async ($: IGlobalVariable) => {
sendAsBot, sendAsBot,
}; };
const response = await $.http.post( const response = await $.http.post('/chat.postMessage', data, {
'/chat.postMessage', additionalProperties: customConfig,
data, });
{ additionalProperties: customConfig },
);
if (response.data.ok === false) { if (response.data.ok === false) {
throw new Error(JSON.stringify(response.data)); throw new Error(JSON.stringify(response.data));

View File

@@ -59,4 +59,4 @@ export default async function createAuthData($: IGlobalVariable) {
await $.auth.set({ await $.auth.set({
url, url,
}); });
}; }

View File

@@ -22,14 +22,9 @@ const verifyCredentials = async ($: IGlobalVariable) => {
const { const {
bot_user_id: botId, bot_user_id: botId,
authed_user: { authed_user: { id: userId, access_token: userAccessToken },
id: userId,
access_token: userAccessToken,
},
access_token: botAccessToken, access_token: botAccessToken,
team: { team: { name: teamName },
name: teamName,
}
} = response.data; } = response.data;
await $.auth.set({ await $.auth.set({
@@ -44,7 +39,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
const currentUser = await getCurrentUser($); const currentUser = await getCurrentUser($);
await $.auth.set({ await $.auth.set({
screenName: `${currentUser.real_name} @ ${teamName}` screenName: `${currentUser.real_name} @ ${teamName}`,
}); });
}; };

View File

@@ -3,9 +3,9 @@ import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => { const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const authData = $.auth.data; const authData = $.auth.data;
if ( if (
requestConfig.headers requestConfig.headers &&
&& authData?.userAccessToken authData?.userAccessToken &&
&& authData?.botAccessToken authData?.botAccessToken
) { ) {
if (requestConfig.additionalProperties?.sendAsBot) { if (requestConfig.additionalProperties?.sendAsBot) {
requestConfig.headers.Authorization = `Bearer ${authData.botAccessToken}`; requestConfig.headers.Authorization = `Bearer ${authData.botAccessToken}`;
@@ -14,7 +14,8 @@ const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
} }
} }
requestConfig.headers['Content-Type'] = requestConfig.headers['Content-Type'] || 'application/json; charset=utf-8'; requestConfig.headers['Content-Type'] =
requestConfig.headers['Content-Type'] || 'application/json; charset=utf-8';
return requestConfig; return requestConfig;
}; };

View File

@@ -3,7 +3,7 @@ import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => { const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const params = { const params = {
user: $.auth.data.userId as string, user: $.auth.data.userId as string,
} };
const response = await $.http.get('/users.info', { params }); const response = await $.http.get('/users.info', { params });
const currentUser = response.data.user; const currentUser = response.data.user;

View File

@@ -3,9 +3,4 @@ import newFollowerOfMe from './new-follower-of-me';
import searchTweets from './search-tweets'; import searchTweets from './search-tweets';
import userTweets from './user-tweets'; import userTweets from './user-tweets';
export default [ export default [myTweets, newFollowerOfMe, searchTweets, userTweets];
myTweets,
newFollowerOfMe,
searchTweets,
userTweets,
];

View File

@@ -1,10 +1,10 @@
import appConfig from './app' import appConfig from './app';
const corsOptions = { const corsOptions = {
origin: appConfig.webAppUrl, origin: appConfig.webAppUrl,
methods: 'POST', methods: 'POST',
credentials: true, credentials: true,
optionsSuccessStatus: 200, optionsSuccessStatus: 200,
} };
export default corsOptions; export default corsOptions;

View File

@@ -1,4 +1,4 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('users', (table) => { return knex.schema.createTable('users', (table) => {
@@ -8,10 +8,8 @@ export async function up(knex: Knex): Promise<void> {
table.timestamps(true, true); table.timestamps(true, true);
}); });
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('users'); return knex.schema.dropTable('users');
} }

View File

@@ -1,8 +1,8 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('credentials', (table) => { return knex.schema.createTable('credentials', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').notNullable(); table.string('key').notNullable();
table.string('display_name').notNullable(); table.string('display_name').notNullable();
table.text('data').notNullable(); table.text('data').notNullable();
@@ -11,7 +11,7 @@ export async function up(knex: Knex): Promise<void> {
table.timestamps(true, true); table.timestamps(true, true);
}); });
}; }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('credentials'); return knex.schema.dropTable('credentials');

View File

@@ -1,4 +1,4 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.table('credentials', (table) => { return knex.schema.table('credentials', (table) => {

View File

@@ -1,4 +1,4 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.renameTable('credentials', 'connections'); return knex.schema.renameTable('credentials', 'connections');

View File

@@ -2,7 +2,7 @@ import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('steps', (table) => { return knex.schema.createTable('steps', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').notNullable(); table.string('key').notNullable();
table.string('app_key').notNullable(); table.string('app_key').notNullable();
table.string('type').notNullable(); table.string('type').notNullable();

View File

@@ -1,14 +1,14 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('flows', (table) => { return knex.schema.createTable('flows', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('name'); table.string('name');
table.uuid('user_id').references('id').inTable('users'); table.uuid('user_id').references('id').inTable('users');
table.timestamps(true, true); table.timestamps(true, true);
}); });
}; }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('flows'); return knex.schema.dropTable('flows');

View File

@@ -1,15 +1,11 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.table('steps', (table) => { return knex.schema.table('steps', (table) => {
table table.uuid('flow_id').references('id').inTable('flows');
.uuid('flow_id')
.references('id')
.inTable('flows');
}); });
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
return knex.schema.table('steps', (table) => { return knex.schema.table('steps', (table) => {
table.dropColumn('flow_id'); table.dropColumn('flow_id');

View File

@@ -1,16 +1,15 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.alterTable('steps', table => { return knex.schema.alterTable('steps', (table) => {
table.string('key').nullable().alter(); table.string('key').nullable().alter();
table.string('app_key').nullable().alter(); table.string('app_key').nullable().alter();
}) });
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
return knex.schema.alterTable('steps', table => { return knex.schema.alterTable('steps', (table) => {
table.string('key').notNullable().alter(); table.string('key').notNullable().alter();
table.string('app_key').notNullable().alter(); table.string('app_key').notNullable().alter();
}) });
} }

View File

@@ -1,8 +1,8 @@
import { Knex } from "knex"; import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.table('flows', (table) => { return knex.schema.table('flows', (table) => {
table.boolean('active').defaultTo(false) table.boolean('active').defaultTo(false);
}); });
} }

View File

@@ -2,7 +2,7 @@ import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('executions', (table) => { return knex.schema.createTable('executions', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.uuid('flow_id').references('id').inTable('flows'); table.uuid('flow_id').references('id').inTable('flows');
table.boolean('test_run').notNullable().defaultTo(false); table.boolean('test_run').notNullable().defaultTo(false);

View File

@@ -2,7 +2,7 @@ import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('execution_steps', (table) => { return knex.schema.createTable('execution_steps', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.uuid('execution_id').references('id').inTable('executions'); table.uuid('execution_id').references('id').inTable('executions');
table.uuid('step_id').references('id').inTable('steps'); table.uuid('step_id').references('id').inTable('steps');
table.string('status'); table.string('status');

View File

@@ -1,4 +1,4 @@
import { Knex } from "knex"; import { Knex } from 'knex';
async function addDeletedColumn(knex: Knex, tableName: string) { async function addDeletedColumn(knex: Knex, tableName: string) {
return await knex.schema.table(tableName, (table) => { return await knex.schema.table(tableName, (table) => {

View File

@@ -32,13 +32,13 @@ const createFlow = async (
type: 'trigger', type: 'trigger',
position: 1, position: 1,
appKey, appKey,
connectionId connectionId,
}); });
await Step.query().insert({ await Step.query().insert({
flowId: flow.id, flowId: flow.id,
type: 'action', type: 'action',
position: 2 position: 2,
}); });
return flow; return flow;

View File

@@ -18,7 +18,9 @@ const resetConnection = async (
}) })
.throwIfNotFound(); .throwIfNotFound();
if (!connection.formattedData) { return null; } if (!connection.formattedData) {
return null;
}
connection = await connection.$query().patchAndFetch({ connection = await connection.$query().patchAndFetch({
formattedData: { screenName: connection.formattedData.screenName }, formattedData: { screenName: connection.formattedData.screenName },

View File

@@ -1,6 +1,10 @@
import Context from '../../types/express/context'; import Context from '../../types/express/context';
const getCurrentUser = async (_parent: unknown, _params: unknown, context: Context) => { const getCurrentUser = async (
_parent: unknown,
_params: unknown,
context: Context
) => {
return context.currentUser; return context.currentUser;
}; };

View File

@@ -13,8 +13,8 @@ const getExecution = async (
.$relatedQuery('executions') .$relatedQuery('executions')
.withGraphFetched({ .withGraphFetched({
flow: { flow: {
steps: true steps: true,
} },
}) })
.findById(params.executionId) .findById(params.executionId)
.throwIfNotFound(); .throwIfNotFound();

View File

@@ -13,12 +13,12 @@ const getFlows = async (_parent: unknown, params: Params, context: Context) => {
const flowsQuery = context.currentUser const flowsQuery = context.currentUser
.$relatedQuery('flows') .$relatedQuery('flows')
.joinRelated({ .joinRelated({
steps: true steps: true,
}) })
.withGraphFetched({ .withGraphFetched({
steps: { steps: {
connection: true connection: true,
} },
}) })
.where((builder) => { .where((builder) => {
if (params.connectionId) { if (params.connectionId) {

View File

@@ -3,7 +3,7 @@ import appConfig from '../../config/app';
const healthcheck = () => { const healthcheck = () => {
return { return {
version: appConfig.version, version: appConfig.version,
} };
}; };
export default healthcheck; export default healthcheck;

View File

@@ -1,4 +1,8 @@
import { IApp, IAuthenticationStep, IAuthenticationStepField } from '@automatisch/types'; import {
IApp,
IAuthenticationStep,
IAuthenticationStepField,
} from '@automatisch/types';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
const connectionIdArgument = { const connectionIdArgument = {
@@ -9,16 +13,11 @@ const connectionIdArgument = {
const resetConnectionStep = { const resetConnectionStep = {
type: 'mutation' as const, type: 'mutation' as const,
name: 'resetConnection', name: 'resetConnection',
arguments: [ arguments: [connectionIdArgument],
connectionIdArgument,
],
}; };
function replaceCreateConnection(string: string) { function replaceCreateConnection(string: string) {
return string.replace( return string.replace('{createConnection.id}', '{connection.id}');
'{createConnection.id}',
'{connection.id}'
);
} }
function removeAppKeyArgument(args: IAuthenticationStepField[]) { function removeAppKeyArgument(args: IAuthenticationStepField[]) {
@@ -36,7 +35,7 @@ function addConnectionId(step: IAuthenticationStep) {
return { return {
name: property.name, name: property.name,
value: replaceCreateConnection(property.value), value: replaceCreateConnection(property.value),
} };
}); });
} }
@@ -60,7 +59,7 @@ function replaceCreateConnectionsWithUpdate(steps: IAuthenticationStep[]) {
} }
return step; return step;
}) });
} }
function addReconnectionSteps(app: IApp): IApp { function addReconnectionSteps(app: IApp): IApp {
@@ -68,12 +67,11 @@ function addReconnectionSteps(app: IApp): IApp {
if (hasReconnectionSteps) return app; if (hasReconnectionSteps) return app;
const updatedSteps = replaceCreateConnectionsWithUpdate(app.auth.authenticationSteps); const updatedSteps = replaceCreateConnectionsWithUpdate(
app.auth.authenticationSteps
);
app.auth.reconnectionSteps = [ app.auth.reconnectionSteps = [resetConnectionStep, ...updatedSteps];
resetConnectionStep,
...updatedSteps,
]
return app; return app;
} }

View File

@@ -16,7 +16,7 @@ const appInfoConverter = (rawAppData: IApp) => {
}; };
} }
return field return field;
}); });
} }

View File

@@ -3,15 +3,15 @@ import logger from './logger';
type Error = { type Error = {
message: string; message: string;
} };
const errorHandler = (err: Error, req: Request, res: Response): void => { const errorHandler = (err: Error, req: Request, res: Response): void => {
if (err.message === 'Not Found') { if (err.message === 'Not Found') {
res.status(404).end() res.status(404).end();
} else { } else {
logger.error(err.message) logger.error(err.message);
res.status(500).end() res.status(500).end();
}
} }
};
export default errorHandler; export default errorHandler;

View File

@@ -75,7 +75,10 @@ const globalVariable = async (
}, },
}, },
pushTriggerItem: (triggerItem: ITriggerItem) => { pushTriggerItem: (triggerItem: ITriggerItem) => {
if (isAlreadyProcessed(triggerItem.meta.internalId) && !$.execution.testRun) { if (
isAlreadyProcessed(triggerItem.meta.internalId) &&
!$.execution.testRun
) {
// early exit as we do not want to process duplicate items in actual executions // early exit as we do not want to process duplicate items in actual executions
throw new EarlyExitError(); throw new EarlyExitError();
} }

View File

@@ -10,7 +10,7 @@ const levels = {
}; };
const level = () => { const level = () => {
return appConfig.appEnv === 'development' ? 'debug' : 'info' return appConfig.appEnv === 'development' ? 'debug' : 'info';
}; };
const colors = { const colors = {
@@ -27,8 +27,8 @@ const format = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.colorize({ all: true }), winston.format.colorize({ all: true }),
winston.format.printf( winston.format.printf(
(info) => `${info.timestamp} [${info.level}]: ${info.message}`, (info) => `${info.timestamp} [${info.level}]: ${info.message}`
), )
); );
const transports = [ const transports = [

View File

@@ -3,13 +3,14 @@ import { Request } from 'express';
import logger from './logger'; import logger from './logger';
const stream: StreamOptions = { const stream: StreamOptions = {
write: (message) => logger.http(message.substring(0, message.lastIndexOf("\n"))) write: (message) =>
logger.http(message.substring(0, message.lastIndexOf('\n'))),
}; };
const registerGraphQLToken = () => { const registerGraphQLToken = () => {
morgan.token("graphql-query", (req: Request) => { morgan.token('graphql-query', (req: Request) => {
if (req.body.query) { if (req.body.query) {
return `GraphQL ${req.body.query}` return `GraphQL ${req.body.query}`;
} }
}); });
}; };
@@ -17,7 +18,7 @@ const registerGraphQLToken = () => {
registerGraphQLToken(); registerGraphQLToken();
const morganMiddleware = morgan( const morganMiddleware = morgan(
":method :url :status :res[content-length] - :response-time ms\n:graphql-query", ':method :url :status :res[content-length] - :response-time ms\n:graphql-query',
{ stream } { stream }
); );

View File

@@ -25,7 +25,10 @@ export default function parseLinkHeader(link: string): TParsedLinkHeader {
const items = link.split(','); const items = link.split(',');
for (const item of items) { for (const item of items) {
const [rawUriReference, ...rawLinkParameters] = item.split(';') as [string, ...string[]]; const [rawUriReference, ...rawLinkParameters] = item.split(';') as [
string,
...string[]
];
const trimmedUriReference = rawUriReference.trim(); const trimmedUriReference = rawUriReference.trim();
const reference = trimmedUriReference.slice(1, -1); const reference = trimmedUriReference.slice(1, -1);

View File

@@ -36,7 +36,10 @@ class Base extends Model {
this.updatedAt = new Date().toISOString(); this.updatedAt = new Date().toISOString();
} }
async $beforeUpdate(opts: ModelOptions, queryContext: QueryContext): Promise<void> { async $beforeUpdate(
opts: ModelOptions,
queryContext: QueryContext
): Promise<void> {
this.updatedAt = new Date().toISOString(); this.updatedAt = new Date().toISOString();
await super.$beforeUpdate(opts, queryContext); await super.$beforeUpdate(opts, queryContext);

View File

@@ -1,20 +1,34 @@
import { Model, Page, PartialModelObject, ForClassMethod, AnyQueryBuilder } from "objection"; import {
Model,
Page,
PartialModelObject,
ForClassMethod,
AnyQueryBuilder,
} from 'objection';
const DELETED_COLUMN_NAME = 'deleted_at'; const DELETED_COLUMN_NAME = 'deleted_at';
const buildQueryBuidlerForClass = (): ForClassMethod => { const buildQueryBuidlerForClass = (): ForClassMethod => {
return (modelClass) => { return (modelClass) => {
const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(ExtendedQueryBuilder, modelClass); const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(
ExtendedQueryBuilder,
modelClass
);
qb.onBuild((builder) => { qb.onBuild((builder) => {
if (!builder.context().withSoftDeleted) { if (!builder.context().withSoftDeleted) {
builder.whereNull(`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`); builder.whereNull(
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
);
} }
}); });
return qb; return qb;
}; };
}; };
class ExtendedQueryBuilder<M extends Model, R = M[]> extends Model.QueryBuilder<M, R> { class ExtendedQueryBuilder<M extends Model, R = M[]> extends Model.QueryBuilder<
M,
R
> {
ArrayQueryBuilderType!: ExtendedQueryBuilder<M, M[]>; ArrayQueryBuilderType!: ExtendedQueryBuilder<M, M[]>;
SingleQueryBuilderType!: ExtendedQueryBuilder<M, M>; SingleQueryBuilderType!: ExtendedQueryBuilder<M, M>;
MaybeSingleQueryBuilderType!: ExtendedQueryBuilder<M, M | undefined>; MaybeSingleQueryBuilderType!: ExtendedQueryBuilder<M, M | undefined>;
@@ -25,7 +39,7 @@ class ExtendedQueryBuilder<M extends Model, R = M[]> extends Model.QueryBuilder<
delete() { delete() {
return this.patch({ return this.patch({
[DELETED_COLUMN_NAME]: (new Date()).toISOString(), [DELETED_COLUMN_NAME]: new Date().toISOString(),
} as unknown as PartialModelObject<M>); } as unknown as PartialModelObject<M>);
} }

View File

@@ -2,13 +2,13 @@ import FieldType from './field';
import AuthenticationStepType from './authentication-step'; import AuthenticationStepType from './authentication-step';
type AppInfo = { type AppInfo = {
name: string, name: string;
key: string, key: string;
iconUrl: string, iconUrl: string;
docUrl: string, docUrl: string;
primaryColor: string, primaryColor: string;
fields: FieldType[], fields: FieldType[];
authenticationSteps?: AuthenticationStepType[] authenticationSteps?: AuthenticationStepType[];
} };
export default AppInfo; export default AppInfo;

View File

@@ -1,10 +1,10 @@
type AuthenticationStepField = { type AuthenticationStepField = {
name: string, name: string;
value: string | null, value: string | null;
fields?: { fields?: {
name: string, name: string;
value: string | null value: string | null;
}[] }[];
} };
export default AuthenticationStepField; export default AuthenticationStepField;

View File

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

View File

@@ -1,14 +1,14 @@
type Field = { type Field = {
key: string, key: string;
label: string, label: string;
type: string, type: string;
required: boolean, required: boolean;
readOnly: boolean, readOnly: boolean;
value: string, value: string;
placeholder: string | null, placeholder: string | null;
description: string, description: string;
docUrl: string, docUrl: string;
clickToCopy: boolean clickToCopy: boolean;
} };
export default Field; export default Field;

View File

@@ -2,6 +2,6 @@ import test from 'ava';
const fn = () => 'foo'; const fn = () => 'foo';
test('fn() returns foo', t => { test('fn() returns foo', (t) => {
t.is(fn(), 'foo'); t.is(fn(), 'foo');
}); });

View File

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

View File

@@ -11,7 +11,7 @@ export default class StartWorker extends Command {
char: 'e', char: 'e',
}), }),
'env-file': Flags.string(), 'env-file': Flags.string(),
} };
async prepareEnvVars(): Promise<void> { async prepareEnvVars(): Promise<void> {
const { flags } = await this.parse(StartWorker); const { flags } = await this.parse(StartWorker);

View File

@@ -11,7 +11,7 @@ export default class Start extends Command {
char: 'e', char: 'e',
}), }),
'env-file': Flags.string(), 'env-file': Flags.string(),
} };
get isProduction() { get isProduction() {
return process.env.APP_ENV === 'production'; return process.env.APP_ENV === 'production';
@@ -46,7 +46,7 @@ export default class Start extends Command {
await utils.createDatabaseAndUser( await utils.createDatabaseAndUser(
process.env.POSTGRES_DATABASE, process.env.POSTGRES_DATABASE,
process.env.POSTGRES_USERNAME, process.env.POSTGRES_USERNAME
); );
} }

View File

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

View File

@@ -1,4 +1,4 @@
const { defineConfig } = require("cypress"); const { defineConfig } = require('cypress');
const TO_BE_PROVIDED = 'HAS_TO_BE_PROVIDED_IN_cypress.env.json'; const TO_BE_PROVIDED = 'HAS_TO_BE_PROVIDED_IN_cypress.env.json';
@@ -6,12 +6,12 @@ module.exports = defineConfig({
e2e: { e2e: {
baseUrl: 'http://localhost:3001', baseUrl: 'http://localhost:3001',
env: { env: {
login_email: "user@automatisch.io", login_email: 'user@automatisch.io',
login_password: "sample", login_password: 'sample',
slack_user_token: TO_BE_PROVIDED, slack_user_token: TO_BE_PROVIDED,
}, },
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
viewportWidth: 1280, viewportWidth: 1280,
viewportHeight: 768 viewportHeight: 768,
}, },
}); });

View File

@@ -23,24 +23,17 @@ describe('Connections page', () => {
context('can add connection', () => { context('can add connection', () => {
it('has a button to open add connection dialog', () => { it('has a button to open add connection dialog', () => {
cy cy.og('add-connection-button').scrollIntoView().should('be.visible');
.og('add-connection-button')
.scrollIntoView()
.should('be.visible');
}); });
it('add connection button takes user to add connection page', () => { it('add connection button takes user to add connection page', () => {
cy cy.og('add-connection-button').click({ force: true });
.og('add-connection-button')
.click({ force: true });
cy.location('pathname').should('equal', '/app/slack/connections/add'); cy.location('pathname').should('equal', '/app/slack/connections/add');
}); });
it('shows add connection dialog to create a new connection', () => { it('shows add connection dialog to create a new connection', () => {
cy cy.get('input[name="accessToken"]').type(Cypress.env('slack_user_token'));
.get('input[name="accessToken"]')
.type(Cypress.env('slack_user_token'));
cy.og('create-connection-button').click(); cy.og('create-connection-button').click();

View File

@@ -27,10 +27,7 @@ describe('Flow editor page', () => {
}); });
it('choose an event', () => { it('choose an event', () => {
cy cy.og('choose-event-autocomplete').should('be.visible').click();
.og('choose-event-autocomplete')
.should('be.visible')
.click();
cy.get('li[role="option"]:contains("Every hour")').click(); cy.get('li[role="option"]:contains("Every hour")').click();
}); });
@@ -42,13 +39,12 @@ describe('Flow editor page', () => {
it('collapses the substep', () => { it('collapses the substep', () => {
cy.og('choose-app-autocomplete').should('not.be.visible'); cy.og('choose-app-autocomplete').should('not.be.visible');
cy.og('choose-event-autocomplete').should('not.be.visible'); cy.og('choose-event-autocomplete').should('not.be.visible');
}) });
}); });
context('set up a trigger', () => { context('set up a trigger', () => {
it('choose "yes" in "trigger on weekends?"', () => { it('choose "yes" in "trigger on weekends?"', () => {
cy cy.og('parameters.triggersOnWeekend-autocomplete')
.og('parameters.triggersOnWeekend-autocomplete')
.should('be.visible') .should('be.visible')
.click(); .click();
@@ -60,7 +56,9 @@ describe('Flow editor page', () => {
}); });
it('collapses the substep', () => { it('collapses the substep', () => {
cy.og('parameters.triggersOnWeekend-autocomplete').should('not.exist'); cy.og('parameters.triggersOnWeekend-autocomplete').should(
'not.exist'
);
}); });
}); });
@@ -88,12 +86,11 @@ describe('Flow editor page', () => {
}); });
it('choose an event', () => { it('choose an event', () => {
cy cy.og('choose-event-autocomplete').should('be.visible').click();
.og('choose-event-autocomplete')
.should('be.visible')
.click();
cy.get('li[role="option"]:contains("Send a message to channel")').click(); cy.get(
'li[role="option"]:contains("Send a message to channel")'
).click();
}); });
it('continue to next step', () => { it('continue to next step', () => {
@@ -130,14 +127,16 @@ describe('Flow editor page', () => {
}); });
it('arrange message text', () => { it('arrange message text', () => {
cy cy.og('power-input', ' [contenteditable]')
.og('power-input', ' [contenteditable]')
.click() .click()
.type(`Hello from e2e tests! Here is the first suggested variable's value; `); .type(
`Hello from e2e tests! Here is the first suggested variable's value; `
);
cy cy.og('power-input-suggestion-group')
.og('power-input-suggestion-group').first() .first()
.og('power-input-suggestion-item').first() .og('power-input-suggestion-item')
.first()
.click(); .click();
cy.clickOutside(); cy.clickOutside();
@@ -150,9 +149,7 @@ describe('Flow editor page', () => {
}); });
it('collapses the substep', () => { it('collapses the substep', () => {
cy cy.og('power-input', ' [contenteditable]').should('not.exist');
.og('power-input', ' [contenteditable]')
.should('not.exist');
}); });
}); });
@@ -176,10 +173,7 @@ describe('Flow editor page', () => {
it('publish flow', () => { it('publish flow', () => {
cy.og('unpublish-flow-button').should('not.exist'); cy.og('unpublish-flow-button').should('not.exist');
cy cy.og('publish-flow-button').should('be.visible').click();
.og('publish-flow-button')
.should('be.visible')
.click();
cy.og('publish-flow-button').should('not.exist'); cy.og('publish-flow-button').should('not.exist');
}); });
@@ -191,27 +185,19 @@ describe('Flow editor page', () => {
}); });
it('unpublish from snackbar', () => { it('unpublish from snackbar', () => {
cy cy.og('unpublish-flow-from-snackbar').click();
.og('unpublish-flow-from-snackbar')
.click();
cy.og('flow-cannot-edit-info-snackbar').should('not.exist'); cy.og('flow-cannot-edit-info-snackbar').should('not.exist');
}) });
it('publish once again', () => { it('publish once again', () => {
cy cy.og('publish-flow-button').should('be.visible').click();
.og('publish-flow-button')
.should('be.visible')
.click();
cy.og('publish-flow-button').should('not.exist'); cy.og('publish-flow-button').should('not.exist');
}); });
it('unpublish from layout top bar', () => { it('unpublish from layout top bar', () => {
cy cy.og('unpublish-flow-button').should('be.visible').click();
.og('unpublish-flow-button')
.should('be.visible')
.click();
cy.og('unpublish-flow-button').should('not.exist'); cy.og('unpublish-flow-button').should('not.exist');

View File

@@ -1,10 +1,14 @@
Cypress.Commands.add('og', { prevSubject: 'optional' }, (subject, selector, suffix = '') => { Cypress.Commands.add(
'og',
{ prevSubject: 'optional' },
(subject, selector, suffix = '') => {
if (subject) { if (subject) {
return cy.wrap(subject).get(`[data-test="${selector}"]${suffix}`); return cy.wrap(subject).get(`[data-test="${selector}"]${suffix}`);
} }
return cy.get(`[data-test="${selector}"]${suffix}`); return cy.get(`[data-test="${selector}"]${suffix}`);
}); }
);
Cypress.Commands.add('login', () => { Cypress.Commands.add('login', () => {
cy.visit('/login'); cy.visit('/login');
@@ -12,12 +16,10 @@ Cypress.Commands.add('login', () => {
cy.og('email-text-field').type(Cypress.env('login_email')); cy.og('email-text-field').type(Cypress.env('login_email'));
cy.og('password-text-field').type(Cypress.env('login_password')); cy.og('password-text-field').type(Cypress.env('login_password'));
cy cy.intercept('/graphql').as('graphqlCalls');
.intercept('/graphql') cy.intercept('https://notifications.automatisch.io/notifications.json').as(
.as('graphqlCalls'); 'notificationsCall'
cy );
.intercept('https://notifications.automatisch.io/notifications.json')
.as('notificationsCall');
cy.og('login-button').click(); cy.og('login-button').click();
cy.wait(['@graphqlCalls', '@notificationsCall']); cy.wait(['@graphqlCalls', '@notificationsCall']);
@@ -30,14 +32,11 @@ Cypress.Commands.add('logout', () => {
}); });
Cypress.Commands.add('ss', (name, opts = {}) => { Cypress.Commands.add('ss', (name, opts = {}) => {
return cy.screenshot( return cy.screenshot(name, {
name,
{
overwrite: true, overwrite: true,
capture: 'viewport', capture: 'viewport',
...opts, ...opts,
} });
);
}); });
Cypress.Commands.add('clickOutside', () => { Cypress.Commands.add('clickOutside', () => {

View File

@@ -14,7 +14,7 @@
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands' import './commands';
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')

View File

@@ -25,9 +25,12 @@
--> -->
<title>Automatisch</title> <title>Automatisch</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap"
rel="stylesheet"
/>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -12,21 +12,18 @@ import useFormatMessage from 'hooks/useFormatMessage';
type AccountDropdownMenuProps = { type AccountDropdownMenuProps = {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
anchorEl: MenuProps["anchorEl"]; anchorEl: MenuProps['anchorEl'];
id: string; id: string;
} };
function AccountDropdownMenu(props: AccountDropdownMenuProps): React.ReactElement { function AccountDropdownMenu(
props: AccountDropdownMenuProps
): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const authentication = useAuthentication(); const authentication = useAuthentication();
const navigate = useNavigate(); const navigate = useNavigate();
const { const { open, onClose, anchorEl, id } = props;
open,
onClose,
anchorEl,
id
} = props
const logout = async () => { const logout = async () => {
authentication.updateToken(''); authentication.updateToken('');
@@ -53,17 +50,11 @@ function AccountDropdownMenu(props: AccountDropdownMenuProps): React.ReactElemen
open={open} open={open}
onClose={onClose} onClose={onClose}
> >
<MenuItem <MenuItem component={Link} to={URLS.SETTINGS_DASHBOARD}>
component={Link}
to={URLS.SETTINGS_DASHBOARD}
>
{formatMessage('accountDropdownMenu.settings')} {formatMessage('accountDropdownMenu.settings')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem onClick={logout} data-test="logout-item">
onClick={logout}
data-test="logout-item"
>
{formatMessage('accountDropdownMenu.logout')} {formatMessage('accountDropdownMenu.logout')}
</MenuItem> </MenuItem>
</Menu> </Menu>

View File

@@ -14,8 +14,11 @@ import InputCreator from 'components/InputCreator';
import type { IApp, IField } from '@automatisch/types'; import type { IApp, IField } from '@automatisch/types';
import { Form } from './style'; import { Form } from './style';
const generateDocsLink = (link: string) => (str: string) => ( const generateDocsLink = (link: string) => (str: string) =>
<a href={link} target="_blank">{str}</a> (
<a href={link} target="_blank">
{str}
</a>
); );
type AddAppConnectionProps = { type AddAppConnectionProps = {
@@ -26,25 +29,33 @@ type AddAppConnectionProps = {
type Response = { type Response = {
[key: string]: any; [key: string]: any;
} };
export default function AddAppConnection(props: AddAppConnectionProps): React.ReactElement { export default function AddAppConnection(
props: AddAppConnectionProps
): React.ReactElement {
const { application, connectionId, onClose } = props; const { application, connectionId, onClose } = props;
const { name, authDocUrl, key, auth } = application; const { name, authDocUrl, key, auth } = application;
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const [errorMessage, setErrorMessage] = React.useState<string | null>(null); const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
const [inProgress, setInProgress] = React.useState(false); const [inProgress, setInProgress] = React.useState(false);
const hasConnection = Boolean(connectionId); const hasConnection = Boolean(connectionId);
const steps = hasConnection ? auth?.reconnectionSteps : auth?.authenticationSteps; const steps = hasConnection
? auth?.reconnectionSteps
: auth?.authenticationSteps;
React.useEffect(() => { React.useEffect(() => {
if (window.opener) { if (window.opener) {
window.opener.postMessage({ source: 'automatisch', payload: window.location.search }); window.opener.postMessage({
source: 'automatisch',
payload: window.location.search,
});
window.close(); window.close();
} }
}, []); }, []);
const submitHandler: SubmitHandler<FieldValues> = React.useCallback(async (data) => { const submitHandler: SubmitHandler<FieldValues> = React.useCallback(
async (data) => {
if (!steps) return; if (!steps) return;
setInProgress(true); setInProgress(true);
@@ -53,7 +64,7 @@ export default function AddAppConnection(props: AddAppConnectionProps): React.Re
const response: Response = { const response: Response = {
key, key,
connection: { connection: {
id: connectionId id: connectionId,
}, },
fields: data, fields: data,
}; };
@@ -84,21 +95,24 @@ export default function AddAppConnection(props: AddAppConnectionProps): React.Re
} }
setInProgress(false); setInProgress(false);
}, [connectionId, key, steps, onClose]); },
[connectionId, key, steps, onClose]
);
return ( return (
<Dialog open={true} onClose={onClose} data-test="add-app-connection-dialog"> <Dialog open={true} onClose={onClose} data-test="add-app-connection-dialog">
<DialogTitle>{hasConnection ? formatMessage('app.reconnectConnection') : formatMessage('app.addConnection')}</DialogTitle> <DialogTitle>
{hasConnection
? formatMessage('app.reconnectConnection')
: formatMessage('app.addConnection')}
</DialogTitle>
{authDocUrl && ( {authDocUrl && (
<Alert severity="info" sx={{ fontWeight: 300 }}> <Alert severity="info" sx={{ fontWeight: 300 }}>
{formatMessage( {formatMessage('addAppConnection.callToDocs', {
'addAppConnection.callToDocs',
{
appName: name, appName: name,
docsLink: generateDocsLink(authDocUrl) docsLink: generateDocsLink(authDocUrl),
} })}
)}
</Alert> </Alert>
)} )}
@@ -111,7 +125,9 @@ 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}>
{auth?.fields?.map((field: IField) => (<InputCreator key={field.key} schema={field} />))} {auth?.fields?.map((field: IField) => (
<InputCreator key={field.key} schema={field} />
))}
<LoadingButton <LoadingButton
type="submit" type="submit"
@@ -128,4 +144,4 @@ export default function AddAppConnection(props: AddAppConnectionProps): React.Re
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
}; }

View File

@@ -13,9 +13,12 @@ const ApolloProvider = (props: ApolloProviderProps): React.ReactElement => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const authentication = useAuthentication(); const authentication = useAuthentication();
const onError = React.useCallback((message) => { const onError = React.useCallback(
(message) => {
enqueueSnackbar(message, { variant: 'error' }); enqueueSnackbar(message, { variant: 'error' });
}, [enqueueSnackbar]); },
[enqueueSnackbar]
);
const client = React.useMemo(() => { const client = React.useMemo(() => {
return mutateAndGetClient({ return mutateAndGetClient({
@@ -24,9 +27,7 @@ const ApolloProvider = (props: ApolloProviderProps): React.ReactElement => {
}); });
}, [onError, authentication]); }, [onError, authentication]);
return ( return <BaseApolloProvider client={client} {...props} />;
<BaseApolloProvider client={client} {...props} />
);
}; };
export default ApolloProvider; export default ApolloProvider;

View File

@@ -20,23 +20,21 @@ type AppBarProps = {
drawerOpen: boolean; drawerOpen: boolean;
onDrawerOpen: () => void; onDrawerOpen: () => void;
onDrawerClose: () => void; onDrawerClose: () => void;
maxWidth?: ContainerProps["maxWidth"]; maxWidth?: ContainerProps['maxWidth'];
}; };
const accountMenuId = 'account-menu'; const accountMenuId = 'account-menu';
export default function AppBar(props: AppBarProps): React.ReactElement { export default function AppBar(props: AppBarProps): React.ReactElement {
const { const { drawerOpen, onDrawerOpen, onDrawerClose, maxWidth = false } = props;
drawerOpen,
onDrawerOpen,
onDrawerClose,
maxWidth = false,
} = props;
const theme = useTheme(); const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { noSsr: true }); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), {
noSsr: true,
});
const [accountMenuAnchorElement, setAccountMenuAnchorElement] = React.useState<null | HTMLElement>(null); const [accountMenuAnchorElement, setAccountMenuAnchorElement] =
React.useState<null | HTMLElement>(null);
const isMenuOpen = Boolean(accountMenuAnchorElement); const isMenuOpen = Boolean(accountMenuAnchorElement);
@@ -65,11 +63,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
<div style={{ flexGrow: 1 }}> <div style={{ flexGrow: 1 }}>
<Link to={URLS.DASHBOARD}> <Link to={URLS.DASHBOARD}>
<Typography <Typography variant="h6" component="h1" noWrap>
variant="h6"
component="h1"
noWrap
>
<FormattedMessage id="brandText" /> <FormattedMessage id="brandText" />
</Typography> </Typography>
</Link> </Link>

View File

@@ -19,17 +19,22 @@ type ContextMenuProps = {
anchorEl: PopoverProps['anchorEl']; anchorEl: PopoverProps['anchorEl'];
}; };
export default function ContextMenu(props: ContextMenuProps): React.ReactElement { export default function ContextMenu(
props: ContextMenuProps
): React.ReactElement {
const { appKey, connectionId, onClose, onMenuItemClick, anchorEl } = props; const { appKey, connectionId, onClose, onMenuItemClick, anchorEl } = props;
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const createActionHandler = React.useCallback((action: Action) => { const createActionHandler = React.useCallback(
(action: Action) => {
return function clickHandler(event: React.MouseEvent) { return function clickHandler(event: React.MouseEvent) {
onMenuItemClick(event, action); onMenuItemClick(event, action);
onClose(); onClose();
}; };
}, [onMenuItemClick, onClose]); },
[onMenuItemClick, onClose]
);
return ( return (
<Menu <Menu
@@ -63,4 +68,4 @@ export default function ContextMenu(props: ContextMenuProps): React.ReactElement
</MenuItem> </MenuItem>
</Menu> </Menu>
); );
}; }

View File

@@ -20,13 +20,11 @@ import { CardContent, Typography } from './style';
type AppConnectionRowProps = { type AppConnectionRowProps = {
connection: IConnection; connection: IConnection;
} };
const countTranslation = (value: React.ReactNode) => ( const countTranslation = (value: React.ReactNode) => (
<> <>
<Typography variant="body1"> <Typography variant="body1">{value}</Typography>
{value}
</Typography>
<br /> <br />
</> </>
); );
@@ -34,15 +32,21 @@ const countTranslation = (value: React.ReactNode) => (
function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement { function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [verificationVisible, setVerificationVisible] = React.useState(false); const [verificationVisible, setVerificationVisible] = React.useState(false);
const [testConnection, { called: testCalled, loading: testLoading }] = useLazyQuery(TEST_CONNECTION, { const [testConnection, { called: testCalled, loading: testLoading }] =
useLazyQuery(TEST_CONNECTION, {
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
onCompleted: () => { setTimeout(() => setVerificationVisible(false), 3000); }, onCompleted: () => {
onError: () => { setTimeout(() => setVerificationVisible(false), 3000); }, setTimeout(() => setVerificationVisible(false), 3000);
},
onError: () => {
setTimeout(() => setVerificationVisible(false), 3000);
},
}); });
const [deleteConnection] = useMutation(DELETE_CONNECTION); const [deleteConnection] = useMutation(DELETE_CONNECTION);
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { id, key, formattedData, verified, createdAt, flowCount } = props.connection; const { id, key, formattedData, verified, createdAt, flowCount } =
props.connection;
const contextButtonRef = React.useRef<SVGSVGElement | null>(null); const contextButtonRef = React.useRef<SVGSVGElement | null>(null);
const [anchorEl, setAnchorEl] = React.useState<SVGSVGElement | null>(null); const [anchorEl, setAnchorEl] = React.useState<SVGSVGElement | null>(null);
@@ -52,7 +56,8 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
}; };
const onContextMenuClick = () => setAnchorEl(contextButtonRef.current); const onContextMenuClick = () => setAnchorEl(contextButtonRef.current);
const onContextMenuAction = React.useCallback(async (event, action: { [key: string]: string }) => { const onContextMenuAction = React.useCallback(
async (event, action: { [key: string]: string }) => {
if (action.type === 'delete') { if (action.type === 'delete') {
await deleteConnection({ await deleteConnection({
variables: { input: { id } }, variables: { input: { id } },
@@ -65,34 +70,38 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
cache.evict({ cache.evict({
id: connectionCacheId, id: connectionCacheId,
}); });
} },
}); });
enqueueSnackbar(formatMessage('connection.deletedMessage'), { variant: 'success' }); enqueueSnackbar(formatMessage('connection.deletedMessage'), {
variant: 'success',
});
} else if (action.type === 'test') { } else if (action.type === 'test') {
setVerificationVisible(true); setVerificationVisible(true);
testConnection({ variables: { id } }); testConnection({ variables: { id } });
} }
}, [deleteConnection, id, testConnection, formatMessage, enqueueSnackbar]); },
[deleteConnection, id, testConnection, formatMessage, enqueueSnackbar]
);
const relativeCreatedAt = DateTime.fromMillis(parseInt(createdAt, 10)).toRelative(); const relativeCreatedAt = DateTime.fromMillis(
parseInt(createdAt, 10)
).toRelative();
return ( return (
<> <>
<Card sx={{ my: 2 }} data-test="app-connection-row"> <Card sx={{ my: 2 }} data-test="app-connection-row">
<CardActionArea onClick={onContextMenuClick}> <CardActionArea onClick={onContextMenuClick}>
<CardContent> <CardContent>
<Stack <Stack justifyContent="center" alignItems="flex-start" spacing={1}>
justifyContent="center"
alignItems="flex-start"
spacing={1}
>
<Typography variant="h6" sx={{ textAlign: 'left' }}> <Typography variant="h6" sx={{ textAlign: 'left' }}>
{formattedData?.screenName} {formattedData?.screenName}
</Typography> </Typography>
<Typography variant="caption"> <Typography variant="caption">
{formatMessage('connection.addedAt', { datetime: relativeCreatedAt })} {formatMessage('connection.addedAt', {
datetime: relativeCreatedAt,
})}
</Typography> </Typography>
</Stack> </Stack>
@@ -101,27 +110,42 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
{verificationVisible && testCalled && testLoading && ( {verificationVisible && testCalled && testLoading && (
<> <>
<CircularProgress size={16} /> <CircularProgress size={16} />
<Typography variant="caption">{formatMessage('connection.testing')}</Typography> <Typography variant="caption">
{formatMessage('connection.testing')}
</Typography>
</> </>
)} )}
{verificationVisible && testCalled && !testLoading && verified && ( {verificationVisible && testCalled && !testLoading && verified && (
<> <>
<CheckCircleIcon fontSize="small" color="success" /> <CheckCircleIcon fontSize="small" color="success" />
<Typography variant="caption">{formatMessage('connection.testSuccessful')}</Typography> <Typography variant="caption">
{formatMessage('connection.testSuccessful')}
</Typography>
</> </>
)} )}
{verificationVisible && testCalled && !testLoading && !verified && ( {verificationVisible &&
testCalled &&
!testLoading &&
!verified && (
<> <>
<ErrorIcon fontSize="small" color="error" /> <ErrorIcon fontSize="small" color="error" />
<Typography variant="caption">{formatMessage('connection.testFailed')}</Typography> <Typography variant="caption">
{formatMessage('connection.testFailed')}
</Typography>
</> </>
)} )}
</Stack> </Stack>
</Box> </Box>
<Box sx={{ px: 2 }}> <Box sx={{ px: 2 }}>
<Typography variant="caption" color="textSecondary" sx={{ display: ['none', 'inline-block'] }}> <Typography
{formatMessage('connection.flowCount', { count: countTranslation(flowCount) })} variant="caption"
color="textSecondary"
sx={{ display: ['none', 'inline-block'] }}
>
{formatMessage('connection.flowCount', {
count: countTranslation(flowCount),
})}
</Typography> </Typography>
</Box> </Box>
@@ -132,13 +156,15 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
</CardActionArea> </CardActionArea>
</Card> </Card>
{anchorEl && <ConnectionContextMenu {anchorEl && (
<ConnectionContextMenu
appKey={key} appKey={key}
connectionId={id} connectionId={id}
onClose={handleClose} onClose={handleClose}
onMenuItemClick={onContextMenuAction} onMenuItemClick={onContextMenuAction}
anchorEl={anchorEl} anchorEl={anchorEl}
/>} />
)}
</> </>
); );
} }

View File

@@ -10,7 +10,6 @@ export const CardContent = styled(MuiCardContent)(({ theme }) => ({
alignItems: 'center', alignItems: 'center',
})); }));
export const Typography = styled(MuiTypography)(() => ({ export const Typography = styled(MuiTypography)(() => ({
textAlign: 'center', textAlign: 'center',
display: 'inline-block', display: 'inline-block',

View File

@@ -10,12 +10,16 @@ import * as URLS from 'config/urls';
type AppConnectionsProps = { type AppConnectionsProps = {
appKey: string; appKey: string;
} };
export default function AppConnections(props: AppConnectionsProps): React.ReactElement { export default function AppConnections(
props: AppConnectionsProps
): React.ReactElement {
const { appKey } = props; const { appKey } = props;
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { data } = useQuery(GET_APP_CONNECTIONS, { variables: { key: appKey } }); const { data } = useQuery(GET_APP_CONNECTIONS, {
variables: { key: appKey },
});
const appConnections: IConnection[] = data?.getApp?.connections || []; const appConnections: IConnection[] = data?.getApp?.connections || [];
const hasConnections = appConnections?.length; const hasConnections = appConnections?.length;
@@ -35,5 +39,5 @@ export default function AppConnections(props: AppConnectionsProps): React.ReactE
<AppConnectionRow key={appConnection.id} connection={appConnection} /> <AppConnectionRow key={appConnection.id} connection={appConnection} />
))} ))}
</> </>
) );
}; }

View File

@@ -12,7 +12,7 @@ import type { IFlow } from '@automatisch/types';
type AppFlowsProps = { type AppFlowsProps = {
appKey: string; appKey: string;
} };
const FLOW_PER_PAGE = 10; const FLOW_PER_PAGE = 10;
@@ -27,11 +27,13 @@ export default function AppFlows(props: AppFlowsProps): React.ReactElement {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const connectionId = searchParams.get('connectionId') || undefined; const connectionId = searchParams.get('connectionId') || undefined;
const page = parseInt(searchParams.get('page') || '', 10) || 1; const page = parseInt(searchParams.get('page') || '', 10) || 1;
const { data } = useQuery(GET_FLOWS, { variables: { const { data } = useQuery(GET_FLOWS, {
variables: {
appKey, appKey,
connectionId, connectionId,
...getLimitAndOffset(page) ...getLimitAndOffset(page),
}}); },
});
const getFlows = data?.getFlows || {}; const getFlows = data?.getFlows || {};
const { pageInfo, edges } = getFlows; const { pageInfo, edges } = getFlows;
@@ -53,7 +55,8 @@ export default function AppFlows(props: AppFlowsProps): React.ReactElement {
<AppFlowRow key={appFlow.id} flow={appFlow} /> <AppFlowRow key={appFlow.id} flow={appFlow} />
))} ))}
{pageInfo && pageInfo.totalPages > 1 && <Pagination {pageInfo && pageInfo.totalPages > 1 && (
<Pagination
sx={{ display: 'flex', justifyContent: 'center', mt: 3 }} sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}
page={pageInfo?.currentPage} page={pageInfo?.currentPage}
count={pageInfo?.totalPages} count={pageInfo?.totalPages}
@@ -65,7 +68,8 @@ export default function AppFlows(props: AppFlowsProps): React.ReactElement {
{...item} {...item}
/> />
)} )}
/>} />
)}
</> </>
) );
}; }

View File

@@ -13,15 +13,10 @@ const inlineImgStyle: React.CSSProperties = {
objectFit: 'contain', objectFit: 'contain',
}; };
export default function AppIcon(props: AppIconProps & AvatarProps): React.ReactElement { export default function AppIcon(
const { props: AppIconProps & AvatarProps
name, ): React.ReactElement {
url, const { name, url, color, sx = {}, variant = 'square', ...restProps } = props;
color,
sx = {},
variant = "square",
...restProps
} = props;
const initialLetter = name?.[0]; const initialLetter = name?.[0];
@@ -37,4 +32,4 @@ export default function AppIcon(props: AppIconProps & AvatarProps): React.ReactE
{...restProps} {...restProps}
/> />
); );
}; }

View File

@@ -14,20 +14,19 @@ import { CardContent, Typography } from './style';
type AppRowProps = { type AppRowProps = {
application: IApp; application: IApp;
} };
const countTranslation = (value: React.ReactNode) => ( const countTranslation = (value: React.ReactNode) => (
<> <>
<Typography variant="body1"> <Typography variant="body1">{value}</Typography>
{value}
</Typography>
<br /> <br />
</> </>
); );
function AppRow(props: AppRowProps): React.ReactElement { function AppRow(props: AppRowProps): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { name, primaryColor, iconUrl, connectionCount, flowCount } = props.application; const { name, primaryColor, iconUrl, connectionCount, flowCount } =
props.application;
return ( return (
<Link to={URLS.APP(name.toLowerCase())} data-test="app-row"> <Link to={URLS.APP(name.toLowerCase())} data-test="app-row">
@@ -39,25 +38,37 @@ function AppRow(props: AppRowProps): React.ReactElement {
</Box> </Box>
<Box> <Box>
<Typography variant="h6"> <Typography variant="h6">{name}</Typography>
{name} </Box>
<Box sx={{ px: 2 }}>
<Typography
variant="caption"
color="textSecondary"
sx={{ display: ['none', 'inline-block'] }}
>
{formatMessage('app.connectionCount', {
count: countTranslation(connectionCount),
})}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ px: 2 }}> <Box sx={{ px: 2 }}>
<Typography variant="caption" color="textSecondary" sx={{ display: ['none', 'inline-block'] }}> <Typography
{formatMessage('app.connectionCount', { count: countTranslation(connectionCount) })} variant="caption"
</Typography> color="textSecondary"
</Box> sx={{ display: ['none', 'inline-block'] }}
>
<Box sx={{ px: 2 }}> {formatMessage('app.flowCount', {
<Typography variant="caption" color="textSecondary" sx={{ display: ['none', 'inline-block'] }}> count: countTranslation(flowCount),
{formatMessage('app.flowCount', { count: countTranslation(flowCount) })} })}
</Typography> </Typography>
</Box> </Box>
<Box> <Box>
<ArrowForwardIosIcon sx={{ color: (theme) => theme.palette.primary.main }} /> <ArrowForwardIosIcon
sx={{ color: (theme) => theme.palette.primary.main }}
/>
</Box> </Box>
</CardContent> </CardContent>
</CardActionArea> </CardActionArea>

View File

@@ -10,7 +10,6 @@ export const CardContent = styled(MuiCardContent)(({ theme }) => ({
alignItems: 'center', alignItems: 'center',
})); }));
export const Typography = styled(MuiTypography)(() => ({ export const Typography = styled(MuiTypography)(() => ({
'&.MuiTypography-h6': { '&.MuiTypography-h6': {
textTransform: 'capitalize', textTransform: 'capitalize',
@@ -22,5 +21,5 @@ export const Typography = styled(MuiTypography)(() => ({
export const DesktopOnlyBreakline = styled('br')(({ theme }) => ({ export const DesktopOnlyBreakline = styled('br')(({ theme }) => ({
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
display: 'none', display: 'none',
} },
})); }));

View File

@@ -12,7 +12,13 @@ import useFormatMessage from 'hooks/useFormatMessage';
import { EditorContext } from 'contexts/Editor'; import { EditorContext } from 'contexts/Editor';
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 { IApp, IStep, ISubstep, ITrigger, IAction } from '@automatisch/types'; import type {
IApp,
IStep,
ISubstep,
ITrigger,
IAction,
} from '@automatisch/types';
type ChooseAppAndEventSubstepProps = { type ChooseAppAndEventSubstepProps = {
substep: ISubstep; substep: ISubstep;
@@ -24,7 +30,10 @@ type ChooseAppAndEventSubstepProps = {
step: IStep; step: IStep;
}; };
const optionGenerator = (app: { name: string, key: string, }): { label: string; value: string } => ({ const optionGenerator = (app: {
name: string;
key: string;
}): { label: string; value: string } => ({
label: app.name as string, label: app.name as string,
value: app.key as string, value: app.key as string,
}); });
@@ -61,13 +70,13 @@ function ChooseAppAndEventSubstep(
() => apps?.map((app) => optionGenerator(app)), () => apps?.map((app) => optionGenerator(app)),
[apps] [apps]
); );
const actionsOrTriggers: Array<ITrigger | IAction> = (isTrigger ? app?.triggers : app?.actions) || []; const actionsOrTriggers: Array<ITrigger | IAction> =
(isTrigger ? app?.triggers : app?.actions) || [];
const actionOptions = React.useMemo( const actionOptions = React.useMemo(
() => actionsOrTriggers.map((trigger) => optionGenerator(trigger)), () => actionsOrTriggers.map((trigger) => optionGenerator(trigger)),
[app?.key] [app?.key]
); );
const selectedActionOrTrigger = const selectedActionOrTrigger = actionsOrTriggers.find(
actionsOrTriggers.find(
(actionOrTrigger: IAction | ITrigger) => actionOrTrigger.key === step?.key (actionOrTrigger: IAction | ITrigger) => actionOrTrigger.key === step?.key
); );

View File

@@ -16,7 +16,7 @@ import { TEST_CONNECTION } from 'graphql/queries/test-connection';
type ChooseConnectionSubstepProps = { type ChooseConnectionSubstepProps = {
application: IApp; application: IApp;
substep: ISubstep, substep: ISubstep;
expanded?: boolean; expanded?: boolean;
onExpand: () => void; onExpand: () => void;
onCollapse: () => void; onCollapse: () => void;
@@ -27,14 +27,19 @@ type ChooseConnectionSubstepProps = {
const ADD_CONNECTION_VALUE = 'ADD_CONNECTION'; const ADD_CONNECTION_VALUE = 'ADD_CONNECTION';
const optionGenerator = (connection: IConnection): { label: string; value: string; } => ({ const optionGenerator = (
label: connection?.formattedData?.screenName as string ?? 'Unnamed', connection: IConnection
): { label: string; value: string } => ({
label: (connection?.formattedData?.screenName as string) ?? 'Unnamed',
value: connection?.id as string, value: connection?.id as string,
}); });
const getOption = (options: Record<string, unknown>[], connectionId?: string) => options.find(connection => connection.value === connectionId) || null; const getOption = (options: Record<string, unknown>[], connectionId?: string) =>
options.find((connection) => connection.value === connectionId) || null;
function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.ReactElement { function ChooseConnectionSubstep(
props: ChooseConnectionSubstepProps
): React.ReactElement {
const { const {
substep, substep,
expanded = false, expanded = false,
@@ -45,36 +50,30 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
onChange, onChange,
application, application,
} = props; } = props;
const { const { connection, appKey } = step;
connection,
appKey,
} = step;
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const editorContext = React.useContext(EditorContext); const editorContext = React.useContext(EditorContext);
const [showAddConnectionDialog, setShowAddConnectionDialog] = React.useState(false); const [showAddConnectionDialog, setShowAddConnectionDialog] =
const { data, loading, refetch } = useQuery(GET_APP_CONNECTIONS, { variables: { key: appKey }}); React.useState(false);
const { data, loading, refetch } = useQuery(GET_APP_CONNECTIONS, {
variables: { key: appKey },
});
// TODO: show detailed error when connection test/verification fails // TODO: show detailed error when connection test/verification fails
const [ const [
testConnection, testConnection,
{ { loading: testResultLoading, refetch: retestConnection },
loading: testResultLoading, ] = useLazyQuery(TEST_CONNECTION, {
refetch: retestConnection
}
] = useLazyQuery(
TEST_CONNECTION,
{
variables: { variables: {
id: connection?.id, id: connection?.id,
} },
} });
);
React.useEffect(() => { React.useEffect(() => {
if (connection?.id) { if (connection?.id) {
testConnection({ testConnection({
variables: { variables: {
id: connection.id, id: connection.id,
} },
}); });
} }
// intentionally no dependencies for initial test // intentionally no dependencies for initial test
@@ -82,21 +81,23 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
const connectionOptions = React.useMemo(() => { const connectionOptions = React.useMemo(() => {
const appWithConnections = data?.getApp as IApp; const appWithConnections = data?.getApp as IApp;
const options = appWithConnections const options =
?.connections appWithConnections?.connections?.map((connection) =>
?.map((connection) => optionGenerator(connection)) || []; optionGenerator(connection)
) || [];
options.push({ options.push({
label: formatMessage('chooseConnectionSubstep.addNewConnection'), label: formatMessage('chooseConnectionSubstep.addNewConnection'),
value: ADD_CONNECTION_VALUE value: ADD_CONNECTION_VALUE,
}) });
return options; return options;
}, [data, formatMessage]); }, [data, formatMessage]);
const { name } = substep; const { name } = substep;
const handleAddConnectionClose = React.useCallback(async (response) => { const handleAddConnectionClose = React.useCallback(
async (response) => {
setShowAddConnectionDialog(false); setShowAddConnectionDialog(false);
const connectionId = response?.createConnection.id; const connectionId = response?.createConnection.id;
@@ -113,9 +114,12 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
}, },
}); });
} }
}, [onChange, refetch, step]); },
[onChange, refetch, step]
);
const handleChange = React.useCallback((event: React.SyntheticEvent, selectedOption: unknown) => { const handleChange = React.useCallback(
(event: React.SyntheticEvent, selectedOption: unknown) => {
if (typeof selectedOption === 'object') { if (typeof selectedOption === 'object') {
// TODO: try to simplify type casting below. // TODO: try to simplify type casting below.
const typedSelectedOption = selectedOption as { value: string }; const typedSelectedOption = selectedOption as { value: string };
@@ -138,7 +142,9 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
}); });
} }
} }
}, [step, onChange]); },
[step, onChange]
);
React.useEffect(() => { React.useEffect(() => {
if (step.connection?.id) { if (step.connection?.id) {
@@ -146,7 +152,7 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
id: step.connection.id, id: step.connection.id,
}); });
} }
}, [step.connection?.id, retestConnection]) }, [step.connection?.id, retestConnection]);
const onToggle = expanded ? onCollapse : onExpand; const onToggle = expanded ? onCollapse : onExpand;
@@ -159,7 +165,14 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
valid={testResultLoading ? null : connection?.verified} valid={testResultLoading ? null : connection?.verified}
/> />
<Collapse in={expanded} timeout="auto" unmountOnExit> <Collapse in={expanded} timeout="auto" unmountOnExit>
<ListItem sx={{ pt: 2, pb: 3, flexDirection: 'column', alignItems: 'flex-start' }}> <ListItem
sx={{
pt: 2,
pb: 3,
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Autocomplete <Autocomplete
fullWidth fullWidth
disablePortal disablePortal
@@ -169,7 +182,9 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
{...params} {...params}
label={formatMessage('chooseConnectionSubstep.chooseConnection')} label={formatMessage(
'chooseConnectionSubstep.chooseConnection'
)}
/> />
)} )}
value={getOption(connectionOptions, connection?.id)} value={getOption(connectionOptions, connection?.id)}
@@ -183,17 +198,24 @@ function ChooseConnectionSubstep(props: ChooseConnectionSubstepProps): React.Rea
variant="contained" variant="contained"
onClick={onSubmit} onClick={onSubmit}
sx={{ mt: 2 }} sx={{ mt: 2 }}
disabled={testResultLoading || !connection?.verified || editorContext.readOnly}data-test="flow-substep-continue-button" disabled={
testResultLoading ||
!connection?.verified ||
editorContext.readOnly
}
data-test="flow-substep-continue-button"
> >
{formatMessage('chooseConnectionSubstep.continue')} {formatMessage('chooseConnectionSubstep.continue')}
</Button> </Button>
</ListItem> </ListItem>
</Collapse> </Collapse>
{application && showAddConnectionDialog && <AddAppConnection {application && showAddConnectionDialog && (
<AddAppConnection
onClose={handleAddConnectionClose} onClose={handleAddConnectionClose}
application={application} application={application}
/>} />
)}
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -10,7 +10,9 @@ import { IconButton } from './style';
export default function ConditionalIconButton(props: any): React.ReactElement { export default function ConditionalIconButton(props: any): React.ReactElement {
const { icon, ...buttonProps } = props; const { icon, ...buttonProps } = props;
const theme = useTheme(); const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { noSsr: true }); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), {
noSsr: true,
});
if (matchSmallScreens) { if (matchSmallScreens) {
return ( return (
@@ -22,10 +24,8 @@ export default function ConditionalIconButton(props: any): React.ReactElement {
> >
{icon} {icon}
</IconButton> </IconButton>
)
}
return (
<Button {...(buttonProps as ButtonProps)} />
); );
} }
return <Button {...(buttonProps as ButtonProps)} />;
}

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