Compare commits
34 Commits
test-docs-
...
AUT-860
Author | SHA1 | Date | |
---|---|---|---|
![]() |
aee0674d20 | ||
![]() |
fc4eeed764 | ||
![]() |
3596d13be1 | ||
![]() |
104d49ea1c | ||
![]() |
7057317446 | ||
![]() |
280575df88 | ||
![]() |
d2cb434b7b | ||
![]() |
2ecb802a2e | ||
![]() |
46e706c415 | ||
![]() |
3a57349d8a | ||
![]() |
754c3269ec | ||
![]() |
a079842408 | ||
![]() |
7664b58553 | ||
![]() |
de77488f7e | ||
![]() |
d808afd21b | ||
![]() |
b68aff76a1 | ||
![]() |
6da7fe158f | ||
![]() |
4dbc7fdc7d | ||
![]() |
ad1e1f7eca | ||
![]() |
9c3f7a3823 | ||
![]() |
86f4cb7701 | ||
![]() |
359a90245d | ||
![]() |
d8d7d86359 | ||
![]() |
7189b629c0 | ||
![]() |
55c9b5566c | ||
![]() |
ab671ccbf7 | ||
![]() |
316bda8c3f | ||
![]() |
76f77e8a4c | ||
![]() |
4a99d5eab7 | ||
![]() |
473d287c6d | ||
![]() |
bddd9896e4 | ||
![]() |
95eb115965 | ||
![]() |
ec87c7f21c | ||
![]() |
5c684cd499 |
2
.github/workflows/docs-change.yml
vendored
2
.github/workflows/docs-change.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
|||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const github = require('@actions/github');
|
|
||||||
const { context } = github;
|
|
||||||
const { pull_request } = context.payload;
|
const { pull_request } = context.payload;
|
||||||
|
|
||||||
const label = 'documentation-change';
|
const label = 'documentation-change';
|
||||||
|
@@ -31,6 +31,7 @@
|
|||||||
"accounting": "^0.4.1",
|
"accounting": "^0.4.1",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
"axios": "1.6.0",
|
"axios": "1.6.0",
|
||||||
|
"basic-ftp": "^5.0.5",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"bullmq": "^3.0.0",
|
"bullmq": "^3.0.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
1
packages/backend/src/apps/ftp/assets/favicon.svg
Normal file
1
packages/backend/src/apps/ftp/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 109.36" style="enable-background:new 0 0 122.88 109.36" xml:space="preserve"><g><path d="M14.69,16.44h3.56V8.63c0-1.09,0.88-1.97,1.97-1.97h3.5V1.97C23.72,0.88,24.6,0,25.69,0h31.92c1.09,0,1.97,0.88,1.97,1.97 v4.69h43.05c1.09,0,1.97,0.88,1.97,1.97v7.82h3.59c1.09,0,1.97,0.88,1.97,1.97c0,0.14-0.01,0.28-0.04,0.42l-6.88,50.59 c-0.22,1.65-0.95,3.18-2.05,4.29c-1.1,1.1-2.54,1.78-4.25,1.78H70.95v5.86h2.12c1.54,0,2.8,1.26,2.8,2.8v5.06h10.44 c1.25,0,2.32,0.83,2.67,1.97h26.1c3.73,0,6.14,2.07,7.2,4.71c0.4,1,0.6,2.08,0.6,3.14c0,1.06-0.2,2.14-0.6,3.14 c-1.06,2.64-3.47,4.71-7.2,4.71H89.09c-0.15,1.4-1.35,2.49-2.78,2.49H72.63H38.8c-1.44,0-2.63-1.1-2.78-2.49H7.8 c-3.73,0-6.14-2.07-7.2-4.71c-0.4-1-0.6-2.08-0.6-3.14c0-1.06,0.2-2.14,0.6-3.14c1.06-2.64,3.47-4.71,7.2-4.71h28.32 c0.36-1.14,1.42-1.97,2.67-1.97h8.74v-5.06c0-1.54,1.26-2.8,2.8-2.8h1.69v-5.86H24.38c-1.71,0-3.19-0.69-4.3-1.79 c-1.11-1.11-1.83-2.66-2.01-4.33l-5.33-50.74c-0.11-1.08,0.67-2.04,1.75-2.15C14.56,16.45,14.63,16.45,14.69,16.44L14.69,16.44 L14.69,16.44L14.69,16.44z M31.38,34.43h16.29v4.58H38v3.72h8.26v4.3H38v8.71h-6.61V34.43L31.38,34.43z M50.08,34.43H70.1v5.27 h-6.72v16.05h-6.59V39.7h-6.71V34.43L50.08,34.43z M73.41,34.43h10.95c2.38,0,4.17,0.57,5.35,1.7c1.19,1.14,1.78,2.75,1.78,4.84 c0,2.15-0.65,3.84-1.94,5.05c-1.3,1.21-3.27,1.82-5.93,1.82h-3.6v7.91h-6.61V34.43L73.41,34.43z M80.03,43.52h1.61 c1.27,0,2.16-0.22,2.68-0.66c0.51-0.44,0.77-1,0.77-1.69c0-0.67-0.22-1.24-0.67-1.7c-0.44-0.47-1.28-0.7-2.51-0.7h-1.88V43.52 L80.03,43.52z M56.38,81.34h10.21v-5.79H56.38V81.34L56.38,81.34z M89.11,94.88v8.27h25.97c1.97,0,3.22-1.04,3.76-2.37 c0.22-0.54,0.33-1.15,0.33-1.76s-0.11-1.22-0.33-1.76c-0.54-1.33-1.79-2.37-3.76-2.37H89.11L89.11,94.88z M36,103.15v-8.27H7.8 c-1.97,0-3.22,1.04-3.76,2.37c-0.22,0.54-0.33,1.15-0.33,1.76s0.11,1.22,0.33,1.76c0.54,1.33,1.79,2.37,3.76,2.37H36L36,103.15z M20.23,20.38h-3.35l5.1,48.57c0.08,0.78,0.39,1.47,0.87,1.95c0.4,0.4,0.92,0.64,1.53,0.64h72.54c0.59,0,1.1-0.24,1.48-0.62 c0.49-0.49,0.82-1.22,0.93-2.03l6.6-48.51L20.23,20.38L20.23,20.38L20.23,20.38z M22.19,10.6v5.83l78.46-0.83v-5H57.61 c-1.09,0-1.97-0.88-1.97-1.97V3.94H27.66v4.69c0,1.09-0.88,1.97-1.97,1.97L22.19,10.6L22.19,10.6L22.19,10.6z"/></g></svg>
|
After Width: | Height: | Size: 2.3 KiB |
91
packages/backend/src/apps/ftp/auth/index.js
Normal file
91
packages/backend/src/apps/ftp/auth/index.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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: 'host',
|
||||||
|
label: 'Host',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'The host information Automatisch will connect to.',
|
||||||
|
docUrl: 'https://automatisch.io/docs/ftp#host',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'username',
|
||||||
|
label: 'Email/Username',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'Your FTP login credentials.',
|
||||||
|
docUrl: 'https://automatisch.io/docs/ftp#username',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
docUrl: 'https://automatisch.io/docs/ftp#password',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'secure',
|
||||||
|
label: 'Secure',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
readOnly: false,
|
||||||
|
value: false,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
docUrl: 'https://automatisch.io/docs/ftp#secure',
|
||||||
|
clickToCopy: false,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'port',
|
||||||
|
label: 'Port',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
readOnly: false,
|
||||||
|
value: '21',
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
docUrl: 'https://automatisch.io/docs/ftp#port',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
8
packages/backend/src/apps/ftp/auth/is-still-verified.js
Normal file
8
packages/backend/src/apps/ftp/auth/is-still-verified.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
19
packages/backend/src/apps/ftp/auth/verify-credentials.js
Normal file
19
packages/backend/src/apps/ftp/auth/verify-credentials.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Client } from 'basic-ftp';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
const client = new Client();
|
||||||
|
client.ftp.verbose = true;
|
||||||
|
|
||||||
|
await client.access({
|
||||||
|
host: $.auth.data.host,
|
||||||
|
user: $.auth.data.username,
|
||||||
|
password: $.auth.data.password,
|
||||||
|
secure: $.auth.data.secure,
|
||||||
|
});
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName: $.auth.data.screenName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
14
packages/backend/src/apps/ftp/index.js
Normal file
14
packages/backend/src/apps/ftp/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'FTP',
|
||||||
|
key: 'ftp',
|
||||||
|
iconUrl: '{BASE_URL}/apps/ftp/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/ftp/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
primaryColor: '000000',
|
||||||
|
auth,
|
||||||
|
});
|
@@ -14,24 +14,55 @@ export default defineAction({
|
|||||||
value: '200',
|
value: '200',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'JSON body',
|
label: 'Headers',
|
||||||
key: 'stringifiedJsonBody',
|
key: 'headers',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: 'Add or remove headers as needed',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Key',
|
||||||
|
key: 'key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'Header key',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
key: 'value',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'Header value',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Body',
|
||||||
|
key: 'body',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The content of the JSON body. It must be a valid JSON.',
|
description: 'The content of the response body.',
|
||||||
variables: true,
|
variables: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
async run($) {
|
async run($) {
|
||||||
const parsedStatusCode = parseInt($.step.parameters.statusCode, 10);
|
const statusCode = parseInt($.step.parameters.statusCode, 10);
|
||||||
const stringifiedJsonBody = $.step.parameters.stringifiedJsonBody;
|
const body = $.step.parameters.body;
|
||||||
const parsedJsonBody = JSON.parse(stringifiedJsonBody);
|
const headers = $.step.parameters.headers.reduce((result, entry) => {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
[entry.key]: entry.value,
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
$.setActionItem({
|
$.setActionItem({
|
||||||
raw: {
|
raw: {
|
||||||
body: parsedJsonBody,
|
headers,
|
||||||
statusCode: parsedStatusCode,
|
body,
|
||||||
|
statusCode,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -4,7 +4,7 @@ import Crypto from 'crypto';
|
|||||||
import app from '../../../../../app.js';
|
import app from '../../../../../app.js';
|
||||||
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
|
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
|
||||||
import { createUser } from '../../../../../../test/factories/user.js';
|
import { createUser } from '../../../../../../test/factories/user.js';
|
||||||
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
|
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/app-auth-clients/get-app-auth-client.js';
|
||||||
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
|
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
|
||||||
import { createRole } from '../../../../../../test/factories/role.js';
|
import { createRole } from '../../../../../../test/factories/role.js';
|
||||||
import * as license from '../../../../../helpers/license.ee.js';
|
import * as license from '../../../../../helpers/license.ee.js';
|
@@ -0,0 +1,11 @@
|
|||||||
|
import { renderObject } from '../../../../../helpers/renderer.js';
|
||||||
|
import AppAuthClient from '../../../../../models/app-auth-client.js';
|
||||||
|
|
||||||
|
export default async (request, response) => {
|
||||||
|
const appAuthClients = await AppAuthClient.query().orderBy(
|
||||||
|
'created_at',
|
||||||
|
'desc'
|
||||||
|
);
|
||||||
|
|
||||||
|
renderObject(response, appAuthClients);
|
||||||
|
};
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import app from '../../../../../app.js';
|
||||||
|
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
|
||||||
|
import { createUser } from '../../../../../../test/factories/user.js';
|
||||||
|
import getAdminAppAuthClientsMock from '../../../../../../test/mocks/rest/api/v1/admin/app-auth-clients/get-app-auth-clients.js';
|
||||||
|
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
|
||||||
|
import { createRole } from '../../../../../../test/factories/role.js';
|
||||||
|
import * as license from '../../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
describe('GET /api/v1/admin/app-auth-clients', () => {
|
||||||
|
let currentUser, currentUserRole, token;
|
||||||
|
|
||||||
|
describe('with valid license key', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
|
currentUserRole = await createRole({ key: 'admin' });
|
||||||
|
currentUser = await createUser({ roleId: currentUserRole.id });
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return app auth clients', async () => {
|
||||||
|
const appAuthClientOne = await createAppAuthClient();
|
||||||
|
const appAuthClientTwo = await createAppAuthClient();
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/v1/admin/app-auth-clients')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = getAdminAppAuthClientsMock([
|
||||||
|
appAuthClientTwo,
|
||||||
|
appAuthClientOne,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -4,7 +4,7 @@ import Crypto from 'crypto';
|
|||||||
import app from '../../../../app.js';
|
import app from '../../../../app.js';
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
|
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
|
||||||
import { createUser } from '../../../../../test/factories/user.js';
|
import { createUser } from '../../../../../test/factories/user.js';
|
||||||
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
|
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/app-auth-clients/get-app-auth-client.js';
|
||||||
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
|
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
|
||||||
import * as license from '../../../../helpers/license.ee.js';
|
import * as license from '../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { renderObject } from '../../../../helpers/renderer.js';
|
||||||
|
import AppAuthClient from '../../../../models/app-auth-client.js';
|
||||||
|
|
||||||
|
export default async (request, response) => {
|
||||||
|
const appAuthClients = await AppAuthClient.query()
|
||||||
|
.where({ active: true })
|
||||||
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
renderObject(response, appAuthClients);
|
||||||
|
};
|
@@ -0,0 +1,37 @@
|
|||||||
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import app from '../../../../app.js';
|
||||||
|
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
|
||||||
|
import { createUser } from '../../../../../test/factories/user.js';
|
||||||
|
import getAppAuthClientsMock from '../../../../../test/mocks/rest/api/v1/app-auth-clients/get-app-auth-clients.js';
|
||||||
|
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
|
||||||
|
import * as license from '../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
describe('GET /api/v1/app-auth-clients', () => {
|
||||||
|
let currentUser, token;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
|
currentUser = await createUser();
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return specified app auth client info', async () => {
|
||||||
|
const appAuthClientOne = await createAppAuthClient();
|
||||||
|
const appAuthClientTwo = await createAppAuthClient();
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/v1/app-auth-clients')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = getAppAuthClientsMock([
|
||||||
|
appAuthClientTwo,
|
||||||
|
appAuthClientOne,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
});
|
@@ -42,9 +42,12 @@ describe('GET /api/v1/executions', () => {
|
|||||||
|
|
||||||
const currentUserExecutionTwo = await createExecution({
|
const currentUserExecutionTwo = await createExecution({
|
||||||
flowId: currentUserFlow.id,
|
flowId: currentUserFlow.id,
|
||||||
deletedAt: new Date().toISOString(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await currentUserExecutionTwo
|
||||||
|
.$query()
|
||||||
|
.patchAndFetch({ deletedAt: new Date().toISOString() });
|
||||||
|
|
||||||
await createPermission({
|
await createPermission({
|
||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Execution',
|
subject: 'Execution',
|
||||||
@@ -87,9 +90,12 @@ describe('GET /api/v1/executions', () => {
|
|||||||
|
|
||||||
const anotherUserExecutionTwo = await createExecution({
|
const anotherUserExecutionTwo = await createExecution({
|
||||||
flowId: anotherUserFlow.id,
|
flowId: anotherUserFlow.id,
|
||||||
deletedAt: new Date().toISOString(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await anotherUserExecutionTwo
|
||||||
|
.$query()
|
||||||
|
.patchAndFetch({ deletedAt: new Date().toISOString() });
|
||||||
|
|
||||||
await createPermission({
|
await createPermission({
|
||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Execution',
|
subject: 'Execution',
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { renderObject } from '../../../../helpers/renderer.js';
|
||||||
|
|
||||||
|
export default async (request, response) => {
|
||||||
|
const step = await request.currentUser.authorizedSteps
|
||||||
|
.clone()
|
||||||
|
.where('steps.id', request.params.stepId)
|
||||||
|
.whereNotNull('steps.app_key')
|
||||||
|
.first()
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const dynamicFields = await step.createDynamicFields(
|
||||||
|
request.body.dynamicFieldsKey,
|
||||||
|
request.body.parameters
|
||||||
|
);
|
||||||
|
|
||||||
|
renderObject(response, dynamicFields);
|
||||||
|
};
|
@@ -0,0 +1,169 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import Crypto from 'crypto';
|
||||||
|
import app from '../../../../app.js';
|
||||||
|
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
||||||
|
import { createUser } from '../../../../../test/factories/user';
|
||||||
|
import { createFlow } from '../../../../../test/factories/flow';
|
||||||
|
import { createStep } from '../../../../../test/factories/step';
|
||||||
|
import { createPermission } from '../../../../../test/factories/permission';
|
||||||
|
import createDynamicFieldsMock from '../../../../../test/mocks/rest/api/v1/steps/create-dynamic-fields';
|
||||||
|
|
||||||
|
describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
|
||||||
|
let currentUser, currentUserRole, token;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
currentUser = await createUser();
|
||||||
|
currentUserRole = await currentUser.$relatedQuery('role');
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return dynamically created fields of the current users step', async () => {
|
||||||
|
const currentUserflow = await createFlow({ userId: currentUser.id });
|
||||||
|
|
||||||
|
const actionStep = await createStep({
|
||||||
|
flowId: currentUserflow.id,
|
||||||
|
type: 'action',
|
||||||
|
appKey: 'slack',
|
||||||
|
key: 'sendMessageToChannel',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/v1/steps/${actionStep.id}/dynamic-fields`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({
|
||||||
|
dynamicFieldsKey: 'listFieldsAfterSendAsBot',
|
||||||
|
parameters: {
|
||||||
|
sendAsBot: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = await createDynamicFieldsMock();
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return dynamically created fields of the another users step', async () => {
|
||||||
|
const anotherUser = await createUser();
|
||||||
|
const anotherUserflow = await createFlow({ userId: anotherUser.id });
|
||||||
|
|
||||||
|
const actionStep = await createStep({
|
||||||
|
flowId: anotherUserflow.id,
|
||||||
|
type: 'action',
|
||||||
|
appKey: 'slack',
|
||||||
|
key: 'sendMessageToChannel',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/v1/steps/${actionStep.id}/dynamic-fields`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({
|
||||||
|
dynamicFieldsKey: 'listFieldsAfterSendAsBot',
|
||||||
|
parameters: {
|
||||||
|
sendAsBot: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = await createDynamicFieldsMock();
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return not found response for not existing step UUID', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const notExistingStepUUID = Crypto.randomUUID();
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.get(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return not found response for existing step UUID without app key', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const step = await createStep({ appKey: null });
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.get(`/api/v1/steps/${step.id}/dynamic-fields`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return bad request response for invalid UUID', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.post('/api/v1/steps/invalidStepUUID/dynamic-fields')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,27 @@
|
|||||||
|
import { ref } from 'objection';
|
||||||
|
import ExecutionStep from '../../../../models/execution-step.js';
|
||||||
|
import { renderObject } from '../../../../helpers/renderer.js';
|
||||||
|
|
||||||
|
export default async (request, response) => {
|
||||||
|
const step = await request.currentUser.authorizedSteps
|
||||||
|
.clone()
|
||||||
|
.findOne({ 'steps.id': request.params.stepId })
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const previousSteps = await request.currentUser.authorizedSteps
|
||||||
|
.clone()
|
||||||
|
.withGraphJoined('executionSteps')
|
||||||
|
.where('flow_id', '=', step.flowId)
|
||||||
|
.andWhere('position', '<', step.position)
|
||||||
|
.andWhere(
|
||||||
|
'executionSteps.created_at',
|
||||||
|
'=',
|
||||||
|
ExecutionStep.query()
|
||||||
|
.max('created_at')
|
||||||
|
.where('step_id', '=', ref('steps.id'))
|
||||||
|
.andWhere('status', 'success')
|
||||||
|
)
|
||||||
|
.orderBy('steps.position', 'asc');
|
||||||
|
|
||||||
|
renderObject(response, previousSteps);
|
||||||
|
};
|
@@ -0,0 +1,173 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import Crypto from 'crypto';
|
||||||
|
import app from '../../../../app.js';
|
||||||
|
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
||||||
|
import { createUser } from '../../../../../test/factories/user';
|
||||||
|
import { createFlow } from '../../../../../test/factories/flow';
|
||||||
|
import { createStep } from '../../../../../test/factories/step';
|
||||||
|
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
|
||||||
|
import { createPermission } from '../../../../../test/factories/permission';
|
||||||
|
import getPreviousStepsMock from '../../../../../test/mocks/rest/api/v1/steps/get-previous-steps';
|
||||||
|
|
||||||
|
describe('GET /api/v1/steps/:stepId/previous-steps', () => {
|
||||||
|
let currentUser, currentUserRole, token;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
currentUser = await createUser();
|
||||||
|
currentUserRole = await currentUser.$relatedQuery('role');
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the previous steps of the specified step of the current user', async () => {
|
||||||
|
const currentUserflow = await createFlow({ userId: currentUser.id });
|
||||||
|
|
||||||
|
const triggerStep = await createStep({
|
||||||
|
flowId: currentUserflow.id,
|
||||||
|
type: 'trigger',
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStepOne = await createStep({
|
||||||
|
flowId: currentUserflow.id,
|
||||||
|
type: 'action',
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStepTwo = await createStep({
|
||||||
|
flowId: currentUserflow.id,
|
||||||
|
type: 'action',
|
||||||
|
});
|
||||||
|
|
||||||
|
const executionStepOne = await createExecutionStep({
|
||||||
|
stepId: triggerStep.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const executionStepTwo = await createExecutionStep({
|
||||||
|
stepId: actionStepOne.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/v1/steps/${actionStepTwo.id}/previous-steps`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = await getPreviousStepsMock(
|
||||||
|
[triggerStep, actionStepOne],
|
||||||
|
[executionStepOne, executionStepTwo]
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the previous steps of the specified step of another user', async () => {
|
||||||
|
const anotherUser = await createUser();
|
||||||
|
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
||||||
|
|
||||||
|
const triggerStep = await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
type: 'trigger',
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStepOne = await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
type: 'action',
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionStepTwo = await createStep({
|
||||||
|
flowId: anotherUserFlow.id,
|
||||||
|
type: 'action',
|
||||||
|
});
|
||||||
|
|
||||||
|
const executionStepOne = await createExecutionStep({
|
||||||
|
stepId: triggerStep.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const executionStepTwo = await createExecutionStep({
|
||||||
|
stepId: actionStepOne.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/v1/steps/${actionStepTwo.id}/previous-steps`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = await getPreviousStepsMock(
|
||||||
|
[triggerStep, actionStepOne],
|
||||||
|
[executionStepOne, executionStepTwo]
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return not found response for not existing step UUID', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const notExistingFlowUUID = Crypto.randomUUID();
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.get(`/api/v1/steps/${notExistingFlowUUID}/previous-steps`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return bad request response for invalid UUID', async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Flow',
|
||||||
|
roleId: currentUserRole.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.get('/api/v1/steps/invalidFlowUUID/previous-steps')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
|
});
|
@@ -26,6 +26,4 @@ export default async (request, response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await handlerSync(flowId, request, response);
|
await handlerSync(flowId, request, response);
|
||||||
|
|
||||||
response.sendStatus(204);
|
|
||||||
};
|
};
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
import appConfig from '../../config/app.js';
|
|
||||||
import { hasValidLicense } from '../../helpers/license.ee.js';
|
|
||||||
import Config from '../../models/config.js';
|
|
||||||
|
|
||||||
const getConfig = async (_parent, params) => {
|
|
||||||
if (!(await hasValidLicense())) return {};
|
|
||||||
|
|
||||||
const defaultConfig = {
|
|
||||||
disableNotificationsPage: appConfig.disableNotificationsPage,
|
|
||||||
disableFavicon: appConfig.disableFavicon,
|
|
||||||
additionalDrawerLink: appConfig.additionalDrawerLink,
|
|
||||||
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
|
|
||||||
};
|
|
||||||
|
|
||||||
const configQuery = Config.query();
|
|
||||||
|
|
||||||
if (Array.isArray(params.keys)) {
|
|
||||||
configQuery.whereIn('key', params.keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await configQuery.orderBy('key', 'asc');
|
|
||||||
|
|
||||||
return config.reduce((computedConfig, configEntry) => {
|
|
||||||
const { key, value } = configEntry;
|
|
||||||
|
|
||||||
computedConfig[key] = value?.data;
|
|
||||||
|
|
||||||
return computedConfig;
|
|
||||||
}, defaultConfig);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getConfig;
|
|
@@ -1,140 +0,0 @@
|
|||||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
|
||||||
import { createConfig } from '../../../test/factories/config';
|
|
||||||
import appConfig from '../../config/app';
|
|
||||||
import * as license from '../../helpers/license.ee';
|
|
||||||
|
|
||||||
describe('graphQL getConfig query', () => {
|
|
||||||
let configOne, configTwo, configThree, query;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
configOne = await createConfig({ key: 'configOne' });
|
|
||||||
configTwo = await createConfig({ key: 'configTwo' });
|
|
||||||
configThree = await createConfig({ key: 'configThree' });
|
|
||||||
|
|
||||||
query = `
|
|
||||||
query {
|
|
||||||
getConfig
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and without valid license', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and correct permissions', () => {
|
|
||||||
it('should return empty config data', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = { data: { getConfig: {} } };
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with valid license', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and without providing specific keys', () => {
|
|
||||||
it('should return all config data', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getConfig: {
|
|
||||||
[configOne.key]: configOne.value.data,
|
|
||||||
[configTwo.key]: configTwo.value.data,
|
|
||||||
[configThree.key]: configThree.value.data,
|
|
||||||
disableNotificationsPage: false,
|
|
||||||
disableFavicon: false,
|
|
||||||
additionalDrawerLink: undefined,
|
|
||||||
additionalDrawerLinkText: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with providing specific keys', () => {
|
|
||||||
it('should return all config data', async () => {
|
|
||||||
query = `
|
|
||||||
query {
|
|
||||||
getConfig(keys: ["configOne", "configTwo"])
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getConfig: {
|
|
||||||
[configOne.key]: configOne.value.data,
|
|
||||||
[configTwo.key]: configTwo.value.data,
|
|
||||||
disableNotificationsPage: false,
|
|
||||||
disableFavicon: false,
|
|
||||||
additionalDrawerLink: undefined,
|
|
||||||
additionalDrawerLinkText: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with different defaults', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
|
|
||||||
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue(
|
|
||||||
'https://automatisch.io'
|
|
||||||
);
|
|
||||||
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue(
|
|
||||||
'Automatisch'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return custom config', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getConfig: {
|
|
||||||
[configOne.key]: configOne.value.data,
|
|
||||||
[configTwo.key]: configTwo.value.data,
|
|
||||||
[configThree.key]: configThree.value.data,
|
|
||||||
disableNotificationsPage: true,
|
|
||||||
disableFavicon: true,
|
|
||||||
additionalDrawerLink: 'https://automatisch.io',
|
|
||||||
additionalDrawerLinkText: 'Automatisch',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -136,7 +136,7 @@ describe('graphQL getFlow query', () => {
|
|||||||
id: actionStep.id,
|
id: actionStep.id,
|
||||||
key: 'translateText',
|
key: 'translateText',
|
||||||
parameters: {},
|
parameters: {},
|
||||||
position: 1,
|
position: 2,
|
||||||
status: actionStep.status,
|
status: actionStep.status,
|
||||||
type: 'action',
|
type: 'action',
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
webhookUrl: 'http://localhost:3000/null',
|
||||||
@@ -223,7 +223,7 @@ describe('graphQL getFlow query', () => {
|
|||||||
id: actionStep.id,
|
id: actionStep.id,
|
||||||
key: 'translateText',
|
key: 'translateText',
|
||||||
parameters: {},
|
parameters: {},
|
||||||
position: 1,
|
position: 2,
|
||||||
status: actionStep.status,
|
status: actionStep.status,
|
||||||
type: 'action',
|
type: 'action',
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
webhookUrl: 'http://localhost:3000/null',
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
import Flow from '../../models/flow.js';
|
|
||||||
import paginate from '../../helpers/pagination.js';
|
|
||||||
|
|
||||||
const getFlows = async (_parent, params, context) => {
|
|
||||||
const conditions = context.currentUser.can('read', 'Flow');
|
|
||||||
const userFlows = context.currentUser.$relatedQuery('flows');
|
|
||||||
const allFlows = Flow.query();
|
|
||||||
const baseQuery = conditions.isCreator ? userFlows : allFlows;
|
|
||||||
|
|
||||||
const flowsQuery = baseQuery
|
|
||||||
.clone()
|
|
||||||
.joinRelated({
|
|
||||||
steps: true,
|
|
||||||
})
|
|
||||||
.withGraphFetched({
|
|
||||||
steps: {
|
|
||||||
connection: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.where((builder) => {
|
|
||||||
if (params.connectionId) {
|
|
||||||
builder.where('steps.connection_id', params.connectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.name) {
|
|
||||||
builder.where('flows.name', 'ilike', `%${params.name}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.appKey) {
|
|
||||||
builder.where('steps.app_key', params.appKey);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.groupBy('flows.id')
|
|
||||||
.orderBy('active', 'desc')
|
|
||||||
.orderBy('updated_at', 'desc');
|
|
||||||
|
|
||||||
return paginate(flowsQuery, params.limit, params.offset);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getFlows;
|
|
@@ -1,16 +0,0 @@
|
|||||||
import axios from '../../helpers/axios-with-proxy.js';
|
|
||||||
|
|
||||||
const NOTIFICATIONS_URL =
|
|
||||||
'https://notifications.automatisch.io/notifications.json';
|
|
||||||
|
|
||||||
const getNotifications = async () => {
|
|
||||||
try {
|
|
||||||
const { data: notifications = [] } = await axios.get(NOTIFICATIONS_URL);
|
|
||||||
|
|
||||||
return notifications;
|
|
||||||
} catch (err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getNotifications;
|
|
@@ -1,19 +0,0 @@
|
|||||||
import paginate from '../../helpers/pagination.js';
|
|
||||||
import User from '../../models/user.js';
|
|
||||||
|
|
||||||
const getUsers = async (_parent, params, context) => {
|
|
||||||
context.currentUser.can('read', 'User');
|
|
||||||
|
|
||||||
const usersQuery = User.query()
|
|
||||||
.leftJoinRelated({
|
|
||||||
role: true,
|
|
||||||
})
|
|
||||||
.withGraphFetched({
|
|
||||||
role: true,
|
|
||||||
})
|
|
||||||
.orderBy('full_name', 'asc');
|
|
||||||
|
|
||||||
return paginate(usersQuery, params.limit, params.offset);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getUsers;
|
|
@@ -1,148 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createRole } from '../../../test/factories/role';
|
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
|
||||||
import { createUser } from '../../../test/factories/user';
|
|
||||||
|
|
||||||
describe('graphQL getUsers query', () => {
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getUsers(limit: 10, offset: 0) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
totalCount
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
fullName
|
|
||||||
email
|
|
||||||
role {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
describe('and without permissions', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const userWithoutPermissions = await createUser();
|
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with correct permissions', () => {
|
|
||||||
let role, currentUser, anotherUser, token, requestObject;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
role = await createRole({
|
|
||||||
key: 'sample',
|
|
||||||
name: 'sample',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'User',
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
fullName: 'Current User',
|
|
||||||
});
|
|
||||||
|
|
||||||
anotherUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
fullName: 'Another User',
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
requestObject = request(app).post('/graphql').set('Authorization', token);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return users data', async () => {
|
|
||||||
const response = await requestObject.send({ query }).expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getUsers: {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
email: anotherUser.email,
|
|
||||||
fullName: anotherUser.fullName,
|
|
||||||
id: anotherUser.id,
|
|
||||||
role: {
|
|
||||||
id: role.id,
|
|
||||||
name: role.name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
email: currentUser.email,
|
|
||||||
fullName: currentUser.fullName,
|
|
||||||
id: currentUser.id,
|
|
||||||
role: {
|
|
||||||
id: role.id,
|
|
||||||
name: role.name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
pageInfo: {
|
|
||||||
currentPage: 1,
|
|
||||||
totalPages: 1,
|
|
||||||
},
|
|
||||||
totalCount: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return users data with password', async () => {
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getUsers(limit: 10, offset: 0) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
totalCount
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
fullName
|
|
||||||
password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await requestObject.send({ query }).expect(400);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual(
|
|
||||||
'Cannot query field "password" on type "User".'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -2,15 +2,11 @@ import getApp from './queries/get-app.js';
|
|||||||
import getAppAuthClient from './queries/get-app-auth-client.ee.js';
|
import getAppAuthClient from './queries/get-app-auth-client.ee.js';
|
||||||
import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
|
import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
|
||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
||||||
import getConfig from './queries/get-config.ee.js';
|
|
||||||
import getConnectedApps from './queries/get-connected-apps.js';
|
import getConnectedApps from './queries/get-connected-apps.js';
|
||||||
import getDynamicData from './queries/get-dynamic-data.js';
|
import getDynamicData from './queries/get-dynamic-data.js';
|
||||||
import getDynamicFields from './queries/get-dynamic-fields.js';
|
import getDynamicFields from './queries/get-dynamic-fields.js';
|
||||||
import getFlow from './queries/get-flow.js';
|
import getFlow from './queries/get-flow.js';
|
||||||
import getFlows from './queries/get-flows.js';
|
|
||||||
import getNotifications from './queries/get-notifications.js';
|
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
|
||||||
import getUsers from './queries/get-users.js';
|
|
||||||
import testConnection from './queries/test-connection.js';
|
import testConnection from './queries/test-connection.js';
|
||||||
|
|
||||||
const queryResolvers = {
|
const queryResolvers = {
|
||||||
@@ -18,15 +14,11 @@ const queryResolvers = {
|
|||||||
getAppAuthClient,
|
getAppAuthClient,
|
||||||
getAppAuthClients,
|
getAppAuthClients,
|
||||||
getBillingAndUsage,
|
getBillingAndUsage,
|
||||||
getConfig,
|
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
getDynamicData,
|
getDynamicData,
|
||||||
getDynamicFields,
|
getDynamicFields,
|
||||||
getFlow,
|
getFlow,
|
||||||
getFlows,
|
|
||||||
getNotifications,
|
|
||||||
getStepWithTestExecutions,
|
getStepWithTestExecutions,
|
||||||
getUsers,
|
|
||||||
testConnection,
|
testConnection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -5,13 +5,6 @@ type Query {
|
|||||||
getConnectedApps(name: String): [App]
|
getConnectedApps(name: String): [App]
|
||||||
testConnection(id: String!): Connection
|
testConnection(id: String!): Connection
|
||||||
getFlow(id: String!): Flow
|
getFlow(id: String!): Flow
|
||||||
getFlows(
|
|
||||||
limit: Int!
|
|
||||||
offset: Int!
|
|
||||||
appKey: String
|
|
||||||
connectionId: String
|
|
||||||
name: String
|
|
||||||
): FlowConnection
|
|
||||||
getStepWithTestExecutions(stepId: String!): [Step]
|
getStepWithTestExecutions(stepId: String!): [Step]
|
||||||
getDynamicData(
|
getDynamicData(
|
||||||
stepId: String!
|
stepId: String!
|
||||||
@@ -24,9 +17,6 @@ type Query {
|
|||||||
parameters: JSONObject
|
parameters: JSONObject
|
||||||
): [SubstepArgument]
|
): [SubstepArgument]
|
||||||
getBillingAndUsage: GetBillingAndUsage
|
getBillingAndUsage: GetBillingAndUsage
|
||||||
getConfig(keys: [String]): JSONObject
|
|
||||||
getNotifications: [Notification]
|
|
||||||
getUsers(limit: Int!, offset: Int!): UserConnection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
@@ -257,15 +247,6 @@ type Field {
|
|||||||
options: [SubstepArgumentOption]
|
options: [SubstepArgumentOption]
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlowConnection {
|
|
||||||
edges: [FlowEdge]
|
|
||||||
pageInfo: PageInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type FlowEdge {
|
|
||||||
node: Flow
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FlowStatus {
|
enum FlowStatus {
|
||||||
paused
|
paused
|
||||||
published
|
published
|
||||||
@@ -304,16 +285,6 @@ type SamlAuthProvidersRoleMapping {
|
|||||||
remoteRoleName: String
|
remoteRoleName: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserConnection {
|
|
||||||
edges: [UserEdge]
|
|
||||||
pageInfo: PageInfo
|
|
||||||
totalCount: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserEdge {
|
|
||||||
node: User
|
|
||||||
}
|
|
||||||
|
|
||||||
input CreateConnectionInput {
|
input CreateConnectionInput {
|
||||||
key: String!
|
key: String!
|
||||||
appAuthClientId: String
|
appAuthClientId: String
|
||||||
@@ -692,13 +663,6 @@ input UpdateAppAuthClientInput {
|
|||||||
active: Boolean
|
active: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification {
|
|
||||||
name: String
|
|
||||||
createdAt: String
|
|
||||||
documentationUrl: String
|
|
||||||
description: String
|
|
||||||
}
|
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
|
@@ -42,8 +42,6 @@ const isAuthenticatedRule = rule()(isAuthenticated);
|
|||||||
export const authenticationRules = {
|
export const authenticationRules = {
|
||||||
Query: {
|
Query: {
|
||||||
'*': isAuthenticatedRule,
|
'*': isAuthenticatedRule,
|
||||||
getConfig: allow,
|
|
||||||
getNotifications: allow,
|
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': isAuthenticatedRule,
|
'*': isAuthenticatedRule,
|
||||||
|
@@ -19,6 +19,14 @@ const authorizationList = {
|
|||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Flow',
|
subject: 'Flow',
|
||||||
},
|
},
|
||||||
|
'GET /api/v1/steps/:stepId/previous-steps': {
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
},
|
||||||
|
'POST /api/v1/steps/:stepId/dynamic-fields': {
|
||||||
|
action: 'update',
|
||||||
|
subject: 'Flow',
|
||||||
|
},
|
||||||
'GET /api/v1/connections/:connectionId/flows': {
|
'GET /api/v1/connections/:connectionId/flows': {
|
||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Flow',
|
subject: 'Flow',
|
||||||
|
@@ -75,9 +75,20 @@ export default async (flowId, request, response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (actionStep.key === 'respondWith' && !response.headersSent) {
|
if (actionStep.key === 'respondWith' && !response.headersSent) {
|
||||||
|
const { headers, statusCode, body } = actionExecutionStep.dataOut;
|
||||||
|
|
||||||
|
// we set the custom response headers
|
||||||
|
if (headers) {
|
||||||
|
for (const [key, value] of Object.entries(headers)) {
|
||||||
|
if (key) {
|
||||||
|
response.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we send the response only if it's not sent yet. This allows us to early respond from the flow.
|
// we send the response only if it's not sent yet. This allows us to early respond from the flow.
|
||||||
response.status(actionExecutionStep.dataOut.statusCode);
|
response.status(statusCode);
|
||||||
response.send(actionExecutionStep.dataOut.body);
|
response.send(body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import Connection from './connection.js';
|
|||||||
import ExecutionStep from './execution-step.js';
|
import ExecutionStep from './execution-step.js';
|
||||||
import Telemetry from '../helpers/telemetry/index.js';
|
import Telemetry from '../helpers/telemetry/index.js';
|
||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
|
import globalVariable from '../helpers/global-variable.js';
|
||||||
|
|
||||||
class Step extends Base {
|
class Step extends Base {
|
||||||
static tableName = 'steps';
|
static tableName = 'steps';
|
||||||
@@ -196,6 +197,26 @@ class Step extends Base {
|
|||||||
return existingArguments;
|
return existingArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createDynamicFields(dynamicFieldsKey, parameters) {
|
||||||
|
const connection = await this.$relatedQuery('connection');
|
||||||
|
const flow = await this.$relatedQuery('flow');
|
||||||
|
const app = await this.getApp();
|
||||||
|
const $ = await globalVariable({ connection, app, flow, step: this });
|
||||||
|
|
||||||
|
const command = app.dynamicFields.find(
|
||||||
|
(data) => data.key === dynamicFieldsKey
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const parameterKey in parameters) {
|
||||||
|
const parameterValue = parameters[parameterKey];
|
||||||
|
$.step.parameters[parameterKey] = parameterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicFields = (await command.run($)) || [];
|
||||||
|
|
||||||
|
return dynamicFields;
|
||||||
|
}
|
||||||
|
|
||||||
async updateWebhookUrl() {
|
async updateWebhookUrl() {
|
||||||
if (this.isAction) return this;
|
if (this.isAction) return this;
|
||||||
|
|
||||||
|
@@ -3,16 +3,25 @@ import asyncHandler from 'express-async-handler';
|
|||||||
import { authenticateUser } from '../../../../helpers/authentication.js';
|
import { authenticateUser } from '../../../../helpers/authentication.js';
|
||||||
import { authorizeAdmin } from '../../../../helpers/authorization.js';
|
import { authorizeAdmin } from '../../../../helpers/authorization.js';
|
||||||
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
|
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
|
||||||
import getAdminAppAuthClientsAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-client.js';
|
import getAdminAppAuthClientsAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-clients.ee.js';
|
||||||
|
import getAdminAppAuthClientAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-client.ee.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:appAuthClientId',
|
'/',
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
authorizeAdmin,
|
authorizeAdmin,
|
||||||
checkIsEnterprise,
|
checkIsEnterprise,
|
||||||
asyncHandler(getAdminAppAuthClientsAction)
|
asyncHandler(getAdminAppAuthClientsAction)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:appAuthClientId',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeAdmin,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(getAdminAppAuthClientAction)
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@@ -3,9 +3,17 @@ import asyncHandler from 'express-async-handler';
|
|||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
import { authenticateUser } from '../../../helpers/authentication.js';
|
||||||
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
|
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
|
||||||
import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js';
|
import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js';
|
||||||
|
import getAppAuthClientsAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-clients.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
authenticateUser,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(getAppAuthClientsAction)
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:appAuthClientId',
|
'/:appAuthClientId',
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
|
@@ -3,6 +3,8 @@ import asyncHandler from 'express-async-handler';
|
|||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
import { authenticateUser } from '../../../helpers/authentication.js';
|
||||||
import { authorizeUser } from '../../../helpers/authorization.js';
|
import { authorizeUser } from '../../../helpers/authorization.js';
|
||||||
import getConnectionAction from '../../../controllers/api/v1/steps/get-connection.js';
|
import getConnectionAction from '../../../controllers/api/v1/steps/get-connection.js';
|
||||||
|
import getPreviousStepsAction from '../../../controllers/api/v1/steps/get-previous-steps.js';
|
||||||
|
import createDynamicFieldsAction from '../../../controllers/api/v1/steps/create-dynamic-fields.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -13,4 +15,18 @@ router.get(
|
|||||||
asyncHandler(getConnectionAction)
|
asyncHandler(getConnectionAction)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:stepId/previous-steps',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeUser,
|
||||||
|
asyncHandler(getPreviousStepsAction)
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:stepId/dynamic-fields',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeUser,
|
||||||
|
asyncHandler(createDynamicFieldsAction)
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@@ -18,7 +18,7 @@ import adminSamlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.
|
|||||||
import rolesRouter from './api/v1/admin/roles.ee.js';
|
import rolesRouter from './api/v1/admin/roles.ee.js';
|
||||||
import permissionsRouter from './api/v1/admin/permissions.ee.js';
|
import permissionsRouter from './api/v1/admin/permissions.ee.js';
|
||||||
import adminUsersRouter from './api/v1/admin/users.ee.js';
|
import adminUsersRouter from './api/v1/admin/users.ee.js';
|
||||||
import adminAppAuthClientsRouter from './api/v1/admin/app-auth-clients.js';
|
import adminAppAuthClientsRouter from './api/v1/admin/app-auth-clients.ee.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
|
import executionStepSerializer from './execution-step.js';
|
||||||
|
|
||||||
const stepSerializer = (step) => {
|
const stepSerializer = (step) => {
|
||||||
return {
|
let stepData = {
|
||||||
id: step.id,
|
id: step.id,
|
||||||
type: step.type,
|
type: step.type,
|
||||||
key: step.key,
|
key: step.key,
|
||||||
@@ -10,6 +12,14 @@ const stepSerializer = (step) => {
|
|||||||
position: step.position,
|
position: step.position,
|
||||||
parameters: step.parameters,
|
parameters: step.parameters,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (step.executionSteps?.length > 0) {
|
||||||
|
stepData.executionSteps = step.executionSteps.map((executionStep) =>
|
||||||
|
executionStepSerializer(executionStep)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stepData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default stepSerializer;
|
export default stepSerializer;
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
import { createStep } from '../../test/factories/step';
|
import { createStep } from '../../test/factories/step';
|
||||||
|
import { createExecutionStep } from '../../test/factories/execution-step';
|
||||||
import stepSerializer from './step';
|
import stepSerializer from './step';
|
||||||
|
import executionStepSerializer from './execution-step';
|
||||||
|
|
||||||
describe('stepSerializer', () => {
|
describe('stepSerializer', () => {
|
||||||
let step;
|
let step;
|
||||||
@@ -24,4 +26,20 @@ describe('stepSerializer', () => {
|
|||||||
|
|
||||||
expect(stepSerializer(step)).toEqual(expectedPayload);
|
expect(stepSerializer(step)).toEqual(expectedPayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return step data with the execution steps', async () => {
|
||||||
|
const executionStepOne = await createExecutionStep({ stepId: step.id });
|
||||||
|
const executionStepTwo = await createExecutionStep({ stepId: step.id });
|
||||||
|
|
||||||
|
step.executionSteps = [executionStepOne, executionStepTwo];
|
||||||
|
|
||||||
|
const expectedPayload = {
|
||||||
|
executionSteps: [
|
||||||
|
executionStepSerializer(executionStepOne),
|
||||||
|
executionStepSerializer(executionStepTwo),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(stepSerializer(step)).toMatchObject(expectedPayload);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -17,9 +17,7 @@ export const createAppAuthClient = async (params = {}) => {
|
|||||||
params.formattedAuthDefaults =
|
params.formattedAuthDefaults =
|
||||||
params?.formattedAuthDefaults || formattedAuthDefaults;
|
params?.formattedAuthDefaults || formattedAuthDefaults;
|
||||||
|
|
||||||
const appAuthClient = await AppAuthClient.query()
|
const appAuthClient = await AppAuthClient.query().insertAndFetch(params);
|
||||||
.insert(params)
|
|
||||||
.returning('*');
|
|
||||||
|
|
||||||
return appAuthClient;
|
return appAuthClient;
|
||||||
};
|
};
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import AppConfig from '../../src/models/app-config.js';
|
import AppConfig from '../../src/models/app-config.js';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
export const createAppConfig = async (params = {}) => {
|
export const createAppConfig = async (params = {}) => {
|
||||||
params.key = params?.key || 'gitlab';
|
params.key = params?.key || faker.lorem.word();
|
||||||
|
|
||||||
const appConfig = await AppConfig.query().insert(params).returning('*');
|
const appConfig = await AppConfig.query().insertAndFetch(params);
|
||||||
|
|
||||||
return appConfig;
|
return appConfig;
|
||||||
};
|
};
|
||||||
|
@@ -7,7 +7,7 @@ export const createConfig = async (params = {}) => {
|
|||||||
value: params?.value || { data: 'sampleConfig' },
|
value: params?.value || { data: 'sampleConfig' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = await Config.query().insert(configData).returning('*');
|
const config = await Config.query().insertAndFetch(configData);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
@@ -9,9 +9,7 @@ export const createExecutionStep = async (params = {}) => {
|
|||||||
params.dataIn = params?.dataIn || { dataIn: 'dataIn' };
|
params.dataIn = params?.dataIn || { dataIn: 'dataIn' };
|
||||||
params.dataOut = params?.dataOut || { dataOut: 'dataOut' };
|
params.dataOut = params?.dataOut || { dataOut: 'dataOut' };
|
||||||
|
|
||||||
const executionStep = await ExecutionStep.query()
|
const executionStep = await ExecutionStep.query().insertAndFetch(params);
|
||||||
.insert(params)
|
|
||||||
.returning('*');
|
|
||||||
|
|
||||||
return executionStep;
|
return executionStep;
|
||||||
};
|
};
|
||||||
|
@@ -4,10 +4,8 @@ import { createFlow } from './flow';
|
|||||||
export const createExecution = async (params = {}) => {
|
export const createExecution = async (params = {}) => {
|
||||||
params.flowId = params?.flowId || (await createFlow()).id;
|
params.flowId = params?.flowId || (await createFlow()).id;
|
||||||
params.testRun = params?.testRun || false;
|
params.testRun = params?.testRun || false;
|
||||||
params.createdAt = params?.createdAt || new Date().toISOString();
|
|
||||||
params.updatedAt = params?.updatedAt || new Date().toISOString();
|
|
||||||
|
|
||||||
const execution = await Execution.query().insert(params).returning('*');
|
const execution = await Execution.query().insertAndFetch(params);
|
||||||
|
|
||||||
return execution;
|
return execution;
|
||||||
};
|
};
|
||||||
|
@@ -7,7 +7,7 @@ export const createFlow = async (params = {}) => {
|
|||||||
params.createdAt = params?.createdAt || new Date().toISOString();
|
params.createdAt = params?.createdAt || new Date().toISOString();
|
||||||
params.updatedAt = params?.updatedAt || new Date().toISOString();
|
params.updatedAt = params?.updatedAt || new Date().toISOString();
|
||||||
|
|
||||||
const flow = await Flow.query().insert(params).returning('*');
|
const flow = await Flow.query().insertAndFetch(params);
|
||||||
|
|
||||||
return flow;
|
return flow;
|
||||||
};
|
};
|
||||||
|
@@ -7,7 +7,7 @@ export const createPermission = async (params = {}) => {
|
|||||||
params.subject = params?.subject || 'User';
|
params.subject = params?.subject || 'User';
|
||||||
params.conditions = params?.conditions || ['isCreator'];
|
params.conditions = params?.conditions || ['isCreator'];
|
||||||
|
|
||||||
const permission = await Permission.query().insert(params).returning('*');
|
const permission = await Permission.query().insertAndFetch(params);
|
||||||
|
|
||||||
return permission;
|
return permission;
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,7 @@ export const createRole = async (params = {}) => {
|
|||||||
params.name = params?.name || 'Viewer';
|
params.name = params?.name || 'Viewer';
|
||||||
params.key = params?.key || 'viewer';
|
params.key = params?.key || 'viewer';
|
||||||
|
|
||||||
const role = await Role.query().insert(params).returning('*');
|
const role = await Role.query().insertAndFetch(params);
|
||||||
|
|
||||||
return role;
|
return role;
|
||||||
};
|
};
|
||||||
|
@@ -25,9 +25,9 @@ export const createSamlAuthProvider = async (params = {}) => {
|
|||||||
params.defaultRoleId = params?.defaultRoleId || (await createRole()).id;
|
params.defaultRoleId = params?.defaultRoleId || (await createRole()).id;
|
||||||
params.active = params?.active || true;
|
params.active = params?.active || true;
|
||||||
|
|
||||||
const samlAuthProvider = await SamlAuthProvider.query()
|
const samlAuthProvider = await SamlAuthProvider.query().insertAndFetch(
|
||||||
.insert(params)
|
params
|
||||||
.returning('*');
|
);
|
||||||
|
|
||||||
return samlAuthProvider;
|
return samlAuthProvider;
|
||||||
};
|
};
|
||||||
|
@@ -5,19 +5,21 @@ export const createStep = async (params = {}) => {
|
|||||||
params.flowId = params?.flowId || (await createFlow()).id;
|
params.flowId = params?.flowId || (await createFlow()).id;
|
||||||
params.type = params?.type || 'action';
|
params.type = params?.type || 'action';
|
||||||
|
|
||||||
const lastStep = await global.knex
|
const lastStep = await Step.query()
|
||||||
.table('steps')
|
.where('flow_id', params.flowId)
|
||||||
.where('flowId', params.flowId)
|
.andWhere('deleted_at', null)
|
||||||
.andWhere('deletedAt', '!=', null)
|
.orderBy('position', 'desc')
|
||||||
.orderBy('createdAt', 'desc')
|
.limit(1)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
params.position = params?.position || (lastStep?.position || 0) + 1;
|
params.position =
|
||||||
|
params?.position || (lastStep?.position ? lastStep.position + 1 : 1);
|
||||||
|
|
||||||
params.status = params?.status || 'completed';
|
params.status = params?.status || 'completed';
|
||||||
params.appKey =
|
params.appKey =
|
||||||
params?.appKey || (params.type === 'action' ? 'deepl' : 'webhook');
|
params?.appKey || (params.type === 'action' ? 'deepl' : 'webhook');
|
||||||
|
|
||||||
const step = await Step.query().insert(params).returning('*');
|
const step = await Step.query().insertAndFetch(params);
|
||||||
|
|
||||||
return step;
|
return step;
|
||||||
};
|
};
|
||||||
|
@@ -15,7 +15,7 @@ export const createSubscription = async (params = {}) => {
|
|||||||
params.nextBillDate =
|
params.nextBillDate =
|
||||||
params?.nextBillDate || DateTime.now().plus({ days: 30 }).toISODate();
|
params?.nextBillDate || DateTime.now().plus({ days: 30 }).toISODate();
|
||||||
|
|
||||||
const subscription = await Subscription.query().insert(params).returning('*');
|
const subscription = await Subscription.query().insertAndFetch(params);
|
||||||
|
|
||||||
return subscription;
|
return subscription;
|
||||||
};
|
};
|
||||||
|
@@ -8,7 +8,7 @@ export const createUser = async (params = {}) => {
|
|||||||
params.email = params?.email || faker.internet.email();
|
params.email = params?.email || faker.internet.email();
|
||||||
params.password = params?.password || faker.internet.password();
|
params.password = params?.password || faker.internet.password();
|
||||||
|
|
||||||
const user = await User.query().insert(params).returning('*');
|
const user = await User.query().insertAndFetch(params);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
const getAdminAppAuthClientsMock = (appAuthClients) => {
|
||||||
|
return {
|
||||||
|
data: appAuthClients.map((appAuthClient) => ({
|
||||||
|
appConfigId: appAuthClient.appConfigId,
|
||||||
|
name: appAuthClient.name,
|
||||||
|
id: appAuthClient.id,
|
||||||
|
active: appAuthClient.active,
|
||||||
|
})),
|
||||||
|
meta: {
|
||||||
|
count: appAuthClients.length,
|
||||||
|
currentPage: null,
|
||||||
|
isArray: true,
|
||||||
|
totalPages: null,
|
||||||
|
type: 'AppAuthClient',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAdminAppAuthClientsMock;
|
@@ -0,0 +1,19 @@
|
|||||||
|
const getAppAuthClientsMock = (appAuthClients) => {
|
||||||
|
return {
|
||||||
|
data: appAuthClients.map((appAuthClient) => ({
|
||||||
|
appConfigId: appAuthClient.appConfigId,
|
||||||
|
name: appAuthClient.name,
|
||||||
|
id: appAuthClient.id,
|
||||||
|
active: appAuthClient.active,
|
||||||
|
})),
|
||||||
|
meta: {
|
||||||
|
count: appAuthClients.length,
|
||||||
|
currentPage: null,
|
||||||
|
isArray: true,
|
||||||
|
totalPages: null,
|
||||||
|
type: 'AppAuthClient',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAppAuthClientsMock;
|
@@ -0,0 +1,36 @@
|
|||||||
|
const createDynamicFieldsMock = async () => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
label: 'Bot name',
|
||||||
|
key: 'botName',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
value: 'Automatisch',
|
||||||
|
description:
|
||||||
|
'Specify the bot name which appears as a bold username above the message inside Slack. Defaults to Automatisch.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bot icon',
|
||||||
|
key: 'botIcon',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'Either an image url or an emoji available to your team (surrounded by :). For example, https://example.com/icon_256.png or :robot_face:',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
meta: {
|
||||||
|
count: data.length,
|
||||||
|
currentPage: null,
|
||||||
|
isArray: true,
|
||||||
|
totalPages: null,
|
||||||
|
type: 'Object',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createDynamicFieldsMock;
|
@@ -0,0 +1,41 @@
|
|||||||
|
const getPreviousStepsMock = async (steps, executionSteps) => {
|
||||||
|
const data = steps.map((step) => {
|
||||||
|
const filteredExecutionSteps = executionSteps.filter(
|
||||||
|
(executionStep) => executionStep.stepId === step.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: step.id,
|
||||||
|
type: step.type,
|
||||||
|
key: step.key,
|
||||||
|
appKey: step.appKey,
|
||||||
|
iconUrl: step.iconUrl,
|
||||||
|
webhookUrl: step.webhookUrl,
|
||||||
|
status: step.status,
|
||||||
|
position: step.position,
|
||||||
|
parameters: step.parameters,
|
||||||
|
executionSteps: filteredExecutionSteps.map((executionStep) => ({
|
||||||
|
id: executionStep.id,
|
||||||
|
dataIn: executionStep.dataIn,
|
||||||
|
dataOut: executionStep.dataOut,
|
||||||
|
errorDetails: executionStep.errorDetails,
|
||||||
|
status: executionStep.status,
|
||||||
|
createdAt: executionStep.createdAt.getTime(),
|
||||||
|
updatedAt: executionStep.updatedAt.getTime(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
meta: {
|
||||||
|
count: data.length,
|
||||||
|
currentPage: null,
|
||||||
|
isArray: true,
|
||||||
|
totalPages: null,
|
||||||
|
type: 'Step',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPreviousStepsMock;
|
@@ -113,6 +113,12 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/formatter/connection' },
|
{ text: 'Connection', link: '/apps/formatter/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'FTP',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [{ text: 'Connection', link: '/apps/ftp/connection' }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Ghost',
|
text: 'Ghost',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
|
16
packages/docs/pages/apps/ftp/connection.md
Normal file
16
packages/docs/pages/apps/ftp/connection.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# FTP
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the FTP connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
FTP, which stands for File Transfer Protocol, is a standard network protocol used for the transfer of computer files between a client and server on a computer network. t's commonly used to download files from a server or upload files to a server.. You need to provide the following information from Automatisch by using FTP connection.
|
||||||
|
|
||||||
|
1. Write any screen name to be displayed in Automatisch.
|
||||||
|
1. Fill host address field with the FTP host address.
|
||||||
|
1. Fill username field with the FTP username.
|
||||||
|
1. Fill password field with the FTP password.
|
||||||
|
1. Select the Secure field.
|
||||||
|
1. Fill port field with the FTP port. Default is 21.
|
||||||
|
1. Click **Submit** button on Automatisch.
|
||||||
|
1. Now, you can start using the FTP connection with Automatisch.
|
1
packages/docs/pages/public/favicons/ftp.svg
Normal file
1
packages/docs/pages/public/favicons/ftp.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 109.36" style="enable-background:new 0 0 122.88 109.36" xml:space="preserve"><g><path d="M14.69,16.44h3.56V8.63c0-1.09,0.88-1.97,1.97-1.97h3.5V1.97C23.72,0.88,24.6,0,25.69,0h31.92c1.09,0,1.97,0.88,1.97,1.97 v4.69h43.05c1.09,0,1.97,0.88,1.97,1.97v7.82h3.59c1.09,0,1.97,0.88,1.97,1.97c0,0.14-0.01,0.28-0.04,0.42l-6.88,50.59 c-0.22,1.65-0.95,3.18-2.05,4.29c-1.1,1.1-2.54,1.78-4.25,1.78H70.95v5.86h2.12c1.54,0,2.8,1.26,2.8,2.8v5.06h10.44 c1.25,0,2.32,0.83,2.67,1.97h26.1c3.73,0,6.14,2.07,7.2,4.71c0.4,1,0.6,2.08,0.6,3.14c0,1.06-0.2,2.14-0.6,3.14 c-1.06,2.64-3.47,4.71-7.2,4.71H89.09c-0.15,1.4-1.35,2.49-2.78,2.49H72.63H38.8c-1.44,0-2.63-1.1-2.78-2.49H7.8 c-3.73,0-6.14-2.07-7.2-4.71c-0.4-1-0.6-2.08-0.6-3.14c0-1.06,0.2-2.14,0.6-3.14c1.06-2.64,3.47-4.71,7.2-4.71h28.32 c0.36-1.14,1.42-1.97,2.67-1.97h8.74v-5.06c0-1.54,1.26-2.8,2.8-2.8h1.69v-5.86H24.38c-1.71,0-3.19-0.69-4.3-1.79 c-1.11-1.11-1.83-2.66-2.01-4.33l-5.33-50.74c-0.11-1.08,0.67-2.04,1.75-2.15C14.56,16.45,14.63,16.45,14.69,16.44L14.69,16.44 L14.69,16.44L14.69,16.44z M31.38,34.43h16.29v4.58H38v3.72h8.26v4.3H38v8.71h-6.61V34.43L31.38,34.43z M50.08,34.43H70.1v5.27 h-6.72v16.05h-6.59V39.7h-6.71V34.43L50.08,34.43z M73.41,34.43h10.95c2.38,0,4.17,0.57,5.35,1.7c1.19,1.14,1.78,2.75,1.78,4.84 c0,2.15-0.65,3.84-1.94,5.05c-1.3,1.21-3.27,1.82-5.93,1.82h-3.6v7.91h-6.61V34.43L73.41,34.43z M80.03,43.52h1.61 c1.27,0,2.16-0.22,2.68-0.66c0.51-0.44,0.77-1,0.77-1.69c0-0.67-0.22-1.24-0.67-1.7c-0.44-0.47-1.28-0.7-2.51-0.7h-1.88V43.52 L80.03,43.52z M56.38,81.34h10.21v-5.79H56.38V81.34L56.38,81.34z M89.11,94.88v8.27h25.97c1.97,0,3.22-1.04,3.76-2.37 c0.22-0.54,0.33-1.15,0.33-1.76s-0.11-1.22-0.33-1.76c-0.54-1.33-1.79-2.37-3.76-2.37H89.11L89.11,94.88z M36,103.15v-8.27H7.8 c-1.97,0-3.22,1.04-3.76,2.37c-0.22,0.54-0.33,1.15-0.33,1.76s0.11,1.22,0.33,1.76c0.54,1.33,1.79,2.37,3.76,2.37H36L36,103.15z M20.23,20.38h-3.35l5.1,48.57c0.08,0.78,0.39,1.47,0.87,1.95c0.4,0.4,0.92,0.64,1.53,0.64h72.54c0.59,0,1.1-0.24,1.48-0.62 c0.49-0.49,0.82-1.22,0.93-2.03l6.6-48.51L20.23,20.38L20.23,20.38L20.23,20.38z M22.19,10.6v5.83l78.46-0.83v-5H57.61 c-1.09,0-1.97-0.88-1.97-1.97V3.94H27.66v4.69c0,1.09-0.88,1.97-1.97,1.97L22.19,10.6L22.19,10.6L22.19,10.6z"/></g></svg>
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,35 +1,39 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { GET_FLOWS } from 'graphql/queries/get-flows';
|
|
||||||
import Pagination from '@mui/material/Pagination';
|
import Pagination from '@mui/material/Pagination';
|
||||||
import PaginationItem from '@mui/material/PaginationItem';
|
import PaginationItem from '@mui/material/PaginationItem';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import AppFlowRow from 'components/FlowRow';
|
import AppFlowRow from 'components/FlowRow';
|
||||||
import NoResultFound from 'components/NoResultFound';
|
import NoResultFound from 'components/NoResultFound';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
const FLOW_PER_PAGE = 10;
|
import useConnectionFlows from 'hooks/useConnectionFlows';
|
||||||
const getLimitAndOffset = (page) => ({
|
import useAppFlows from 'hooks/useAppFlows';
|
||||||
limit: FLOW_PER_PAGE,
|
|
||||||
offset: (page - 1) * FLOW_PER_PAGE,
|
|
||||||
});
|
|
||||||
function AppFlows(props) {
|
function AppFlows(props) {
|
||||||
const { appKey } = props;
|
const { appKey } = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const connectionId = searchParams.get('connectionId') || undefined;
|
const connectionId = searchParams.get('connectionId') || undefined;
|
||||||
const page = parseInt(searchParams.get('page') || '', 10) || 1;
|
const page = parseInt(searchParams.get('page') || '', 10) || 1;
|
||||||
const { data } = useQuery(GET_FLOWS, {
|
const isConnectionFlowEnabled = !!connectionId;
|
||||||
variables: {
|
const isAppFlowEnabled = !!appKey && !connectionId;
|
||||||
appKey,
|
|
||||||
connectionId,
|
const connectionFlows = useConnectionFlows(
|
||||||
...getLimitAndOffset(page),
|
{ connectionId, page },
|
||||||
},
|
{ enabled: isConnectionFlowEnabled },
|
||||||
});
|
);
|
||||||
const getFlows = data?.getFlows || {};
|
|
||||||
const { pageInfo, edges } = getFlows;
|
const appFlows = useAppFlows({ appKey, page }, { enabled: isAppFlowEnabled });
|
||||||
const flows = edges?.map(({ node }) => node);
|
|
||||||
|
const flows = isConnectionFlowEnabled
|
||||||
|
? connectionFlows?.data?.data || []
|
||||||
|
: appFlows?.data?.data || [];
|
||||||
|
const pageInfo = isConnectionFlowEnabled
|
||||||
|
? connectionFlows?.data?.meta || []
|
||||||
|
: appFlows?.data?.meta || [];
|
||||||
const hasFlows = flows?.length;
|
const hasFlows = flows?.length;
|
||||||
|
|
||||||
if (!hasFlows) {
|
if (!hasFlows) {
|
||||||
return (
|
return (
|
||||||
<NoResultFound
|
<NoResultFound
|
||||||
@@ -39,6 +43,7 @@ function AppFlows(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{flows?.map((appFlow) => (
|
{flows?.map((appFlow) => (
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
import useConfig from 'hooks/useConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
import { LogoImage } from './style.ee';
|
import { LogoImage } from './style.ee';
|
||||||
|
|
||||||
const CustomLogo = () => {
|
const CustomLogo = () => {
|
||||||
const { config, loading } = useConfig(['logo.svgData']);
|
const { data: configData, isLoading } = useAutomatischConfig();
|
||||||
if (loading || !config?.['logo.svgData']) return null;
|
const config = configData?.data;
|
||||||
|
|
||||||
|
if (isLoading || !config?.['logo.svgData']) return null;
|
||||||
|
|
||||||
const logoSvgData = config['logo.svgData'];
|
const logoSvgData = config['logo.svgData'];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogoImage
|
<LogoImage
|
||||||
data-test="custom-logo"
|
data-test="custom-logo"
|
||||||
|
@@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import ConfirmationDialog from 'components/ConfirmationDialog';
|
import ConfirmationDialog from 'components/ConfirmationDialog';
|
||||||
@@ -17,9 +19,13 @@ function DeleteUserButton(props) {
|
|||||||
});
|
});
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const handleConfirm = React.useCallback(async () => {
|
const handleConfirm = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await deleteUser();
|
await deleteUser();
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'user', userId] });
|
||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
enqueueSnackbar(formatMessage('deleteUserButton.successfullyDeleted'), {
|
enqueueSnackbar(formatMessage('deleteUserButton.successfullyDeleted'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
@@ -31,6 +37,7 @@ function DeleteUserButton(props) {
|
|||||||
throw new Error('Failed while deleting!');
|
throw new Error('Failed while deleting!');
|
||||||
}
|
}
|
||||||
}, [deleteUser]);
|
}, [deleteUser]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@@ -15,7 +15,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
|||||||
import useVersion from 'hooks/useVersion';
|
import useVersion from 'hooks/useVersion';
|
||||||
import AppBar from 'components/AppBar';
|
import AppBar from 'components/AppBar';
|
||||||
import Drawer from 'components/Drawer';
|
import Drawer from 'components/Drawer';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
|
|
||||||
const drawerLinks = [
|
const drawerLinks = [
|
||||||
{
|
{
|
||||||
@@ -77,11 +77,9 @@ const generateDrawerBottomLinks = async ({
|
|||||||
|
|
||||||
export default function PublicLayout({ children }) {
|
export default function PublicLayout({ children }) {
|
||||||
const version = useVersion();
|
const version = useVersion();
|
||||||
const { config, loading } = useConfig([
|
const { data: configData, isLoading } = useAutomatischConfig();
|
||||||
'disableNotificationsPage',
|
const config = configData?.data;
|
||||||
'additionalDrawerLink',
|
|
||||||
'additionalDrawerLinkText',
|
|
||||||
]);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [bottomLinks, setBottomLinks] = React.useState([]);
|
const [bottomLinks, setBottomLinks] = React.useState([]);
|
||||||
@@ -102,10 +100,10 @@ export default function PublicLayout({ children }) {
|
|||||||
setBottomLinks(newBottomLinks);
|
setBottomLinks(newBottomLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) return;
|
if (isLoading) return;
|
||||||
|
|
||||||
perform();
|
perform();
|
||||||
}, [config, loading, version.newVersionCount]);
|
}, [config, isLoading, version.newVersionCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@@ -1,12 +1,19 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import CustomLogo from 'components/CustomLogo/index.ee';
|
import CustomLogo from 'components/CustomLogo/index.ee';
|
||||||
import DefaultLogo from 'components/DefaultLogo';
|
import DefaultLogo from 'components/DefaultLogo';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
|
|
||||||
const Logo = () => {
|
const Logo = () => {
|
||||||
const { config, loading } = useConfig(['logo.svgData']);
|
const { data: configData, isLoading } = useAutomatischConfig();
|
||||||
|
const config = configData?.data;
|
||||||
const logoSvgData = config?.['logo.svgData'];
|
const logoSvgData = config?.['logo.svgData'];
|
||||||
if (loading && !logoSvgData) return <React.Fragment />;
|
|
||||||
|
if (isLoading && !logoSvgData) return <React.Fragment />;
|
||||||
|
|
||||||
if (logoSvgData) return <CustomLogo />;
|
if (logoSvgData) return <CustomLogo />;
|
||||||
|
|
||||||
return <DefaultLogo />;
|
return <DefaultLogo />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Logo;
|
export default Logo;
|
||||||
|
@@ -1,15 +1,22 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import useConfig from 'hooks/useConfig';
|
|
||||||
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
|
|
||||||
const MetadataProvider = ({ children }) => {
|
const MetadataProvider = ({ children }) => {
|
||||||
const { config } = useConfig();
|
const { data: configData } = useAutomatischConfig();
|
||||||
|
const config = configData?.data;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
document.title = config?.title || 'Automatisch';
|
document.title = config?.title || 'Automatisch';
|
||||||
}, [config?.title]);
|
}, [config?.title]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const existingFaviconElement = document.querySelector("link[rel~='icon']");
|
const existingFaviconElement = document.querySelector("link[rel~='icon']");
|
||||||
|
|
||||||
if (config?.disableFavicon === true) {
|
if (config?.disableFavicon === true) {
|
||||||
existingFaviconElement?.remove();
|
existingFaviconElement?.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config?.disableFavicon === false) {
|
if (config?.disableFavicon === false) {
|
||||||
if (existingFaviconElement) {
|
if (existingFaviconElement) {
|
||||||
existingFaviconElement.href = '/browser-tab.ico';
|
existingFaviconElement.href = '/browser-tab.ico';
|
||||||
@@ -20,7 +27,10 @@ const MetadataProvider = ({ children }) => {
|
|||||||
newFaviconElement.href = '/browser-tab.ico';
|
newFaviconElement.href = '/browser-tab.ico';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [config?.disableFavicon]);
|
}, [config?.disableFavicon]);
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MetadataProvider;
|
export default MetadataProvider;
|
||||||
|
@@ -6,7 +6,7 @@ import set from 'lodash/set';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
import { defaultTheme, mationTheme } from 'styles/theme';
|
import { defaultTheme, mationTheme } from 'styles/theme';
|
||||||
|
|
||||||
const customizeTheme = (theme, config) => {
|
const customizeTheme = (theme, config) => {
|
||||||
@@ -28,7 +28,8 @@ const ThemeProvider = ({ children, ...props }) => {
|
|||||||
const { data: automatischInfo, isPending: isAutomatischInfoPending } =
|
const { data: automatischInfo, isPending: isAutomatischInfoPending } =
|
||||||
useAutomatischInfo();
|
useAutomatischInfo();
|
||||||
const isMation = automatischInfo?.data.isMation;
|
const isMation = automatischInfo?.data.isMation;
|
||||||
const { config, loading: configLoading } = useConfig();
|
const { data: configData, isLoading: configLoading } = useAutomatischConfig();
|
||||||
|
const config = configData?.data;
|
||||||
|
|
||||||
const customTheme = React.useMemo(() => {
|
const customTheme = React.useMemo(() => {
|
||||||
const installationTheme = isMation ? mationTheme : defaultTheme;
|
const installationTheme = isMation ? mationTheme : defaultTheme;
|
||||||
@@ -51,4 +52,5 @@ const ThemeProvider = ({ children, ...props }) => {
|
|||||||
</BaseThemeProvider>
|
</BaseThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThemeProvider;
|
export default ThemeProvider;
|
||||||
|
@@ -14,23 +14,23 @@ import EditIcon from '@mui/icons-material/Edit';
|
|||||||
import TableFooter from '@mui/material/TableFooter';
|
import TableFooter from '@mui/material/TableFooter';
|
||||||
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
||||||
import ListLoader from 'components/ListLoader';
|
import ListLoader from 'components/ListLoader';
|
||||||
import useUsers from 'hooks/useUsers';
|
import useAdminUsers from 'hooks/useAdminUsers';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import TablePaginationActions from './TablePaginationActions';
|
import TablePaginationActions from './TablePaginationActions';
|
||||||
import { TablePagination } from './style';
|
import { TablePagination } from './style';
|
||||||
|
|
||||||
export default function UserList() {
|
export default function UserList() {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = React.useState(10);
|
const { data: usersData, isLoading } = useAdminUsers(page + 1);
|
||||||
const { users, pageInfo, totalCount, loading } = useUsers(page, rowsPerPage);
|
const users = usersData?.data;
|
||||||
|
const { count } = usersData?.meta || {};
|
||||||
|
|
||||||
const handleChangePage = (event, newPage) => {
|
const handleChangePage = (event, newPage) => {
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
};
|
};
|
||||||
const handleChangeRowsPerPage = (event) => {
|
|
||||||
setRowsPerPage(+event.target.value);
|
|
||||||
setPage(0);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
@@ -68,14 +68,14 @@ export default function UserList() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading && (
|
{isLoading && (
|
||||||
<ListLoader
|
<ListLoader
|
||||||
data-test="users-list-loader"
|
data-test="users-list-loader"
|
||||||
rowsNumber={3}
|
rowsNumber={3}
|
||||||
columnsNumber={2}
|
columnsNumber={2}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!loading &&
|
{!isLoading &&
|
||||||
users.map((user) => (
|
users.map((user) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={user.id}
|
key={user.id}
|
||||||
@@ -120,18 +120,16 @@ export default function UserList() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
{totalCount && (
|
{!isLoading && typeof count === 'number' && (
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
data-total-count={totalCount}
|
data-total-count={count}
|
||||||
data-rows-per-page={rowsPerPage}
|
rowsPerPageOptions={[]}
|
||||||
rowsPerPageOptions={[10, 25, 50, 100]}
|
|
||||||
page={page}
|
page={page}
|
||||||
count={totalCount}
|
count={count}
|
||||||
onPageChange={handleChangePage}
|
onPageChange={handleChangePage}
|
||||||
rowsPerPage={rowsPerPage}
|
rowsPerPage={10}
|
||||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
||||||
ActionsComponent={TablePaginationActions}
|
ActionsComponent={TablePaginationActions}
|
||||||
/>
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
export const GET_CONFIG = gql`
|
|
||||||
query GetConfig($keys: [String]) {
|
|
||||||
getConfig(keys: $keys)
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,36 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
export const GET_FLOWS = gql`
|
|
||||||
query GetFlows(
|
|
||||||
$limit: Int!
|
|
||||||
$offset: Int!
|
|
||||||
$appKey: String
|
|
||||||
$connectionId: String
|
|
||||||
$name: String
|
|
||||||
) {
|
|
||||||
getFlows(
|
|
||||||
limit: $limit
|
|
||||||
offset: $offset
|
|
||||||
appKey: $appKey
|
|
||||||
connectionId: $connectionId
|
|
||||||
name: $name
|
|
||||||
) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
active
|
|
||||||
status
|
|
||||||
steps {
|
|
||||||
iconUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,11 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
export const GET_NOTIFICATIONS = gql`
|
|
||||||
query GetNotifications {
|
|
||||||
getNotifications {
|
|
||||||
name
|
|
||||||
createdAt
|
|
||||||
documentationUrl
|
|
||||||
description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,23 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
export const GET_USERS = gql`
|
|
||||||
query GetUsers($limit: Int!, $offset: Int!) {
|
|
||||||
getUsers(limit: $limit, offset: $offset) {
|
|
||||||
pageInfo {
|
|
||||||
currentPage
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
totalCount
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
fullName
|
|
||||||
email
|
|
||||||
role {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,9 +1,9 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import api from 'helpers/api';
|
import api from 'helpers/api';
|
||||||
|
|
||||||
export default function useUser({ userId }) {
|
export default function useAdminUser({ userId }) {
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['user', userId],
|
queryKey: ['admin', 'user', userId],
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
const { data } = await api.get(`/v1/admin/users/${userId}`, {
|
const { data } = await api.get(`/v1/admin/users/${userId}`, {
|
||||||
signal,
|
signal,
|
17
packages/web/src/hooks/useAdminUsers.js
Normal file
17
packages/web/src/hooks/useAdminUsers.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useAdminUsers(page) {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['admin', 'users', page],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/admin/users`, {
|
||||||
|
signal,
|
||||||
|
params: { page },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
22
packages/web/src/hooks/useAppFlows.js
Normal file
22
packages/web/src/hooks/useAppFlows.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useAppFlows({ appKey, page }, { enabled }) {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['appFlows', appKey, page],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/apps/${appKey}/flows`, {
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
},
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
16
packages/web/src/hooks/useAutomatischConfig.js
Normal file
16
packages/web/src/hooks/useAutomatischConfig.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useAutomatischConfig() {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['automatisch', 'config'],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/automatisch/config`, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
16
packages/web/src/hooks/useAutomatischNotifications.js
Normal file
16
packages/web/src/hooks/useAutomatischNotifications.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useAutomatischNotifications() {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['automatisch', 'notifications'],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/automatisch/notifications`, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
@@ -1,11 +0,0 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import { GET_CONFIG } from 'graphql/queries/get-config.ee';
|
|
||||||
export default function useConfig(keys) {
|
|
||||||
const { data, loading } = useQuery(GET_CONFIG, {
|
|
||||||
variables: { keys },
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
config: data?.getConfig,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
}
|
|
25
packages/web/src/hooks/useConnectionFlows.js
Normal file
25
packages/web/src/hooks/useConnectionFlows.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useConnectionFlows(
|
||||||
|
{ connectionId, page },
|
||||||
|
{ enabled },
|
||||||
|
) {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['connectionFlows', connectionId, page],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/connections/${connectionId}/flows`, {
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
},
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
30
packages/web/src/hooks/useLazyFlows.js
Normal file
30
packages/web/src/hooks/useLazyFlows.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
export default function useLazyFlows({ flowName, page }, { onSuccess }) {
|
||||||
|
const abortControllerRef = React.useRef(new AbortController());
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
abortControllerRef.current = new AbortController();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
abortControllerRef.current?.abort();
|
||||||
|
};
|
||||||
|
}, [flowName]);
|
||||||
|
|
||||||
|
const query = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const { data } = await api.get('/v1/flows', {
|
||||||
|
params: { name: flowName, page },
|
||||||
|
signal: abortControllerRef.current.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess,
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
@@ -1,10 +0,0 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import { GET_NOTIFICATIONS } from 'graphql/queries/get-notifications';
|
|
||||||
export default function useNotifications() {
|
|
||||||
const { data, loading } = useQuery(GET_NOTIFICATIONS);
|
|
||||||
const notifications = data?.getNotifications || [];
|
|
||||||
return {
|
|
||||||
loading,
|
|
||||||
notifications,
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import { GET_USERS } from 'graphql/queries/get-users';
|
|
||||||
const getLimitAndOffset = (page, rowsPerPage) => ({
|
|
||||||
limit: rowsPerPage,
|
|
||||||
offset: page * rowsPerPage,
|
|
||||||
});
|
|
||||||
export default function useUsers(page, rowsPerPage) {
|
|
||||||
const { data, loading } = useQuery(GET_USERS, {
|
|
||||||
variables: getLimitAndOffset(page, rowsPerPage),
|
|
||||||
});
|
|
||||||
const users = data?.getUsers.edges.map(({ node }) => node) || [];
|
|
||||||
const pageInfo = data?.getUsers.pageInfo;
|
|
||||||
const totalCount = data?.getUsers.totalCount;
|
|
||||||
return {
|
|
||||||
users,
|
|
||||||
pageInfo,
|
|
||||||
totalCount,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,11 +1,11 @@
|
|||||||
import { compare } from 'compare-versions';
|
import { compare } from 'compare-versions';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import useNotifications from 'hooks/useNotifications';
|
import useAutomatischNotifications from 'hooks/useAutomatischNotifications';
|
||||||
import api from 'helpers/api';
|
import api from 'helpers/api';
|
||||||
|
|
||||||
export default function useVersion() {
|
export default function useVersion() {
|
||||||
const { notifications } = useNotifications();
|
const { data: notificationsData } = useAutomatischNotifications();
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
queryKey: ['automatischVersion'],
|
queryKey: ['automatischVersion'],
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
@@ -17,6 +17,7 @@ export default function useVersion() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const version = data?.data?.version;
|
const version = data?.data?.version;
|
||||||
|
const notifications = notificationsData?.data || [];
|
||||||
|
|
||||||
const newVersionCount = notifications.reduce((count, notification) => {
|
const newVersionCount = notifications.reduce((count, notification) => {
|
||||||
if (!version) return 0;
|
if (!version) return 0;
|
||||||
|
@@ -6,6 +6,7 @@ import MuiTextField from '@mui/material/TextField';
|
|||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
@@ -29,6 +30,7 @@ export default function CreateUser() {
|
|||||||
const { data, loading: isRolesLoading } = useRoles();
|
const { data, loading: isRolesLoading } = useRoles();
|
||||||
const roles = data?.data;
|
const roles = data?.data;
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const handleUserCreation = async (userData) => {
|
const handleUserCreation = async (userData) => {
|
||||||
try {
|
try {
|
||||||
@@ -44,7 +46,7 @@ export default function CreateUser() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
|
||||||
enqueueSnackbar(formatMessage('createUser.successfullyCreated'), {
|
enqueueSnackbar(formatMessage('createUser.successfullyCreated'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
persist: true,
|
persist: true,
|
||||||
|
@@ -7,6 +7,7 @@ import MuiTextField from '@mui/material/TextField';
|
|||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
@@ -18,7 +19,7 @@ import * as URLS from 'config/urls';
|
|||||||
import { UPDATE_USER } from 'graphql/mutations/update-user.ee';
|
import { UPDATE_USER } from 'graphql/mutations/update-user.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useRoles from 'hooks/useRoles.ee';
|
import useRoles from 'hooks/useRoles.ee';
|
||||||
import useUser from 'hooks/useUser';
|
import useAdminUser from 'hooks/useAdminUser';
|
||||||
|
|
||||||
function generateRoleOptions(roles) {
|
function generateRoleOptions(roles) {
|
||||||
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
||||||
@@ -28,12 +29,13 @@ export default function EditUser() {
|
|||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [updateUser, { loading }] = useMutation(UPDATE_USER);
|
const [updateUser, { loading }] = useMutation(UPDATE_USER);
|
||||||
const { userId } = useParams();
|
const { userId } = useParams();
|
||||||
const { data: userData, isLoading: isUserLoading } = useUser({ userId });
|
const { data: userData, isLoading: isUserLoading } = useAdminUser({ userId });
|
||||||
const user = userData?.data;
|
const user = userData?.data;
|
||||||
const { data, isLoading: isRolesLoading } = useRoles();
|
const { data, isLoading: isRolesLoading } = useRoles();
|
||||||
const roles = data?.data;
|
const roles = data?.data;
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const handleUserUpdate = async (userDataToUpdate) => {
|
const handleUserUpdate = async (userDataToUpdate) => {
|
||||||
try {
|
try {
|
||||||
@@ -49,6 +51,8 @@ export default function EditUser() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'user', userId] });
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('editUser.successfullyUpdated'), {
|
enqueueSnackbar(formatMessage('editUser.successfullyUpdated'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { useLazyQuery } from '@apollo/client';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
@@ -9,6 +8,7 @@ import CircularProgress from '@mui/material/CircularProgress';
|
|||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import Pagination from '@mui/material/Pagination';
|
import Pagination from '@mui/material/Pagination';
|
||||||
import PaginationItem from '@mui/material/PaginationItem';
|
import PaginationItem from '@mui/material/PaginationItem';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import FlowRow from 'components/FlowRow';
|
import FlowRow from 'components/FlowRow';
|
||||||
import NoResultFound from 'components/NoResultFound';
|
import NoResultFound from 'components/NoResultFound';
|
||||||
@@ -17,45 +17,37 @@ import Container from 'components/Container';
|
|||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import SearchInput from 'components/SearchInput';
|
import SearchInput from 'components/SearchInput';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { GET_FLOWS } from 'graphql/queries/get-flows';
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
const FLOW_PER_PAGE = 10;
|
import useLazyFlows from 'hooks/useLazyFlows';
|
||||||
const getLimitAndOffset = (page) => ({
|
|
||||||
limit: FLOW_PER_PAGE,
|
|
||||||
offset: (page - 1) * FLOW_PER_PAGE,
|
|
||||||
});
|
|
||||||
export default function Flows() {
|
export default function Flows() {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const page = parseInt(searchParams.get('page') || '', 10) || 1;
|
const page = parseInt(searchParams.get('page') || '', 10) || 1;
|
||||||
const [flowName, setFlowName] = React.useState('');
|
const [flowName, setFlowName] = React.useState('');
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
const [getFlows, { data }] = useLazyQuery(GET_FLOWS, {
|
|
||||||
onCompleted: () => {
|
const { data, mutate } = useLazyFlows(
|
||||||
setLoading(false);
|
{ flowName, page },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
setIsLoading(false);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
|
||||||
const fetchData = React.useMemo(
|
|
||||||
() =>
|
|
||||||
debounce(
|
|
||||||
(name) =>
|
|
||||||
getFlows({
|
|
||||||
variables: {
|
|
||||||
...getLimitAndOffset(page),
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
300,
|
|
||||||
),
|
|
||||||
[page, getFlows],
|
|
||||||
);
|
|
||||||
React.useEffect(
|
|
||||||
function fetchFlowsOnSearch() {
|
|
||||||
setLoading(true);
|
|
||||||
fetchData(flowName);
|
|
||||||
},
|
|
||||||
[fetchData, flowName],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchData = React.useMemo(() => debounce(mutate, 300), [mutate]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
fetchData({ flowName, page });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
fetchData.cancel();
|
||||||
|
};
|
||||||
|
}, [fetchData, flowName, page]);
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function resetPageOnSearch() {
|
function resetPageOnSearch() {
|
||||||
// reset search params which only consists of `page`
|
// reset search params which only consists of `page`
|
||||||
@@ -63,17 +55,15 @@ export default function Flows() {
|
|||||||
},
|
},
|
||||||
[flowName],
|
[flowName],
|
||||||
);
|
);
|
||||||
React.useEffect(function cancelDebounceOnUnmount() {
|
|
||||||
return () => {
|
const flows = data?.data || [];
|
||||||
fetchData.cancel();
|
const pageInfo = data?.meta;
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
const { pageInfo, edges } = data?.getFlows || {};
|
|
||||||
const flows = edges?.map(({ node }) => node);
|
|
||||||
const hasFlows = flows?.length;
|
const hasFlows = flows?.length;
|
||||||
|
|
||||||
const onSearchChange = React.useCallback((event) => {
|
const onSearchChange = React.useCallback((event) => {
|
||||||
setFlowName(event.target.value);
|
setFlowName(event.target.value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ py: 3 }}>
|
<Box sx={{ py: 3 }}>
|
||||||
<Container>
|
<Container>
|
||||||
@@ -116,18 +106,18 @@ export default function Flows() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Divider sx={{ mt: [2, 0], mb: 2 }} />
|
<Divider sx={{ mt: [2, 0], mb: 2 }} />
|
||||||
{loading && (
|
{isLoading && (
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
)}
|
)}
|
||||||
{!loading &&
|
{!isLoading &&
|
||||||
flows?.map((flow) => <FlowRow key={flow.id} flow={flow} />)}
|
flows?.map((flow) => <FlowRow key={flow.id} flow={flow} />)}
|
||||||
{!loading && !hasFlows && (
|
{!isLoading && !hasFlows && (
|
||||||
<NoResultFound
|
<NoResultFound
|
||||||
text={formatMessage('flows.noFlows')}
|
text={formatMessage('flows.noFlows')}
|
||||||
to={URLS.CREATE_FLOW}
|
to={URLS.CREATE_FLOW}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!loading && pageInfo && pageInfo.totalPages > 1 && (
|
{!isLoading && pageInfo && pageInfo.totalPages > 1 && (
|
||||||
<Pagination
|
<Pagination
|
||||||
sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}
|
sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}
|
||||||
page={pageInfo?.currentPage}
|
page={pageInfo?.currentPage}
|
||||||
|
@@ -9,14 +9,15 @@ import PageTitle from 'components/PageTitle';
|
|||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useNotifications from 'hooks/useNotifications';
|
import useAutomatischNotifications from 'hooks/useAutomatischNotifications';
|
||||||
|
|
||||||
export default function Updates() {
|
export default function Updates() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { notifications } = useNotifications();
|
const { data: notificationsData } = useAutomatischNotifications();
|
||||||
const { data: automatischInfo, isPending } = useAutomatischInfo();
|
const { data: automatischInfo, isPending } = useAutomatischInfo();
|
||||||
const isMation = automatischInfo?.data.isMation;
|
const isMation = automatischInfo?.data.isMation;
|
||||||
|
const notifications = notificationsData?.data || [];
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function redirectToHomepageInMation() {
|
function redirectToHomepageInMation() {
|
||||||
|
@@ -3,43 +3,44 @@ import LoadingButton from '@mui/lab/LoadingButton';
|
|||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Skeleton from '@mui/material/Skeleton';
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import ColorInput from 'components/ColorInput';
|
import ColorInput from 'components/ColorInput';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Form from 'components/Form';
|
import Form from 'components/Form';
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import TextField from 'components/TextField';
|
import TextField from 'components/TextField';
|
||||||
import { UPDATE_CONFIG } from 'graphql/mutations/update-config.ee';
|
import { UPDATE_CONFIG } from 'graphql/mutations/update-config.ee';
|
||||||
import { GET_CONFIG } from 'graphql/queries/get-config.ee';
|
|
||||||
import nestObject from 'helpers/nestObject';
|
import nestObject from 'helpers/nestObject';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import {
|
import {
|
||||||
primaryDarkColor,
|
primaryDarkColor,
|
||||||
primaryLightColor,
|
primaryLightColor,
|
||||||
primaryMainColor,
|
primaryMainColor,
|
||||||
} from 'styles/theme';
|
} from 'styles/theme';
|
||||||
|
|
||||||
const getPrimaryMainColor = (color) => color || primaryMainColor;
|
const getPrimaryMainColor = (color) => color || primaryMainColor;
|
||||||
const getPrimaryDarkColor = (color) => color || primaryDarkColor;
|
const getPrimaryDarkColor = (color) => color || primaryDarkColor;
|
||||||
const getPrimaryLightColor = (color) => color || primaryLightColor;
|
const getPrimaryLightColor = (color) => color || primaryLightColor;
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
title: 'Automatisch',
|
title: 'Automatisch',
|
||||||
'palette.primary.main': primaryMainColor,
|
'palette.primary.main': primaryMainColor,
|
||||||
'palette.primary.dark': primaryDarkColor,
|
'palette.primary.dark': primaryDarkColor,
|
||||||
'palette.primary.light': primaryLightColor,
|
'palette.primary.light': primaryLightColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function UserInterface() {
|
export default function UserInterface() {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [updateConfig, { loading }] = useMutation(UPDATE_CONFIG);
|
const [updateConfig, { loading }] = useMutation(UPDATE_CONFIG);
|
||||||
const { config, loading: configLoading } = useConfig([
|
const { data: configData, isLoading: configLoading } = useAutomatischConfig();
|
||||||
'title',
|
const config = configData?.data;
|
||||||
'palette.primary.main',
|
const queryClient = useQueryClient();
|
||||||
'palette.primary.light',
|
|
||||||
'palette.primary.dark',
|
|
||||||
'logo.svgData',
|
|
||||||
]);
|
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const configWithDefaults = merge({}, defaultValues, nestObject(config));
|
const configWithDefaults = merge({}, defaultValues, nestObject(config));
|
||||||
const handleUserInterfaceUpdate = async (uiData) => {
|
const handleUserInterfaceUpdate = async (uiData) => {
|
||||||
@@ -64,37 +65,9 @@ export default function UserInterface() {
|
|||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
updateConfig: input,
|
updateConfig: input,
|
||||||
},
|
},
|
||||||
update: async function (cache, { data: { updateConfig } }) {
|
update: async function () {
|
||||||
const newConfigWithDefaults = merge({}, defaultValues, updateConfig);
|
queryClient.invalidateQueries({
|
||||||
cache.writeQuery({
|
queryKey: ['automatisch', 'config'],
|
||||||
query: GET_CONFIG,
|
|
||||||
data: {
|
|
||||||
getConfig: newConfigWithDefaults,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cache.writeQuery({
|
|
||||||
query: GET_CONFIG,
|
|
||||||
data: {
|
|
||||||
getConfig: newConfigWithDefaults,
|
|
||||||
},
|
|
||||||
variables: {
|
|
||||||
keys: ['logo.svgData'],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cache.writeQuery({
|
|
||||||
query: GET_CONFIG,
|
|
||||||
data: {
|
|
||||||
getConfig: newConfigWithDefaults,
|
|
||||||
},
|
|
||||||
variables: {
|
|
||||||
keys: [
|
|
||||||
'title',
|
|
||||||
'palette.primary.main',
|
|
||||||
'palette.primary.light',
|
|
||||||
'palette.primary.dark',
|
|
||||||
'logo.svgData',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Route, Routes as ReactRouterRoutes, Navigate } from 'react-router-dom';
|
import { Route, Routes as ReactRouterRoutes, Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
import Layout from 'components/Layout';
|
import Layout from 'components/Layout';
|
||||||
import NoResultFound from 'components/NotFound';
|
import NoResultFound from 'components/NotFound';
|
||||||
import PublicLayout from 'components/PublicLayout';
|
import PublicLayout from 'components/PublicLayout';
|
||||||
@@ -19,11 +20,14 @@ import * as URLS from 'config/urls';
|
|||||||
import settingsRoutes from './settingsRoutes';
|
import settingsRoutes from './settingsRoutes';
|
||||||
import adminSettingsRoutes from './adminSettingsRoutes';
|
import adminSettingsRoutes from './adminSettingsRoutes';
|
||||||
import Notifications from 'pages/Notifications';
|
import Notifications from 'pages/Notifications';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
import useAuthentication from 'hooks/useAuthentication';
|
import useAuthentication from 'hooks/useAuthentication';
|
||||||
|
|
||||||
function Routes() {
|
function Routes() {
|
||||||
const { config } = useConfig();
|
const { data: configData } = useAutomatischConfig();
|
||||||
const { isAuthenticated } = useAuthentication();
|
const { isAuthenticated } = useAuthentication();
|
||||||
|
const config = configData?.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactRouterRoutes>
|
<ReactRouterRoutes>
|
||||||
<Route
|
<Route
|
||||||
@@ -147,4 +151,5 @@ function Routes() {
|
|||||||
</ReactRouterRoutes>
|
</ReactRouterRoutes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default <Routes />;
|
export default <Routes />;
|
||||||
|
11
yarn.lock
11
yarn.lock
@@ -5471,6 +5471,11 @@ basic-auth@^2.0.1, basic-auth@~2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "5.1.2"
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
|
basic-ftp@^5.0.5:
|
||||||
|
version "5.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0"
|
||||||
|
integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==
|
||||||
|
|
||||||
batch@0.6.1:
|
batch@0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
|
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
|
||||||
@@ -8379,9 +8384,9 @@ fn.name@1.x.x:
|
|||||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||||
|
|
||||||
follow-redirects@^1.0.0, follow-redirects@^1.14.8, follow-redirects@^1.15.0:
|
follow-redirects@^1.0.0, follow-redirects@^1.14.8, follow-redirects@^1.15.0:
|
||||||
version "1.15.3"
|
version "1.15.6"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||||
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
|
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
forever-agent@~0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
|
Reference in New Issue
Block a user