Compare commits
1 Commits
AUT-501
...
deployment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9eeb519d87 |
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -83,3 +83,20 @@ jobs:
|
||||
env:
|
||||
CI: false
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
build-cli:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
|
||||
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||
- run: yarn --frozen-lockfile && yarn lerna bootstrap
|
||||
- run: cd packages/cli && yarn build
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
|
@@ -6,7 +6,8 @@
|
||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||
"start:web": "lerna run --stream --scope=@*/web dev",
|
||||
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
||||
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint",
|
||||
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} lint",
|
||||
"build:watch": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} build:watch",
|
||||
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
||||
},
|
||||
"workspaces": {
|
||||
@@ -17,6 +18,7 @@
|
||||
"**/babel-loader",
|
||||
"**/webpack",
|
||||
"**/@automatisch/web",
|
||||
"**/@automatisch/types",
|
||||
"**/ajv"
|
||||
]
|
||||
},
|
||||
|
@@ -1,3 +1,3 @@
|
||||
import { createUser } from './utils.js';
|
||||
|
||||
createUser();
|
||||
await createUser();
|
||||
|
@@ -1,43 +0,0 @@
|
||||
import defineAction from '../../../../helpers/define-action.js';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Acknowledge incident',
|
||||
key: 'acknowledgeIncident',
|
||||
description: 'Acknowledges an incident.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Incident ID',
|
||||
key: 'incidentId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'This serves as the incident ID that requires your acknowledgment.',
|
||||
},
|
||||
{
|
||||
label: 'Acknowledged by',
|
||||
key: 'acknowledgedBy',
|
||||
type: 'string',
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
"This refers to the individual's name, email, or another form of identification that the person who acknowledged the incident has provided.",
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const acknowledgedBy = $.step.parameters.acknowledgedBy;
|
||||
const incidentId = $.step.parameters.incidentId;
|
||||
|
||||
const body = {
|
||||
acknowledged_by: acknowledgedBy,
|
||||
};
|
||||
|
||||
const response = await $.http.post(
|
||||
`/v2/incidents/${incidentId}/acknowledge`,
|
||||
body
|
||||
);
|
||||
|
||||
$.setActionItem({ raw: response.data.data });
|
||||
},
|
||||
});
|
@@ -1,120 +0,0 @@
|
||||
import defineAction from '../../../../helpers/define-action.js';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create incident',
|
||||
key: 'createIncident',
|
||||
description: 'Creates an incident that informs the team.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Brief Summary',
|
||||
key: 'briefSummary',
|
||||
type: 'string',
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'A short description outlining the issue.',
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
key: 'description',
|
||||
type: 'string',
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'An elaborate description of the situation, offering insights into what is occurring, along with instructions to reproduce the problem.',
|
||||
},
|
||||
{
|
||||
label: 'Requester Email',
|
||||
key: 'requesterEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'This represents the email address of the individual who initiated the incident request.',
|
||||
},
|
||||
{
|
||||
label: 'Alert Settings - Call',
|
||||
key: 'alertSettingsCall',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
description: 'Should we call the on-call person?',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Yes', value: 'true' },
|
||||
{ label: 'No', value: 'false' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Alert Settings - Text',
|
||||
key: 'alertSettingsText',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
description: 'Should we text the on-call person?',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Yes', value: 'true' },
|
||||
{ label: 'No', value: 'false' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Alert Settings - Email',
|
||||
key: 'alertSettingsEmail',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
description: 'Should we email the on-call person?',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Yes', value: 'true' },
|
||||
{ label: 'No', value: 'false' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Alert Settings - Push Notification',
|
||||
key: 'alertSettingsPushNotification',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
description: 'Should we send a push notification to the on-call person?',
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Yes', value: 'true' },
|
||||
{ label: 'No', value: 'false' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Team Alert Wait Time',
|
||||
key: 'teamAlertWaitTime',
|
||||
type: 'string',
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
"What is the time threshold for acknowledgment before escalating to the entire team? (Specify in seconds) - Use a negative value to indicate no team alert if the on-call person doesn't respond, and use 0 for an immediate alert to the entire team.",
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const {
|
||||
briefSummary,
|
||||
description,
|
||||
requesterEmail,
|
||||
alertSettingsCall,
|
||||
alertSettingsText,
|
||||
alertSettingsEmail,
|
||||
alertSettingsPushNotification,
|
||||
teamAlertWaitTime,
|
||||
} = $.step.parameters;
|
||||
|
||||
const body = {
|
||||
summary: briefSummary,
|
||||
description,
|
||||
requester_email: requesterEmail,
|
||||
call: alertSettingsCall,
|
||||
sms: alertSettingsText,
|
||||
email: alertSettingsEmail,
|
||||
push: alertSettingsPushNotification,
|
||||
team_wait: teamAlertWaitTime,
|
||||
};
|
||||
|
||||
const response = await $.http.post('/v2/incidents', body);
|
||||
|
||||
$.setActionItem({ raw: response.data.data });
|
||||
},
|
||||
});
|
@@ -1,4 +0,0 @@
|
||||
import acknowledgeIncident from './acknowledge-incident/index.js';
|
||||
import createIncident from './create-incident/index.js';
|
||||
|
||||
export default [acknowledgeIncident, createIncident];
|
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000" stroke="none">
|
||||
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
|
||||
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
|
||||
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
|
||||
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
|
||||
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
|
||||
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
|
||||
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
|
||||
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
|
||||
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
|
||||
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,33 +0,0 @@
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
import isStillVerified from './is-still-verified.js';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'screenName',
|
||||
label: 'Screen Name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description:
|
||||
'Screen name of your connection to be used on Automatisch UI.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string',
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'Better Stack API key of your account.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -1,8 +0,0 @@
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
|
||||
const isStillVerified = async ($) => {
|
||||
await verifyCredentials($);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -1,10 +0,0 @@
|
||||
const verifyCredentials = async ($) => {
|
||||
await $.http.get('/v2/metadata');
|
||||
|
||||
await $.auth.set({
|
||||
screenName: $.auth.data.screenName,
|
||||
apiKey: $.auth.data.apiKey,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -1,9 +0,0 @@
|
||||
const addAuthHeader = ($, requestConfig) => {
|
||||
if ($.auth.data?.apiKey) {
|
||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -1,18 +0,0 @@
|
||||
import defineApp from '../../helpers/define-app.js';
|
||||
import addAuthHeader from './common/add-auth-header.js';
|
||||
import auth from './auth/index.js';
|
||||
import actions from './actions/index.js';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Better Stack',
|
||||
key: 'better-stack',
|
||||
iconUrl: '{BASE_URL}/apps/better-stack/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/better-stack/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://betterstack.com',
|
||||
apiBaseUrl: 'https://uptime.betterstack.com/api',
|
||||
primaryColor: '000000',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
@@ -6,6 +6,31 @@ import { createRole } from '../../../test/factories/role';
|
||||
import { createUser } from '../../../test/factories/user';
|
||||
|
||||
describe('graphQL getCurrentUser query', () => {
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const invalidUserToken = 'invalid-token';
|
||||
|
||||
const query = `
|
||||
query {
|
||||
getCurrentUser {
|
||||
id
|
||||
email
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', invalidUserToken)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
let role, currentUser, token, requestObject;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -76,4 +101,5 @@ describe('graphQL getCurrentUser query', () => {
|
||||
'Cannot query field "password" on type "User".'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -40,7 +40,23 @@ describe('graphQL getExecutions query', () => {
|
||||
}
|
||||
`;
|
||||
|
||||
describe('and without correct permissions', () => {
|
||||
const invalidToken = 'invalid-token';
|
||||
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', invalidToken)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
describe('and without permissions', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const userWithoutPermissions = await createUser();
|
||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||
@@ -469,4 +485,5 @@ describe('graphQL getExecutions query', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -40,6 +40,23 @@ describe('graphQL getFlow query', () => {
|
||||
`;
|
||||
};
|
||||
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const invalidToken = 'invalid-token';
|
||||
const flow = await createFlow();
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', invalidToken)
|
||||
.send({ query: query(flow.id) })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
describe('and without permissions', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const userWithoutPermissions = await createUser();
|
||||
@@ -128,7 +145,9 @@ describe('graphQL getFlow query', () => {
|
||||
{
|
||||
appKey: actionStep.appKey,
|
||||
connection: {
|
||||
createdAt: actionConnection.createdAt.getTime().toString(),
|
||||
createdAt: actionConnection.createdAt
|
||||
.getTime()
|
||||
.toString(),
|
||||
id: actionConnection.id,
|
||||
verified: actionConnection.verified,
|
||||
},
|
||||
@@ -215,7 +234,9 @@ describe('graphQL getFlow query', () => {
|
||||
{
|
||||
appKey: actionStep.appKey,
|
||||
connection: {
|
||||
createdAt: actionConnection.createdAt.getTime().toString(),
|
||||
createdAt: actionConnection.createdAt
|
||||
.getTime()
|
||||
.toString(),
|
||||
id: actionConnection.id,
|
||||
verified: actionConnection.verified,
|
||||
},
|
||||
@@ -237,4 +258,5 @@ describe('graphQL getFlow query', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -17,6 +17,7 @@ describe('graphQL getRole query', () => {
|
||||
userWithoutPermissions,
|
||||
tokenWithPermissions,
|
||||
tokenWithoutPermissions,
|
||||
invalidToken,
|
||||
permissionOne,
|
||||
permissionTwo;
|
||||
|
||||
@@ -73,8 +74,24 @@ describe('graphQL getRole query', () => {
|
||||
tokenWithoutPermissions = createAuthTokenByUserId(
|
||||
userWithoutPermissions.id
|
||||
);
|
||||
|
||||
invalidToken = 'invalid-token';
|
||||
});
|
||||
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', invalidToken)
|
||||
.send({ query: queryWithValidRole })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
describe('and with valid license', () => {
|
||||
beforeEach(async () => {
|
||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||
@@ -161,4 +178,5 @@ describe('graphQL getRole query', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -15,7 +15,8 @@ describe('graphQL getRoles query', () => {
|
||||
userWithPermissions,
|
||||
userWithoutPermissions,
|
||||
tokenWithPermissions,
|
||||
tokenWithoutPermissions;
|
||||
tokenWithoutPermissions,
|
||||
invalidToken;
|
||||
|
||||
beforeEach(async () => {
|
||||
currentUserRole = await createRole({ name: 'Current user role' });
|
||||
@@ -52,8 +53,24 @@ describe('graphQL getRoles query', () => {
|
||||
tokenWithoutPermissions = createAuthTokenByUserId(
|
||||
userWithoutPermissions.id
|
||||
);
|
||||
|
||||
invalidToken = 'invalid-token';
|
||||
});
|
||||
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', invalidToken)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
describe('and with valid license', () => {
|
||||
beforeEach(async () => {
|
||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||
@@ -131,4 +148,5 @@ describe('graphQL getRoles query', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -16,6 +16,22 @@ describe('graphQL getTrialStatus query', () => {
|
||||
}
|
||||
`;
|
||||
|
||||
const invalidToken = 'invalid-token';
|
||||
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', invalidToken)
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
let user, userToken;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -97,4 +113,5 @@ describe('graphQL getTrialStatus query', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -8,6 +8,31 @@ import { createPermission } from '../../../test/factories/permission';
|
||||
import { createUser } from '../../../test/factories/user';
|
||||
|
||||
describe('graphQL getUser query', () => {
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const invalidUserId = '123123123';
|
||||
|
||||
const query = `
|
||||
query {
|
||||
getUser(id: "${invalidUserId}") {
|
||||
id
|
||||
email
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', 'invalid-token')
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
describe('and without permissions', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const userWithoutPermissions = await createUser();
|
||||
@@ -59,7 +84,9 @@ describe('graphQL getUser query', () => {
|
||||
});
|
||||
|
||||
token = createAuthTokenByUserId(currentUser.id);
|
||||
requestObject = request(app).post('/graphql').set('Authorization', token);
|
||||
requestObject = request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token);
|
||||
});
|
||||
|
||||
it('should return user data for a valid user id', async () => {
|
||||
@@ -143,4 +170,5 @@ describe('graphQL getUser query', () => {
|
||||
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -30,6 +30,20 @@ describe('graphQL getUsers query', () => {
|
||||
}
|
||||
`;
|
||||
|
||||
describe('with unauthenticated user', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', 'invalid-token')
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.errors).toBeDefined();
|
||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with authenticated user', () => {
|
||||
describe('and without permissions', () => {
|
||||
it('should throw not authorized error', async () => {
|
||||
const userWithoutPermissions = await createUser();
|
||||
@@ -72,7 +86,9 @@ describe('graphQL getUsers query', () => {
|
||||
});
|
||||
|
||||
token = createAuthTokenByUserId(currentUser.id);
|
||||
requestObject = request(app).post('/graphql').set('Authorization', token);
|
||||
requestObject = request(app)
|
||||
.post('/graphql')
|
||||
.set('Authorization', token);
|
||||
});
|
||||
|
||||
it('should return users data', async () => {
|
||||
@@ -145,4 +161,5 @@ describe('graphQL getUsers query', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
|
||||
import appConfig from '../config/app.js';
|
||||
import User from '../models/user.js';
|
||||
|
||||
export const isAuthenticated = async (_parent, _args, req) => {
|
||||
const isAuthenticated = rule()(async (_parent, _args, req) => {
|
||||
const token = req.headers['authorization'];
|
||||
|
||||
if (token == null) return false;
|
||||
@@ -26,13 +26,12 @@ export const isAuthenticated = async (_parent, _args, req) => {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const isAuthenticatedRule = rule()(isAuthenticated);
|
||||
|
||||
export const authenticationRules = {
|
||||
const authentication = shield(
|
||||
{
|
||||
Query: {
|
||||
'*': isAuthenticatedRule,
|
||||
'*': isAuthenticated,
|
||||
getAutomatischInfo: allow,
|
||||
getConfig: allow,
|
||||
getNotifications: allow,
|
||||
@@ -40,18 +39,16 @@ export const authenticationRules = {
|
||||
listSamlAuthProviders: allow,
|
||||
},
|
||||
Mutation: {
|
||||
'*': isAuthenticatedRule,
|
||||
'*': isAuthenticated,
|
||||
forgotPassword: allow,
|
||||
login: allow,
|
||||
registerUser: allow,
|
||||
resetPassword: allow,
|
||||
},
|
||||
};
|
||||
|
||||
const authenticationOptions = {
|
||||
},
|
||||
{
|
||||
allowExternalErrors: true,
|
||||
};
|
||||
|
||||
const authentication = shield(authenticationRules, authenticationOptions);
|
||||
}
|
||||
);
|
||||
|
||||
export default authentication;
|
||||
|
@@ -1,78 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { allow } from 'graphql-shield';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import User from '../models/user.js';
|
||||
import { isAuthenticated, authenticationRules } from './authentication.js';
|
||||
|
||||
vi.mock('jsonwebtoken');
|
||||
vi.mock('../models/user.js');
|
||||
|
||||
describe('isAuthenticated', () => {
|
||||
it('should return false if no token is provided', async () => {
|
||||
const req = { headers: {} };
|
||||
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if token is invalid', async () => {
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error('invalid token');
|
||||
});
|
||||
|
||||
const req = { headers: { authorization: 'invalidToken' } };
|
||||
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if token is valid', async () => {
|
||||
jwt.verify.mockReturnValue({ userId: '123' });
|
||||
|
||||
User.query.mockReturnValue({
|
||||
findById: vi.fn().mockReturnValue({
|
||||
leftJoinRelated: vi.fn().mockReturnThis(),
|
||||
withGraphFetched: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ id: '123', role: {}, permissions: {} }),
|
||||
}),
|
||||
});
|
||||
|
||||
const req = { headers: { authorization: 'validToken' } };
|
||||
expect(await isAuthenticated(null, null, req)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('authentication rules', () => {
|
||||
const getQueryAndMutationNames = (rules) => {
|
||||
const queries = Object.keys(rules.Query || {});
|
||||
const mutations = Object.keys(rules.Mutation || {});
|
||||
return { queries, mutations };
|
||||
};
|
||||
|
||||
const { queries, mutations } = getQueryAndMutationNames(authenticationRules);
|
||||
|
||||
describe('for queries', () => {
|
||||
queries.forEach((query) => {
|
||||
it(`should apply correct rule for query: ${query}`, () => {
|
||||
const ruleApplied = authenticationRules.Query[query];
|
||||
|
||||
if (query === '*') {
|
||||
expect(ruleApplied.func).toBe(isAuthenticated);
|
||||
} else {
|
||||
expect(ruleApplied).toEqual(allow);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('for mutations', () => {
|
||||
mutations.forEach((mutation) => {
|
||||
it(`should apply correct rule for mutation: ${mutation}`, () => {
|
||||
const ruleApplied = authenticationRules.Mutation[mutation];
|
||||
|
||||
if (mutation === '*') {
|
||||
expect(ruleApplied.func).toBe(isAuthenticated);
|
||||
} else {
|
||||
expect(ruleApplied).toBe(allow);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
11
packages/cli/.editorconfig
Normal file
11
packages/cli/.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
1
packages/cli/.eslintignore
Normal file
1
packages/cli/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
/dist
|
16
packages/cli/.github/dependabot.yml
vendored
Normal file
16
packages/cli/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
versioning-strategy: increase
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
labels:
|
||||
- 'dependencies'
|
||||
open-pull-requests-limit: 100
|
||||
pull-request-branch-name:
|
||||
separator: '-'
|
||||
ignore:
|
||||
- dependency-name: 'fs-extra'
|
||||
- dependency-name: '*'
|
||||
update-types: ['version-update:semver-major']
|
9
packages/cli/.gitignore
vendored
Normal file
9
packages/cli/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*-debug.log
|
||||
*-error.log
|
||||
/.nyc_output
|
||||
/dist
|
||||
/lib
|
||||
/package-lock.json
|
||||
/tmp
|
||||
node_modules
|
||||
oclif.manifest.json
|
4
packages/cli/README.md
Normal file
4
packages/cli/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# `@automatisch/cli`
|
||||
|
||||
The open source Zapier alternative. Build workflow automation without spending
|
||||
time and money.
|
5
packages/cli/bin/automatisch
Executable file
5
packages/cli/bin/automatisch
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const oclif = require('@oclif/core')
|
||||
|
||||
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
|
3
packages/cli/bin/automatisch.cmd
Normal file
3
packages/cli/bin/automatisch.cmd
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\automatisch" %*
|
73
packages/cli/package.json
Normal file
73
packages/cli/package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "@automatisch/cli",
|
||||
"version": "0.10.0",
|
||||
"license": "See LICENSE file",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "automatisch contributors",
|
||||
"url": "https://github.com/automatisch/automatisch/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"homepage": "https://github.com/automatisch/automatisch#readme",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"automatisch": "./bin/automatisch"
|
||||
},
|
||||
"files": [
|
||||
"/bin",
|
||||
"/dist",
|
||||
"oclif.manifest.json"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/automatisch/automatisch.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "shx rm -rf dist && tsc -b",
|
||||
"build:watch": "nodemon --watch 'src/**/*.ts' --exec 'shx rm -rf dist && tsc -b' --ext 'ts'",
|
||||
"lint": "eslint . --ext .js --ignore-path ../../.eslintignore",
|
||||
"postpack": "shx rm -f oclif.manifest.json",
|
||||
"posttest": "yarn lint",
|
||||
"prepack": "yarn build && oclif manifest && oclif readme",
|
||||
"version": "oclif readme && git add README.md"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/backend": "^0.10.0",
|
||||
"@oclif/core": "^1",
|
||||
"@oclif/plugin-help": "^5",
|
||||
"@oclif/plugin-plugins": "^2.0.1",
|
||||
"dotenv": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@oclif/test": "^2",
|
||||
"@types/node": "^16.9.4",
|
||||
"eslint-config-oclif": "^4",
|
||||
"eslint-config-oclif-typescript": "^1.0.2",
|
||||
"globby": "^11",
|
||||
"oclif": "^2",
|
||||
"shx": "^0.3.3",
|
||||
"ts-node": "^10.2.1",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"oclif": {
|
||||
"bin": "automatisch",
|
||||
"dirname": "automatisch",
|
||||
"commands": "./dist/commands",
|
||||
"plugins": [
|
||||
"@oclif/plugin-help",
|
||||
"@oclif/plugin-plugins"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"types": "dist/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
50
packages/cli/src/commands/start-worker.js
Normal file
50
packages/cli/src/commands/start-worker.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import * as dotenv from 'dotenv';
|
||||
import process from 'process';
|
||||
|
||||
export default class StartWorker extends Command {
|
||||
static description = 'Run automatisch worker';
|
||||
|
||||
static flags = {
|
||||
env: Flags.string({
|
||||
multiple: true,
|
||||
char: 'e',
|
||||
}),
|
||||
'env-file': Flags.string(),
|
||||
};
|
||||
|
||||
async prepareEnvVars() {
|
||||
const { flags } = await this.parse(StartWorker);
|
||||
|
||||
if (flags['env-file']) {
|
||||
const envFile = readFileSync(flags['env-file'], 'utf8');
|
||||
const envConfig = dotenv.parse(envFile);
|
||||
|
||||
for (const key in envConfig) {
|
||||
const value = envConfig[key];
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.env) {
|
||||
for (const env of flags.env) {
|
||||
const [key, value] = env.split('=');
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// must serve until more customization is introduced
|
||||
delete process.env.SERVE_WEB_APP_SEPARATELY;
|
||||
}
|
||||
|
||||
async runWorker() {
|
||||
await import('@automatisch/backend/worker');
|
||||
}
|
||||
|
||||
async run() {
|
||||
await this.prepareEnvVars();
|
||||
|
||||
await this.runWorker();
|
||||
}
|
||||
}
|
96
packages/cli/src/commands/start.js
Normal file
96
packages/cli/src/commands/start.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import * as dotenv from 'dotenv';
|
||||
import process from 'process';
|
||||
|
||||
export default class Start extends Command {
|
||||
static description = 'Run automatisch';
|
||||
|
||||
static flags = {
|
||||
env: Flags.string({
|
||||
multiple: true,
|
||||
char: 'e',
|
||||
}),
|
||||
'env-file': Flags.string(),
|
||||
};
|
||||
|
||||
get isProduction() {
|
||||
return process.env.APP_ENV === 'production';
|
||||
}
|
||||
|
||||
async prepareEnvVars() {
|
||||
const { flags } = await this.parse(Start);
|
||||
|
||||
if (flags['env-file']) {
|
||||
const envFile = readFileSync(flags['env-file'], 'utf8');
|
||||
const envConfig = dotenv.parse(envFile);
|
||||
|
||||
for (const key in envConfig) {
|
||||
const value = envConfig[key];
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.env) {
|
||||
for (const env of flags.env) {
|
||||
const [key, value] = env.split('=');
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// must serve until more customization is introduced
|
||||
delete process.env.SERVE_WEB_APP_SEPARATELY;
|
||||
}
|
||||
|
||||
async createDatabaseAndUser() {
|
||||
const utils = await import('@automatisch/backend/database-utils');
|
||||
|
||||
await utils.createDatabaseAndUser(
|
||||
process.env.POSTGRES_DATABASE,
|
||||
process.env.POSTGRES_USERNAME
|
||||
);
|
||||
}
|
||||
|
||||
async runMigrationsIfNeeded() {
|
||||
const { logger } = await import('@automatisch/backend/logger');
|
||||
const database = await import('@automatisch/backend/database');
|
||||
const migrator = database.client.migrate;
|
||||
|
||||
const [, pendingMigrations] = await migrator.list();
|
||||
const pendingMigrationsCount = pendingMigrations.length;
|
||||
const needsToMigrate = pendingMigrationsCount > 0;
|
||||
|
||||
if (needsToMigrate) {
|
||||
logger.info(`Processing ${pendingMigrationsCount} migrations.`);
|
||||
|
||||
await migrator.latest();
|
||||
logger.info(`Completed ${pendingMigrationsCount} migrations.`);
|
||||
} else {
|
||||
logger.info('No migrations needed.');
|
||||
}
|
||||
}
|
||||
|
||||
async seedUser() {
|
||||
const utils = await import('@automatisch/backend/database-utils');
|
||||
|
||||
await utils.createUser();
|
||||
}
|
||||
|
||||
async runApp() {
|
||||
await import('@automatisch/backend/server');
|
||||
}
|
||||
|
||||
async run() {
|
||||
await this.prepareEnvVars();
|
||||
|
||||
if (!this.isProduction) {
|
||||
await this.createDatabaseAndUser();
|
||||
}
|
||||
|
||||
await this.runMigrationsIfNeeded();
|
||||
|
||||
await this.seedUser();
|
||||
|
||||
await this.runApp();
|
||||
}
|
||||
}
|
1
packages/cli/src/index.js
Normal file
1
packages/cli/src/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { run } from '@oclif/core';
|
19
packages/cli/tsconfig.json
Normal file
19
packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"importHelpers": true,
|
||||
"lib": ["es2021"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": false,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2021",
|
||||
"typeRoots": ["node_modules/@types", "./src/types"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
15993
packages/cli/yarn.lock
Normal file
15993
packages/cli/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -32,15 +32,6 @@ export default defineConfig({
|
||||
],
|
||||
sidebar: {
|
||||
'/apps/': [
|
||||
{
|
||||
text: 'Better Stack',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/better-stack/actions' },
|
||||
{ text: 'Connection', link: '/apps/better-stack/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Carbone',
|
||||
collapsible: true,
|
||||
@@ -314,7 +305,7 @@ export default defineConfig({
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/removebg/actions' },
|
||||
{ text: 'Connection', link: '/apps/removebg/connection' },
|
||||
{ text: 'Connection', link: '/apps/removebg/connection' }
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Telemetry
|
||||
|
||||
:::info
|
||||
We want to be very transparent about the data we collect and how we use it. Therefore, we have abstracted all of the code we use with our telemetry system into a single, easily accessible place. You can check the code [here](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/helpers/telemetry/index.js) and let us know if you have any suggestions for changes.
|
||||
We want to be very transparent about the data we collect and how we use it. Therefore, we have abstracted all of the code we use with our telemetry system into a single, easily accessible place. You can check the code [here](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/helpers/telemetry/index.ts) and let us know if you have any suggestions for changes.
|
||||
:::
|
||||
|
||||
Automatisch comes with a built-in telemetry system that collects anonymous usage data. This data is used to help us improve the product and to make sure we are focusing on the right features. While we're doing it, we don't collect any personal information. You can also disable the telemetry system by setting the `TELEMETRY_ENABLED` environment variable. See the [environment variables](/advanced/configuration#environment-variables) section for more information.
|
||||
|
@@ -1,14 +0,0 @@
|
||||
---
|
||||
favicon: /favicons/better-stack.svg
|
||||
items:
|
||||
- name: Acknowledge incident
|
||||
desc: Acknowledges an incident.
|
||||
- name: Create incident
|
||||
desc: Creates an incident that informs the team.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
@@ -1,14 +0,0 @@
|
||||
# Better Stack
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Better Stack
|
||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
1. Login to your Better Stack account: [https://betterstack.com/](https://betterstack.com/).
|
||||
2. Click on the team name bottom left and select **Manage Teams** option.
|
||||
3. Click on the three dots icon of your team and select **manage** option.
|
||||
4. Click on the **API tokens** tab.
|
||||
5. Copy the token next to **Direct API tokens** to the `API Key` field on Automatisch.
|
||||
6. Fill the screen name on Automatisch.
|
||||
7. Now, you can start using the Better Stack connection with Automatisch.
|
@@ -16,13 +16,13 @@ The build integrations section is best understood when read from beginning to en
|
||||
|
||||
## Add actions to the app.
|
||||
|
||||
Open the `thecatapi/index.js` file and add the highlighted lines for actions.
|
||||
Open the `thecatapi/index.ts` file and add the highlighted lines for actions.
|
||||
|
||||
```javascript{4,17}
|
||||
import defineApp from '../../helpers/define-app.js';
|
||||
import auth from './auth/index.js';
|
||||
import triggers from './triggers/index.js';
|
||||
import actions from './actions/index.js';
|
||||
```typescript{4,17}
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'The cat API',
|
||||
@@ -41,24 +41,24 @@ export default defineApp({
|
||||
|
||||
## Define actions
|
||||
|
||||
Create the `actions/index.js` file inside of the `thecatapi` folder.
|
||||
Create the `actions/index.ts` file inside of the `thecatapi` folder.
|
||||
|
||||
```javascript
|
||||
import markCatImageAsFavorite from './mark-cat-image-as-favorite/index.js';
|
||||
```typescript
|
||||
import markCatImageAsFavorite from './mark-cat-image-as-favorite';
|
||||
|
||||
export default [markCatImageAsFavorite];
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you add new actions, you need to add them to the actions/index.js file and export all actions as an array.
|
||||
If you add new actions, you need to add them to the actions/index.ts file and export all actions as an array.
|
||||
:::
|
||||
|
||||
## Add metadata
|
||||
|
||||
Create the `actions/mark-cat-image-as-favorite/index.js` file inside the `thecatapi` folder.
|
||||
Create the `actions/mark-cat-image-as-favorite/index.ts` file inside the `thecatapi` folder.
|
||||
|
||||
```javascript
|
||||
import defineAction from '../../../../helpers/define-action.js';
|
||||
```typescript
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Mark the cat image as favorite',
|
||||
@@ -68,7 +68,7 @@ export default defineAction({
|
||||
{
|
||||
label: 'Image ID',
|
||||
key: 'imageId',
|
||||
type: 'string',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The ID of the cat image you want to mark as favorite.',
|
||||
variables: true,
|
||||
@@ -91,10 +91,10 @@ Let's briefly explain what we defined here.
|
||||
|
||||
## Implement the action
|
||||
|
||||
Open the `actions/mark-cat-image-as-favorite.js` file and add the highlighted lines.
|
||||
Open the `actions/mark-cat-image-as-favorite.ts` file and add the highlighted lines.
|
||||
|
||||
```javascript{7-20}
|
||||
import defineAction from '../../../../helpers/define-action.js';
|
||||
```typescript{7-20}
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
// ...
|
||||
@@ -104,7 +104,7 @@ export default defineAction({
|
||||
const imageId = $.step.parameters.imageId;
|
||||
|
||||
const headers = {
|
||||
'x-api-key': $.auth.data.apiKey,
|
||||
'x-api-key': $.auth.data.apiKey as string,
|
||||
};
|
||||
|
||||
const response = await $.http.post(
|
||||
|
@@ -27,17 +27,17 @@ cd packages/backend/src/apps
|
||||
mkdir thecatapi
|
||||
```
|
||||
|
||||
We need to create an `index.js` file inside of the `thecatapi` folder.
|
||||
We need to create an `index.ts` file inside of the `thecatapi` folder.
|
||||
|
||||
```bash
|
||||
cd thecatapi
|
||||
touch index.js
|
||||
touch index.ts
|
||||
```
|
||||
|
||||
Then let's define the app inside of the `index.js` file as follows:
|
||||
Then let's define the app inside of the `index.ts` file as follows:
|
||||
|
||||
```javascript
|
||||
import defineApp from '../../helpers/define-app.js';
|
||||
```typescript
|
||||
import defineApp from '../../helpers/define-app';
|
||||
|
||||
export default defineApp({
|
||||
name: 'The cat API',
|
||||
|
@@ -24,11 +24,11 @@ You can find detailed documentation of the cat API [here](https://docs.thecatapi
|
||||
|
||||
## Add auth to the app
|
||||
|
||||
Open the `thecatapi/index.js` file and add the highlighted lines for authentication.
|
||||
Open the `thecatapi/index.ts` file and add the highlighted lines for authentication.
|
||||
|
||||
```javascript{2,13}
|
||||
import defineApp from '../../helpers/define-app.js';
|
||||
import auth from './auth/index.js';
|
||||
```typescript{2,13}
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import auth from './auth';
|
||||
|
||||
export default defineApp({
|
||||
name: 'The cat API',
|
||||
@@ -45,22 +45,22 @@ export default defineApp({
|
||||
|
||||
## Define auth fields
|
||||
|
||||
Let's create the `auth/index.js` file inside of the `thecatapi` folder.
|
||||
Let's create the `auth/index.ts` file inside of the `thecatapi` folder.
|
||||
|
||||
```bash
|
||||
mkdir auth
|
||||
touch auth/index.js
|
||||
touch auth/index.ts
|
||||
```
|
||||
|
||||
Then let's start with defining fields the auth inside of the `auth/index.js` file as follows:
|
||||
Then let's start with defining fields the auth inside of the `auth/index.ts` file as follows:
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'screenName',
|
||||
label: 'Screen Name',
|
||||
type: 'string',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
@@ -101,10 +101,10 @@ If the third-party service you use provides both an API key and OAuth for the au
|
||||
|
||||
So until now, we integrated auth folder with the app definition and defined the auth fields. Now we need to verify the credentials that the user entered. We will do that by defining the `verifyCredentials` method.
|
||||
|
||||
Start with adding the `verifyCredentials` method to the `auth/index.js` file.
|
||||
Start with adding the `verifyCredentials` method to the `auth/index.ts` file.
|
||||
|
||||
```javascript{1,8}
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
```typescript{1,8}
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
@@ -115,10 +115,12 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
Let's create the `verify-credentials.js` file inside the `auth` folder.
|
||||
Let's create the `verify-credentials.ts` file inside the `auth` folder.
|
||||
|
||||
```javascript
|
||||
const verifyCredentials = async ($) => {
|
||||
```typescript
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
// TODO: Implement verification of the credentials
|
||||
};
|
||||
|
||||
@@ -127,10 +129,12 @@ export default verifyCredentials;
|
||||
|
||||
We generally use the `users/me` endpoint or any other endpoint that we can validate the API key or any other credentials that the user provides. For our example, we don't have a specific API endpoint to check whether the credentials are correct or not. So we will randomly pick one of the API endpoints, which will be the `GET /v1/images/search` endpoint. We will send a request to this endpoint with the API key. If the API key is correct, we will get a response from the API. If the API key is incorrect, we will get an error response from the API.
|
||||
|
||||
Let's implement the authentication logic that we mentioned above in the `verify-credentials.js` file.
|
||||
Let's implement the authentication logic that we mentioned above in the `verify-credentials.ts` file.
|
||||
|
||||
```javascript
|
||||
const verifyCredentials = async ($) => {
|
||||
```typescript
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/v1/images/search');
|
||||
|
||||
await $.auth.set({
|
||||
@@ -151,11 +155,11 @@ You must always provide a `screenName` field to auth data in the `verifyCredenti
|
||||
|
||||
We have implemented the `verifyCredentials` method. Now we need to check whether the credentials are still valid or not for the test connection functionality in Automatisch. We will do that by defining the `isStillVerified` method.
|
||||
|
||||
Start with adding the `isStillVerified` method to the `auth/index.js` file.
|
||||
Start with adding the `isStillVerified` method to the `auth/index.ts` file.
|
||||
|
||||
```javascript{2,10}
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
import isStillVerified from './is-still-verified.js';
|
||||
```typescript{2,10}
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
@@ -167,12 +171,13 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
Let's create the `is-still-verified.js` file inside the `auth` folder.
|
||||
Let's create the `is-still-verified.ts` file inside the `auth` folder.
|
||||
|
||||
```javascript
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
```typescript
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
const isStillVerified = async ($) => {
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await verifyCredentials($);
|
||||
return true;
|
||||
};
|
||||
|
@@ -18,35 +18,35 @@ The build integrations section is best understood when read from beginning to en
|
||||
|
||||
### 3-legged OAuth
|
||||
|
||||
- [Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/auth/index.js)
|
||||
- [Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/auth/index.js)
|
||||
- [Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/auth/index.js)
|
||||
- [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.js)
|
||||
- [Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/auth/index.js)
|
||||
- [Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/auth/index.js)
|
||||
- [Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/auth/index.ts)
|
||||
- [Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/auth/index.ts)
|
||||
- [Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/auth/index.ts)
|
||||
- [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.ts)
|
||||
- [Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/auth/index.ts)
|
||||
- [Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/auth/index.ts)
|
||||
|
||||
### OAuth with the refresh token
|
||||
|
||||
- [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.js)
|
||||
- [Salesforce](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/salesforce/auth/index.ts)
|
||||
|
||||
### API key
|
||||
|
||||
- [DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/auth/index.js)
|
||||
- [Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/auth/index.js)
|
||||
- [SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/auth/index.js)
|
||||
- [SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/auth/index.js)
|
||||
- [DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/auth/index.ts)
|
||||
- [Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/auth/index.ts)
|
||||
- [SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/auth/index.ts)
|
||||
- [SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/auth/index.ts)
|
||||
|
||||
### Without authentication
|
||||
|
||||
- [RSS](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/rss/index.js)
|
||||
- [Scheduler](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/scheduler/index.js)
|
||||
- [RSS](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/rss/index.ts)
|
||||
- [Scheduler](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/scheduler/index.ts)
|
||||
|
||||
## Triggers
|
||||
|
||||
### Polling-based triggers
|
||||
|
||||
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.js)
|
||||
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.js)
|
||||
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts)
|
||||
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.ts)
|
||||
|
||||
### Webhook-based triggers
|
||||
|
||||
@@ -54,27 +54,27 @@ The build integrations section is best understood when read from beginning to en
|
||||
If you are developing a webhook-based trigger, you need to ensure that the webhook is publicly accessible. You can use [ngrok](https://ngrok.com) for this purpose and override the webhook URL by setting the **WEBHOOK_URL** environment variable.
|
||||
:::
|
||||
|
||||
- [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.js)
|
||||
- [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.ts)
|
||||
|
||||
### Pagination with descending order
|
||||
|
||||
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.js)
|
||||
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.js)
|
||||
- [Receive SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/triggers/receive-sms/index.js)
|
||||
- [Receive SMS - SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/triggers/receive-sms/index.js)
|
||||
- [New photos - Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/triggers/new-photos/index.js)
|
||||
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts)
|
||||
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.ts)
|
||||
- [Receive SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/triggers/receive-sms/index.ts)
|
||||
- [Receive SMS - SignalWire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts)
|
||||
- [New photos - Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/triggers/new-photos/index.ts)
|
||||
|
||||
### Pagination with ascending order
|
||||
|
||||
- [New stargazers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-stargazers/index.js)
|
||||
- [New watchers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-watchers/index.js)
|
||||
- [New stargazers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-stargazers/index.ts)
|
||||
- [New watchers - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-watchers/index.ts)
|
||||
|
||||
## Actions
|
||||
|
||||
- [Send a message to channel - Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/index.js)
|
||||
- [Send SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/actions/send-sms/index.js)
|
||||
- [Send a message to channel - Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/actions/send-message-to-channel/index.js)
|
||||
- [Create issue - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/actions/create-issue/index.js)
|
||||
- [Send an email - SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/actions/send-email/index.js)
|
||||
- [Create tweet - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/actions/create-tweet/index.js)
|
||||
- [Translate text - DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/actions/translate-text/index.js)
|
||||
- [Send a message to channel - Slack](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/index.ts)
|
||||
- [Send SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/actions/send-sms/index.ts)
|
||||
- [Send a message to channel - Discord](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/discord/actions/send-message-to-channel/index.ts)
|
||||
- [Create issue - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/actions/create-issue/index.ts)
|
||||
- [Send an email - SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/actions/send-email/index.ts)
|
||||
- [Create tweet - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/actions/create-tweet/index.ts)
|
||||
- [Translate text - DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/actions/translate-text/index.ts)
|
||||
|
@@ -35,13 +35,13 @@ Here, you can see the folder structure of an example app. We will briefly walk t
|
||||
├── auth
|
||||
├── common
|
||||
├── dynamic-data
|
||||
├── index.js
|
||||
├── index.ts
|
||||
└── triggers
|
||||
```
|
||||
|
||||
## App
|
||||
|
||||
The `index.js` file is the entry point of the app. It contains the definition of the app and the app's metadata. It also includes the list of triggers, actions, and data sources that the app provides. So, whatever you build inside the app, you need to associate it within the `index.js` file.
|
||||
The `index.ts` file is the entry point of the app. It contains the definition of the app and the app's metadata. It also includes the list of triggers, actions, and data sources that the app provides. So, whatever you build inside the app, you need to associate it within the `index.ts` file.
|
||||
|
||||
## Auth
|
||||
|
||||
|
@@ -16,11 +16,11 @@ The build integrations section is best understood when read from beginning to en
|
||||
|
||||
Before handling authentication and building a trigger and an action, it's better to explain the `global variable` concept in Automatisch. Automatisch provides you the global variable that you need to use with authentication, triggers, actions, and basically all the stuff you will build for the integration.
|
||||
|
||||
The global variable is represented as `$` variable in the codebase, and it's a JS object that contains the following properties:
|
||||
The global variable is represented as `$` variable in the codebase, and it's a JSON object that contains the following properties:
|
||||
|
||||
## $.auth.set
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
await $.auth.set({
|
||||
key: 'value',
|
||||
});
|
||||
@@ -30,7 +30,7 @@ It's used to set the authentication data, and you can use this method with multi
|
||||
|
||||
## $.auth.data
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.auth.data; // { key: 'value' }
|
||||
```
|
||||
|
||||
@@ -38,7 +38,7 @@ It's used to retrieve the authentication data that we set with `$.auth.set()`. T
|
||||
|
||||
## $.app.baseUrl
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.app.baseUrl; // https://thecatapi.com
|
||||
```
|
||||
|
||||
@@ -46,7 +46,7 @@ It's used to retrieve the base URL of the app that we defined previously. In our
|
||||
|
||||
## $.app.apiBaseUrl
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.app.apiBaseUrl; // https://api.thecatapi.com
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ It's used to retrieve the API base URL of the app that we defined previously. In
|
||||
|
||||
## $.app.auth.fields
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.app.auth.fields;
|
||||
```
|
||||
|
||||
@@ -64,7 +64,7 @@ It's used to retrieve the fields that we defined in the `auth` section of the ap
|
||||
|
||||
It's an HTTP client to be used for making HTTP requests. It's a wrapper around the [axios](https://axios-http.com) library. We use this property when we need to make HTTP requests to the third-party service. The `apiBaseUrl` field we set up in the app will be used as the base URL for the HTTP requests. For example, to search the cat images, we can use the following code:
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
await $.http.get('/v1/images/search?order=DESC', {
|
||||
headers: {
|
||||
'x-api-key': $.auth.data.apiKey,
|
||||
@@ -76,15 +76,15 @@ Keep in mind that the HTTP client handles the error with the status code that fa
|
||||
|
||||
## $.step.parameters
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.step.parameters; // { key: 'value' }
|
||||
```
|
||||
|
||||
It refers to the parameters that are set by users in the UI. We use this property when we need to get the parameters for corresponding triggers and actions. For example [Send a message to channel](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.js) action from Slack integration, we have a step parameter called `message` that we need to use in the action. We can use `$.step.parameters.message` to get the value of the message to send a message to the Slack channel.
|
||||
It refers to the parameters that are set by users in the UI. We use this property when we need to get the parameters for corresponding triggers and actions. For example [Send a message to channel](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/apps/slack/actions/send-a-message-to-channel/post-message.ts) action from Slack integration, we have a step parameter called `message` that we need to use in the action. We can use `$.step.parameters.message` to get the value of the message to send a message to the Slack channel.
|
||||
|
||||
## $.pushTriggerItem
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.pushTriggerItem({
|
||||
raw: resourceData,
|
||||
meta: {
|
||||
@@ -97,7 +97,7 @@ It's used to push trigger data to be processed by Automatisch. It must reflect t
|
||||
|
||||
## $.setActionItem
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
$.setActionItem({
|
||||
raw: resourceData,
|
||||
});
|
||||
|
@@ -20,12 +20,12 @@ We used a polling-based HTTP trigger in our example but if you need to use a web
|
||||
|
||||
## Add triggers to the app
|
||||
|
||||
Open the `thecatapi/index.js` file and add the highlighted lines for triggers.
|
||||
Open the `thecatapi/index.ts` file and add the highlighted lines for triggers.
|
||||
|
||||
```javascript{3,15}
|
||||
import defineApp from '../../helpers/define-app.js';
|
||||
import auth from './auth/index.js';
|
||||
import triggers from './triggers/index.js';
|
||||
```typescript{3,15}
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
|
||||
export default defineApp({
|
||||
name: 'The cat API',
|
||||
@@ -43,24 +43,24 @@ export default defineApp({
|
||||
|
||||
## Define triggers
|
||||
|
||||
Create the `triggers/index.js` file inside of the `thecatapi` folder.
|
||||
Create the `triggers/index.ts` file inside of the `thecatapi` folder.
|
||||
|
||||
```javascript
|
||||
import searchCatImages from './search-cat-images/index.js';
|
||||
```typescript
|
||||
import searchCatImages from './search-cat-images';
|
||||
|
||||
export default [searchCatImages];
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you add new triggers, you need to add them to the `triggers/index.js` file and export all triggers as an array. The order of triggers in this array will be reflected in the Automatisch user interface.
|
||||
If you add new triggers, you need to add them to the `triggers/index.ts` file and export all triggers as an array. The order of triggers in this array will be reflected in the Automatisch user interface.
|
||||
:::
|
||||
|
||||
## Add metadata
|
||||
|
||||
Create the `triggers/search-cat-images/index.js` file inside of the `thecatapi` folder.
|
||||
Create the `triggers/search-cat-images/index.ts` file inside of the `thecatapi` folder.
|
||||
|
||||
```javascript
|
||||
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||
```typescript
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Search cat images',
|
||||
@@ -93,8 +93,9 @@ Let's briefly explain what we defined here.
|
||||
|
||||
Implement the `run` function by adding highlighted lines.
|
||||
|
||||
```javascript{1,7-30}
|
||||
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||
```typescript{1,7-30}
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
// ...
|
||||
@@ -103,18 +104,18 @@ export default defineTrigger({
|
||||
let response;
|
||||
|
||||
const headers = {
|
||||
'x-api-key': $.auth.data.apiKey,
|
||||
'x-api-key': $.auth.data.apiKey as string,
|
||||
};
|
||||
|
||||
do {
|
||||
let requestPath = `/v1/images/search?page=${page}&limit=10&order=DESC`;
|
||||
response = await $.http.get(requestPath, { headers });
|
||||
|
||||
response.data.forEach((image) => {
|
||||
response.data.forEach((image: IJSONObject) => {
|
||||
const dataItem = {
|
||||
raw: image,
|
||||
meta: {
|
||||
internalId: image.id
|
||||
internalId: image.id as string
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -35,7 +35,7 @@ yarn db:create
|
||||
```
|
||||
|
||||
:::warning
|
||||
`yarn db:create` commands expect that you have the `postgres` superuser. If not, you can create a superuser called `postgres` manually or you can create the database manually by checking PostgreSQL-related default values from the [app config](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/config/app.js).
|
||||
`yarn db:create` commands expect that you have the `postgres` superuser. If not, you can create a superuser called `postgres` manually or you can create the database manually by checking PostgreSQL-related default values from the [app config](https://github.com/automatisch/automatisch/blob/main/packages/backend/src/config/app.ts).
|
||||
:::
|
||||
|
||||
Run the database migrations in the backend folder.
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
The following integrations are currently supported by Automatisch.
|
||||
|
||||
- [Better Stack](/apps/better-stack/actions)
|
||||
- [Carbone](/apps/carbone/actions)
|
||||
- [DeepL](/apps/deepl/actions)
|
||||
- [Delay](/apps/delay/actions)
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
|
||||
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
|
||||
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
|
||||
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
|
||||
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
|
||||
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
|
||||
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
|
||||
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
|
||||
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
|
||||
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
4
packages/types/README.md
Normal file
4
packages/types/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# `@automatisch/types`
|
||||
|
||||
The open source Zapier alternative. Build workflow automation without spending
|
||||
time and money.
|
20
packages/types/package.json
Normal file
20
packages/types/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@automatisch/types",
|
||||
"version": "0.10.0",
|
||||
"license": "See LICENSE file",
|
||||
"description": "Type definitions for automatisch",
|
||||
"homepage": "https://github.com/automatisch/automatisch",
|
||||
"types": "./index.d.ts",
|
||||
"scripts": {},
|
||||
"typeScriptVersion": "4.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/automatisch/automatisch.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.6.9",
|
||||
"@automatisch/types": "^0.10.0",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@casl/react": "^3.1.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { IApp, IField, IJSONObject } from 'types';
|
||||
import type { IApp, IField, IJSONObject } from '@automatisch/types';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
|
@@ -19,7 +19,7 @@ import InputLabel from '@mui/material/InputLabel';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Box from '@mui/material/Box';
|
||||
import type { IApp } from 'types';
|
||||
import type { IApp } from '@automatisch/types';
|
||||
|
||||
import * as URLS from 'config/urls';
|
||||
import AppIcon from 'components/AppIcon';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import type { IField } from 'types';
|
||||
import type { IField } from '@automatisch/types';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import type { IApp } from 'types';
|
||||
import type { IApp } from '@automatisch/types';
|
||||
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { IApp } from 'types';
|
||||
import type { IApp } from '@automatisch/types';
|
||||
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client';
|
||||
|
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import type { PopoverProps } from '@mui/material/Popover';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import type { IConnection } from 'types';
|
||||
import type { IConnection } from '@automatisch/types';
|
||||
|
||||
import * as URLS from 'config/urls';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
@@ -11,7 +11,7 @@ import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||
import { DateTime } from 'luxon';
|
||||
import * as React from 'react';
|
||||
|
||||
import type { IConnection } from 'types';
|
||||
import type { IConnection } from '@automatisch/types';
|
||||
import ConnectionContextMenu from 'components/AppConnectionContextMenu';
|
||||
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
|
||||
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
|
||||
@@ -83,8 +83,8 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
|
||||
enqueueSnackbar(formatMessage('connection.deletedMessage'), {
|
||||
variant: 'success',
|
||||
SnackbarProps: {
|
||||
'data-test': 'snackbar-delete-connection-success',
|
||||
},
|
||||
'data-test': 'snackbar-delete-connection-success'
|
||||
}
|
||||
});
|
||||
} else if (action.type === 'test') {
|
||||
setVerificationVisible(true);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import type { IConnection } from 'types';
|
||||
import type { IConnection } from '@automatisch/types';
|
||||
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
||||
import AppConnectionRow from 'components/AppConnectionRow';
|
||||
import NoResultFound from 'components/NoResultFound';
|
||||
|
@@ -8,7 +8,7 @@ import * as URLS from 'config/urls';
|
||||
import AppFlowRow from 'components/FlowRow';
|
||||
import NoResultFound from 'components/NoResultFound';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import type { IFlow } from 'types';
|
||||
import type { IFlow } from '@automatisch/types';
|
||||
|
||||
type AppFlowsProps = {
|
||||
appKey: string;
|
||||
|
@@ -7,7 +7,7 @@ import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import AppIcon from 'components/AppIcon';
|
||||
import type { IApp } from 'types';
|
||||
import type { IApp } from '@automatisch/types';
|
||||
|
||||
import { CardContent, Typography } from './style';
|
||||
|
||||
|
@@ -7,7 +7,13 @@ import ListItem from '@mui/material/ListItem';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import type { IApp, IStep, ISubstep, ITrigger, IAction } from 'types';
|
||||
import type {
|
||||
IApp,
|
||||
IStep,
|
||||
ISubstep,
|
||||
ITrigger,
|
||||
IAction,
|
||||
} from '@automatisch/types';
|
||||
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useApps from 'hooks/useApps';
|
||||
|
@@ -6,7 +6,7 @@ import ListItem from '@mui/material/ListItem';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import * as React from 'react';
|
||||
|
||||
import type { IApp, IConnection, IStep, ISubstep } from 'types';
|
||||
import type { IApp, IConnection, IStep, ISubstep } from '@automatisch/types';
|
||||
import AddAppConnection from 'components/AddAppConnection';
|
||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||
|
@@ -1,12 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import Autocomplete, {
|
||||
AutocompleteProps,
|
||||
createFilterOptions,
|
||||
} from '@mui/material/Autocomplete';
|
||||
import Autocomplete, { AutocompleteProps, createFilterOptions } from '@mui/material/Autocomplete';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import type { IFieldDropdownOption } from 'types';
|
||||
import type { IFieldDropdownOption } from '@automatisch/types';
|
||||
|
||||
interface ControlledAutocompleteProps
|
||||
extends AutocompleteProps<IFieldDropdownOption, boolean, boolean, boolean> {
|
||||
@@ -26,8 +23,8 @@ const filterOptions = createFilterOptions<IFieldDropdownOption>({
|
||||
stringify: ({ label, value }) => `
|
||||
${label}
|
||||
${value}
|
||||
`,
|
||||
});
|
||||
`
|
||||
})
|
||||
|
||||
function ControlledAutocomplete(
|
||||
props: ControlledAutocompleteProps
|
||||
|
@@ -3,7 +3,7 @@ import Popper from '@mui/material/Popper';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import * as React from 'react';
|
||||
|
||||
import type { IFieldDropdownOption } from 'types';
|
||||
import type { IFieldDropdownOption } from '@automatisch/types';
|
||||
import Suggestions from 'components/PowerInput/Suggestions';
|
||||
import TabPanel from 'components/TabPanel';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { IFieldDropdownOption } from 'types';
|
||||
import type { IFieldDropdownOption } from '@automatisch/types';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import throttle from 'lodash/throttle';
|
||||
@@ -13,7 +13,7 @@ import { SearchInputWrapper } from './style';
|
||||
interface OptionsProps {
|
||||
data: readonly IFieldDropdownOption[];
|
||||
onOptionClick: (event: React.MouseEvent, option: any) => void;
|
||||
}
|
||||
};
|
||||
|
||||
const SHORT_LIST_LENGTH = 4;
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
@@ -21,11 +21,9 @@ const LIST_ITEM_HEIGHT = 64;
|
||||
const computeListHeight = (currentLength: number) => {
|
||||
const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength);
|
||||
return LIST_ITEM_HEIGHT * numberOfRenderedItems;
|
||||
};
|
||||
}
|
||||
|
||||
const renderItemFactory =
|
||||
({ onOptionClick }: Pick<OptionsProps, 'onOptionClick'>) =>
|
||||
(props: ListChildComponentProps) => {
|
||||
const renderItemFactory = ({ onOptionClick }: Pick<OptionsProps, 'onOptionClick'>) => (props: ListChildComponentProps) => {
|
||||
const { index, style, data } = props;
|
||||
|
||||
const suboption = data[index];
|
||||
@@ -55,34 +53,31 @@ const renderItemFactory =
|
||||
/>
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Options = (props: OptionsProps) => {
|
||||
const formatMessage = useFormatMessage();
|
||||
const { data, onOptionClick } = props;
|
||||
const [filteredData, setFilteredData] =
|
||||
React.useState<readonly IFieldDropdownOption[]>(data);
|
||||
const {
|
||||
data,
|
||||
onOptionClick
|
||||
} = props;
|
||||
const [filteredData, setFilteredData] = React.useState<readonly IFieldDropdownOption[]>(
|
||||
data
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
function syncOptions() {
|
||||
React.useEffect(function syncOptions() {
|
||||
setFilteredData((filteredData) => {
|
||||
if (filteredData.length === 0 && filteredData.length !== data.length) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return filteredData;
|
||||
});
|
||||
},
|
||||
[data]
|
||||
);
|
||||
})
|
||||
}, [data]);
|
||||
|
||||
const renderItem = React.useMemo(
|
||||
() =>
|
||||
renderItemFactory({
|
||||
onOptionClick,
|
||||
}),
|
||||
[onOptionClick]
|
||||
);
|
||||
const renderItem = React.useMemo(() => renderItemFactory({
|
||||
onOptionClick
|
||||
}), [onOptionClick]);
|
||||
|
||||
const onSearchChange = React.useMemo(
|
||||
() =>
|
||||
@@ -94,16 +89,12 @@ const Options = (props: OptionsProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const newFilteredData = data.filter((option) =>
|
||||
`${option.label}\n${option.value}`
|
||||
.toLowerCase()
|
||||
.includes(search.toLowerCase())
|
||||
);
|
||||
const newFilteredData = data.filter(option => `${option.label}\n${option.value}`.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
setFilteredData(newFilteredData);
|
||||
}, 400),
|
||||
[data]
|
||||
);
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -131,4 +122,5 @@ const Options = (props: OptionsProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default Options;
|
||||
|
@@ -5,7 +5,7 @@ import FormHelperText from '@mui/material/FormHelperText';
|
||||
import { AutocompleteProps } from '@mui/material/Autocomplete';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import type { IFieldDropdownOption } from 'types';
|
||||
import type { IFieldDropdownOption } from '@automatisch/types';
|
||||
import { ActionButtonsWrapper } from './style';
|
||||
|
||||
import ClickAwayListener from '@mui/base/ClickAwayListener';
|
||||
|
@@ -8,7 +8,7 @@ import IconButton from '@mui/material/IconButton';
|
||||
import RemoveIcon from '@mui/icons-material/Remove';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
import { IFieldDynamic } from 'types';
|
||||
import { IFieldDynamic } from '@automatisch/types';
|
||||
import InputCreator from 'components/InputCreator';
|
||||
import { EditorContext } from 'contexts/Editor';
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import { useMutation } from '@apollo/client';
|
||||
import Box from '@mui/material/Box';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import type { IFlow, IStep } from 'types';
|
||||
import type { IFlow, IStep } from '@automatisch/types';
|
||||
|
||||
import { GET_FLOW } from 'graphql/queries/get-flow';
|
||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||
|
@@ -17,7 +17,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status';
|
||||
import { UPDATE_FLOW } from 'graphql/mutations/update-flow';
|
||||
import { GET_FLOW } from 'graphql/queries/get-flow';
|
||||
import type { IFlow } from 'types';
|
||||
import type { IFlow } from '@automatisch/types';
|
||||
import * as URLS from 'config/urls';
|
||||
|
||||
export default function EditorLayout(): React.ReactElement {
|
||||
|
@@ -4,7 +4,7 @@ import Stack from '@mui/material/Stack';
|
||||
import Box from '@mui/material/Box';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import type { IExecution } from 'types';
|
||||
import type { IExecution } from '@automatisch/types';
|
||||
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import CardActionArea from '@mui/material/CardActionArea';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import { DateTime } from 'luxon';
|
||||
import type { IExecution } from 'types';
|
||||
import type { IExecution } from '@automatisch/types';
|
||||
|
||||
import * as URLS from 'config/urls';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
@@ -8,7 +8,7 @@ import Tab from '@mui/material/Tab';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Box from '@mui/material/Box';
|
||||
import type { IApp, IExecutionStep, IStep } from 'types';
|
||||
import type { IApp, IExecutionStep, IStep } from '@automatisch/types';
|
||||
|
||||
import TabPanel from 'components/TabPanel';
|
||||
import SearchableJSONViewer from 'components/SearchableJSONViewer';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { IStep } from 'types';
|
||||
import type { IStep } from '@automatisch/types';
|
||||
|
||||
import AppIcon from 'components/AppIcon';
|
||||
import IntermediateStepCount from 'components/IntermediateStepCount';
|
||||
|
@@ -7,7 +7,7 @@ import Chip from '@mui/material/Chip';
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import type { IFlow } from 'types';
|
||||
import type { IFlow } from '@automatisch/types';
|
||||
import FlowAppIcons from 'components/FlowAppIcons';
|
||||
import FlowContextMenu from 'components/FlowContextMenu';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
@@ -14,7 +14,13 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import * as yup from 'yup';
|
||||
import type { BaseSchema } from 'yup';
|
||||
import type { IApp, ITrigger, IAction, IStep, ISubstep } from 'types';
|
||||
import type {
|
||||
IApp,
|
||||
ITrigger,
|
||||
IAction,
|
||||
IStep,
|
||||
ISubstep,
|
||||
} from '@automatisch/types';
|
||||
|
||||
import { EditorContext } from 'contexts/Editor';
|
||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||
|
@@ -8,7 +8,7 @@ import Divider from '@mui/material/Divider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import RemoveIcon from '@mui/icons-material/Remove';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import type { IField, IFieldText, IFieldDropdown } from 'types';
|
||||
import type { IField, IFieldText, IFieldDropdown } from '@automatisch/types';
|
||||
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import InputCreator from 'components/InputCreator';
|
||||
@@ -19,7 +19,7 @@ type TGroupItem = {
|
||||
operator: string;
|
||||
value: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
type TGroup = Record<'and', TGroupItem[]>;
|
||||
|
||||
@@ -31,7 +31,7 @@ const createGroupItem = (): TGroupItem => ({
|
||||
});
|
||||
|
||||
const createGroup = (): TGroup => ({
|
||||
and: [createGroupItem()],
|
||||
and: [createGroupItem()]
|
||||
});
|
||||
|
||||
const operators = [
|
||||
@@ -69,9 +69,7 @@ const operators = [
|
||||
},
|
||||
];
|
||||
|
||||
const createStringArgument = (
|
||||
argumentOptions: Omit<IFieldText, 'type' | 'required' | 'variables'>
|
||||
): IField => {
|
||||
const createStringArgument = (argumentOptions: Omit<IFieldText, 'type' | 'required' | 'variables'>): IField => {
|
||||
return {
|
||||
...argumentOptions,
|
||||
type: 'string',
|
||||
@@ -80,9 +78,7 @@ const createStringArgument = (
|
||||
};
|
||||
};
|
||||
|
||||
const createDropdownArgument = (
|
||||
argumentOptions: Omit<IFieldDropdown, 'type' | 'required'>
|
||||
): IField => {
|
||||
const createDropdownArgument = (argumentOptions: Omit<IFieldDropdown, 'type' | 'required'>): IField => {
|
||||
return {
|
||||
...argumentOptions,
|
||||
required: true,
|
||||
@@ -95,7 +91,9 @@ type FilterConditionsProps = {
|
||||
};
|
||||
|
||||
function FilterConditions(props: FilterConditionsProps): React.ReactElement {
|
||||
const { stepId } = props;
|
||||
const {
|
||||
stepId
|
||||
} = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const { control, setValue, getValues } = useFormContext();
|
||||
const groups = useWatch({ control, name: 'parameters.or' });
|
||||
@@ -112,7 +110,7 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
|
||||
const appendGroup = React.useCallback(() => {
|
||||
const values = getValues('parameters.or');
|
||||
|
||||
setValue('parameters.or', values.concat(createGroup()));
|
||||
setValue('parameters.or', values.concat(createGroup()))
|
||||
}, []);
|
||||
|
||||
const appendGroupItem = React.useCallback((index) => {
|
||||
@@ -126,89 +124,48 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
|
||||
if (group.length === 1) {
|
||||
const groups: TGroup[] = getValues('parameters.or');
|
||||
|
||||
setValue(
|
||||
'parameters.or',
|
||||
groups.filter((group, index) => index !== groupIndex)
|
||||
);
|
||||
setValue('parameters.or', groups.filter((group, index) => index !== groupIndex));
|
||||
} else {
|
||||
setValue(
|
||||
`parameters.or.${groupIndex}.and`,
|
||||
group.filter((groupItem, index) => index !== groupItemIndex)
|
||||
);
|
||||
setValue(`parameters.or.${groupIndex}.and`, group.filter((groupItem, index) => index !== groupItemIndex));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stack sx={{ width: '100%' }} direction="column" spacing={2} mt={2}>
|
||||
<Stack sx={{ width: "100%" }} direction="column" spacing={2} mt={2}>
|
||||
{groups?.map((group: TGroup, groupIndex: number) => (
|
||||
<>
|
||||
{groupIndex !== 0 && <Divider />}
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{groupIndex === 0 &&
|
||||
formatMessage('filterConditions.onlyContinueIf')}
|
||||
{groupIndex !== 0 &&
|
||||
formatMessage('filterConditions.orContinueIf')}
|
||||
{groupIndex === 0 && formatMessage('filterConditions.onlyContinueIf')}
|
||||
{groupIndex !== 0 && formatMessage('filterConditions.orContinueIf')}
|
||||
</Typography>
|
||||
|
||||
{group?.and?.map(
|
||||
(groupItem: TGroupItem, groupItemIndex: number) => (
|
||||
{group?.and?.map((groupItem: TGroupItem, groupItemIndex: number) => (
|
||||
<Stack direction="row" spacing={2} key={`item-${groupItem.id}`}>
|
||||
<Stack
|
||||
direction={{ xs: 'column', sm: 'row' }}
|
||||
spacing={{ xs: 2 }}
|
||||
sx={{ display: 'flex', flex: 1 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flex: '1 0 0px',
|
||||
maxWidth: ['100%', '33%'],
|
||||
}}
|
||||
>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 2 }} sx={{ display: 'flex', flex: 1 }}>
|
||||
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}>
|
||||
<InputCreator
|
||||
schema={createStringArgument({
|
||||
key: `or.${groupIndex}.and.${groupItemIndex}.key`,
|
||||
label: 'Choose field',
|
||||
})}
|
||||
schema={createStringArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.key`, label: 'Choose field' })}
|
||||
namePrefix="parameters"
|
||||
stepId={stepId}
|
||||
disabled={editorContext.readOnly}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flex: '1 0 0px',
|
||||
maxWidth: ['100%', '33%'],
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}>
|
||||
<InputCreator
|
||||
schema={createDropdownArgument({
|
||||
key: `or.${groupIndex}.and.${groupItemIndex}.operator`,
|
||||
options: operators,
|
||||
label: 'Choose condition',
|
||||
})}
|
||||
schema={createDropdownArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.operator`, options: operators, label: 'Choose condition' })}
|
||||
namePrefix="parameters"
|
||||
stepId={stepId}
|
||||
disabled={editorContext.readOnly}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flex: '1 0 0px',
|
||||
maxWidth: ['100%', '33%'],
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flex: '1 0 0px', maxWidth: ['100%', '33%'] }}>
|
||||
<InputCreator
|
||||
schema={createStringArgument({
|
||||
key: `or.${groupIndex}.and.${groupItemIndex}.value`,
|
||||
label: 'Enter text',
|
||||
})}
|
||||
schema={createStringArgument({ key: `or.${groupIndex}.and.${groupItemIndex}.value`, label: 'Enter text' })}
|
||||
namePrefix="parameters"
|
||||
stepId={stepId}
|
||||
disabled={editorContext.readOnly}
|
||||
@@ -225,8 +182,7 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
|
||||
<RemoveIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
)
|
||||
)}
|
||||
))}
|
||||
|
||||
<Stack spacing={1} direction="row">
|
||||
<IconButton
|
||||
@@ -238,16 +194,14 @@ function FilterConditions(props: FilterConditionsProps): React.ReactElement {
|
||||
<AddIcon /> And
|
||||
</IconButton>
|
||||
|
||||
{groups.length - 1 === groupIndex && (
|
||||
<IconButton
|
||||
{(groups.length - 1) === groupIndex && <IconButton
|
||||
size="small"
|
||||
edge="start"
|
||||
onClick={appendGroup}
|
||||
sx={{ width: 61, height: 61 }}
|
||||
>
|
||||
<AddIcon /> Or
|
||||
</IconButton>
|
||||
)}
|
||||
</IconButton>}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
|
@@ -4,7 +4,7 @@ import Collapse from '@mui/material/Collapse';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import Button from '@mui/material/Button';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import type { IStep, ISubstep } from 'types';
|
||||
import type { IStep, ISubstep } from '@automatisch/types';
|
||||
|
||||
import { EditorContext } from 'contexts/Editor';
|
||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||
@@ -54,7 +54,7 @@ function FlowSubstep(props: FlowSubstepProps): React.ReactElement {
|
||||
pb: 3,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
position: 'relative',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{!!args?.length && (
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import MuiTextField from '@mui/material/TextField';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import type { IField, IFieldDropdownOption } from 'types';
|
||||
import type { IField, IFieldDropdownOption } from '@automatisch/types';
|
||||
|
||||
import useDynamicFields from 'hooks/useDynamicFields';
|
||||
import useDynamicData from 'hooks/useDynamicData';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { JSONTree } from 'react-json-tree';
|
||||
import type { IJSONObject } from 'types';
|
||||
import type { IJSONObject } from '@automatisch/types';
|
||||
|
||||
type JSONViewerProps = {
|
||||
data: IJSONObject;
|
||||
|
@@ -14,7 +14,7 @@ import Typography from '@mui/material/Typography';
|
||||
import * as React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { IPermissionCatalog } from 'types';
|
||||
import { IPermissionCatalog } from '@automatisch/types';
|
||||
import ControlledCheckbox from 'components/ControlledCheckbox';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
@@ -118,9 +118,7 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
|
||||
{action.subjects.includes(subject) && (
|
||||
<ControlledCheckbox
|
||||
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
|
||||
dataTest={`${
|
||||
condition.key
|
||||
}-${action.key.toLowerCase()}-checkbox`}
|
||||
dataTest={`${condition.key}-${action.key.toLowerCase()}-checkbox`}
|
||||
defaultValue={defaultChecked}
|
||||
disabled={
|
||||
getValues(
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { IStep } from 'types';
|
||||
import type { IStep } from '@automatisch/types';
|
||||
import ExpandLess from '@mui/icons-material/ExpandLess';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import Box from '@mui/material/Box';
|
||||
@@ -20,7 +20,7 @@ type SuggestionsProps = {
|
||||
data: {
|
||||
id: string;
|
||||
name: string;
|
||||
output: Record<string, unknown>[];
|
||||
output: Record<string, unknown>[]
|
||||
}[];
|
||||
onSuggestionClick: (variable: any) => void;
|
||||
};
|
||||
@@ -31,15 +31,13 @@ const LIST_ITEM_HEIGHT = 64;
|
||||
const computeListHeight = (currentLength: number) => {
|
||||
const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength);
|
||||
return LIST_ITEM_HEIGHT * numberOfRenderedItems;
|
||||
};
|
||||
}
|
||||
|
||||
const getPartialArray = (array: any[], length = array.length) => {
|
||||
return array.slice(0, length);
|
||||
};
|
||||
|
||||
const renderItemFactory =
|
||||
({ onSuggestionClick }: Pick<SuggestionsProps, 'onSuggestionClick'>) =>
|
||||
(props: ListChildComponentProps) => {
|
||||
const renderItemFactory = ({ onSuggestionClick }: Pick<SuggestionsProps, 'onSuggestionClick'>) => (props: ListChildComponentProps) => {
|
||||
const { index, style, data } = props;
|
||||
|
||||
const suboption = data[index];
|
||||
@@ -69,35 +67,33 @@ const renderItemFactory =
|
||||
/>
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const Suggestions = (props: SuggestionsProps) => {
|
||||
const formatMessage = useFormatMessage();
|
||||
const { data, onSuggestionClick = () => null } = props;
|
||||
const {
|
||||
data,
|
||||
onSuggestionClick = () => null
|
||||
} = props;
|
||||
const [current, setCurrent] = React.useState<number | null>(0);
|
||||
const [listLength, setListLength] = React.useState<number>(SHORT_LIST_LENGTH);
|
||||
const [filteredData, setFilteredData] = React.useState<any[]>(data);
|
||||
const [filteredData, setFilteredData] = React.useState<any[]>(
|
||||
data
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
function syncOptions() {
|
||||
React.useEffect(function syncOptions() {
|
||||
setFilteredData((filteredData) => {
|
||||
if (filteredData.length === 0 && filteredData.length !== data.length) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return filteredData;
|
||||
});
|
||||
},
|
||||
[data]
|
||||
);
|
||||
})
|
||||
}, [data]);
|
||||
|
||||
const renderItem = React.useMemo(
|
||||
() =>
|
||||
renderItemFactory({
|
||||
onSuggestionClick,
|
||||
}),
|
||||
[onSuggestionClick]
|
||||
);
|
||||
const renderItem = React.useMemo(() => renderItemFactory({
|
||||
onSuggestionClick
|
||||
}), [onSuggestionClick]);
|
||||
|
||||
const expandList = () => {
|
||||
setListLength(Infinity);
|
||||
@@ -126,12 +122,12 @@ const Suggestions = (props: SuggestionsProps) => {
|
||||
return {
|
||||
id: stepWithOutput.id,
|
||||
name: stepWithOutput.name,
|
||||
output: stepWithOutput.output.filter((option) =>
|
||||
`${option.label}\n${option.sampleValue}`
|
||||
output: stepWithOutput.output
|
||||
.filter(option => `${option.label}\n${option.sampleValue}`
|
||||
.toLowerCase()
|
||||
.includes(search.toLowerCase())
|
||||
),
|
||||
};
|
||||
)
|
||||
}
|
||||
})
|
||||
.filter((stepWithOutput) => stepWithOutput.output.length);
|
||||
|
||||
@@ -165,27 +161,14 @@ const Suggestions = (props: SuggestionsProps) => {
|
||||
(current === index ? <ExpandLess /> : <ExpandMore />)}
|
||||
</ListItemButton>
|
||||
|
||||
<Collapse
|
||||
in={current === index || filteredData.length === 1}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
>
|
||||
<Collapse in={current === index || filteredData.length === 1} timeout="auto" unmountOnExit>
|
||||
<FixedSizeList
|
||||
height={computeListHeight(
|
||||
getPartialArray((option.output as any) || [], listLength)
|
||||
.length
|
||||
)}
|
||||
height={computeListHeight(getPartialArray((option.output as any) || [], listLength).length)}
|
||||
width="100%"
|
||||
itemSize={LIST_ITEM_HEIGHT}
|
||||
itemCount={
|
||||
getPartialArray((option.output as any) || [], listLength)
|
||||
.length
|
||||
}
|
||||
itemCount={getPartialArray((option.output as any) || [], listLength).length}
|
||||
overscanCount={2}
|
||||
itemData={getPartialArray(
|
||||
(option.output as any) || [],
|
||||
listLength
|
||||
)}
|
||||
itemData={getPartialArray((option.output as any) || [], listLength)}
|
||||
data-test="power-input-suggestion-group"
|
||||
>
|
||||
{renderItem}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { IStep } from 'types';
|
||||
import type { IStep } from '@automatisch/types';
|
||||
|
||||
const joinBy = (delimiter = '.', ...args: string[]) =>
|
||||
args.filter(Boolean).join(delimiter);
|
||||
@@ -10,12 +10,7 @@ type TProcessPayload = {
|
||||
parentLabel?: string;
|
||||
};
|
||||
|
||||
const process = ({
|
||||
data,
|
||||
parentKey,
|
||||
index,
|
||||
parentLabel = '',
|
||||
}: TProcessPayload): any[] => {
|
||||
const process = ({ data, parentKey, index, parentLabel = '' }: TProcessPayload): any[] => {
|
||||
if (typeof data !== 'object') {
|
||||
return [
|
||||
{
|
||||
@@ -29,19 +24,27 @@ const process = ({
|
||||
const entries = Object.entries(data);
|
||||
|
||||
return entries.flatMap(([name, sampleValue]) => {
|
||||
const label = joinBy('.', parentLabel, (index as number)?.toString(), name);
|
||||
const label = joinBy(
|
||||
'.',
|
||||
parentLabel,
|
||||
(index as number)?.toString(),
|
||||
name
|
||||
);
|
||||
|
||||
const value = joinBy('.', parentKey, (index as number)?.toString(), name);
|
||||
const value = joinBy(
|
||||
'.',
|
||||
parentKey,
|
||||
(index as number)?.toString(),
|
||||
name
|
||||
);
|
||||
|
||||
if (Array.isArray(sampleValue)) {
|
||||
return sampleValue.flatMap((item, index) =>
|
||||
process({
|
||||
return sampleValue.flatMap((item, index) => process({
|
||||
data: item,
|
||||
parentKey: value,
|
||||
index,
|
||||
parentLabel: label,
|
||||
})
|
||||
);
|
||||
parentLabel: label
|
||||
}));
|
||||
}
|
||||
|
||||
if (typeof sampleValue === 'object' && sampleValue !== null) {
|
||||
@@ -74,8 +77,7 @@ export const processStepWithExecutions = (steps: IStep[]): any[] => {
|
||||
.map((step: IStep, index: number) => ({
|
||||
id: step.id,
|
||||
// TODO: replace with step.name once introduced
|
||||
name: `${index + 1}. ${
|
||||
(step.appKey || '').charAt(0)?.toUpperCase() + step.appKey?.slice(1)
|
||||
name: `${index + 1}. ${(step.appKey || '').charAt(0)?.toUpperCase() + step.appKey?.slice(1)
|
||||
}`,
|
||||
output: process({
|
||||
data: step.executionSteps?.[0]?.dataOut || {},
|
||||
|
@@ -3,7 +3,7 @@ import throttle from 'lodash/throttle';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
import { IJSONObject } from 'types';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import JSONViewer from 'components/JSONViewer';
|
||||
import SearchInput from 'components/SearchInput';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Text, Descendant } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { ReactEditor, withReact } from 'slate-react';
|
||||
import { IFieldDropdownOption } from 'types';
|
||||
import { IFieldDropdownOption } from '@automatisch/types';
|
||||
|
||||
import type {
|
||||
CustomEditor,
|
||||
|
@@ -13,7 +13,7 @@ import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
||||
import JSONViewer from 'components/JSONViewer';
|
||||
import WebhookUrlInfo from 'components/WebhookUrlInfo';
|
||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||
import type { IStep, ISubstep } from 'types';
|
||||
import type { IStep, ISubstep } from '@automatisch/types';
|
||||
|
||||
type TestSubstepProps = {
|
||||
substep: ISubstep;
|
||||
@@ -62,7 +62,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
|
||||
EXECUTE_FLOW,
|
||||
{
|
||||
refetchQueries: ['GetStepWithTestExecutions'],
|
||||
context: { autoSnackbar: false },
|
||||
context: { autoSnackbar: false }
|
||||
}
|
||||
);
|
||||
const response = data?.executeFlow?.data;
|
||||
|
@@ -5,7 +5,7 @@ import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import * as React from 'react';
|
||||
|
||||
import { IJSONObject } from 'types';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||
import { defaultTheme, mationTheme } from 'styles/theme';
|
||||
|
@@ -11,7 +11,7 @@ import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { TBillingCardAction } from 'types';
|
||||
import { TBillingCardAction } from '@automatisch/types';
|
||||
import TrialOverAlert from 'components/TrialOverAlert/index.ee';
|
||||
import SubscriptionCancelledAlert from 'components/SubscriptionCancelledAlert/index.ee';
|
||||
import CheckoutCompletedAlert from 'components/CheckoutCompletedAlert/index.ee';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { IStep } from 'types';
|
||||
import type { IStep } from '@automatisch/types';
|
||||
|
||||
export const StepExecutionsContext = React.createContext<IStep[]>([]);
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { IAuthenticationStep, IJSONObject } from 'types';
|
||||
import type { IAuthenticationStep, IJSONObject } from '@automatisch/types';
|
||||
import apolloClient from 'graphql/client';
|
||||
import MUTATIONS from 'graphql/mutations';
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import template from 'lodash/template';
|
||||
import type { IAuthenticationStepField, IJSONObject } from 'types';
|
||||
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types';
|
||||
|
||||
const interpolate = /{([\s\S]+?)}/g;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { IRole, IPermission } from 'types';
|
||||
import { IRole, IPermission } from '@automatisch/types';
|
||||
|
||||
type ComputeAction = {
|
||||
conditions: Record<string, boolean>;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import template from 'lodash/template';
|
||||
import type { IAuthenticationStepField, IJSONObject } from 'types';
|
||||
import type { IAuthenticationStepField, IJSONObject } from '@automatisch/types';
|
||||
|
||||
const interpolate = /{([\s\S]+?)}/g;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { IJSONObject } from 'types';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import set from 'lodash/set';
|
||||
|
||||
export default function nestObject<T = IJSONObject>(
|
||||
|
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
PureAbility,
|
||||
fieldPatternMatcher,
|
||||
mongoQueryMatcher,
|
||||
} from '@casl/ability';
|
||||
import { IUser } from 'types';
|
||||
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
|
||||
import { IUser } from '@automatisch/types';
|
||||
|
||||
// Must be kept in sync with `packages/backend/src/helpers/user-ability.ts`!
|
||||
export default function userAbility(user: IUser) {
|
||||
@@ -13,7 +9,7 @@ export default function userAbility(user: IUser) {
|
||||
// We're not using mongo, but our fields, conditions match
|
||||
const options = {
|
||||
conditionsMatcher: mongoQueryMatcher,
|
||||
fieldMatcher: fieldPatternMatcher,
|
||||
fieldMatcher: fieldPatternMatcher
|
||||
};
|
||||
|
||||
if (!role || !permissions) {
|
||||
|
@@ -1,16 +1,22 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { IApp } from 'types';
|
||||
import { IApp } from '@automatisch/types';
|
||||
|
||||
import { GET_APP } from 'graphql/queries/get-app';
|
||||
|
||||
type QueryResponse = {
|
||||
getApp: IApp;
|
||||
};
|
||||
}
|
||||
|
||||
export default function useApp(key: string) {
|
||||
const { data, loading } = useQuery<QueryResponse>(GET_APP, {
|
||||
variables: { key },
|
||||
});
|
||||
const {
|
||||
data,
|
||||
loading
|
||||
} = useQuery<QueryResponse>(
|
||||
GET_APP,
|
||||
{
|
||||
variables: { key }
|
||||
}
|
||||
);
|
||||
const app = data?.getApp;
|
||||
|
||||
return {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import { AppAuthClient } from 'types';
|
||||
import { AppAuthClient } from '@automatisch/types';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GET_APP_AUTH_CLIENT } from 'graphql/queries/get-app-auth-client.ee';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import { AppAuthClient } from 'types';
|
||||
import { AppAuthClient } from '@automatisch/types';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GET_APP_AUTH_CLIENTS } from 'graphql/queries/get-app-auth-clients.ee';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user