Compare commits
63 Commits
test-docs-
...
static-can
Author | SHA1 | Date | |
---|---|---|---|
![]() |
40636119fb | ||
![]() |
beb3b2cf45 | ||
![]() |
c2375ed3d4 | ||
![]() |
4942cf8dae | ||
![]() |
8f444eafa7 | ||
![]() |
2484a0e631 | ||
![]() |
d3747ad050 | ||
![]() |
bb68a75636 | ||
![]() |
98131d633e | ||
![]() |
e8193e0e17 | ||
![]() |
74b7dd8f34 | ||
![]() |
4f500e2d04 | ||
![]() |
b53ddca8ce | ||
![]() |
70f30034ab | ||
![]() |
fcd83909f7 | ||
![]() |
eadb472af9 | ||
![]() |
600316577e | ||
![]() |
9aa48c20e4 | ||
![]() |
1b5d3beeca | ||
![]() |
00115d313e | ||
![]() |
190f1a205f | ||
![]() |
8ab6f0c3fe | ||
![]() |
13eea263c0 | ||
![]() |
b52b40962e | ||
![]() |
7d1fa2e40c | ||
![]() |
93b2098829 | ||
![]() |
a2acdc6b12 | ||
![]() |
38b2c1e30f | ||
![]() |
e07f579f3c | ||
![]() |
df3297b6ca | ||
![]() |
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 |
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
import User from '../../../../models/user.js';
|
||||||
|
import { renderObject, renderError } from '../../../../helpers/renderer.js';
|
||||||
|
|
||||||
|
export default async (request, response) => {
|
||||||
|
const { email, password } = request.body;
|
||||||
|
const token = await User.authenticate(email, password);
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
return renderObject(response, { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError(response, [{ general: ['Incorrect email or password.'] }]);
|
||||||
|
};
|
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import app from '../../../../app.js';
|
||||||
|
import { createUser } from '../../../../../test/factories/user';
|
||||||
|
|
||||||
|
describe('POST /api/v1/access-tokens', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createUser({
|
||||||
|
email: 'user@automatisch.io',
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the token data with correct credentials', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/v1/access-tokens')
|
||||||
|
.send({
|
||||||
|
email: 'user@automatisch.io',
|
||||||
|
password: 'password',
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.data.token.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error with incorrect credentials', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/v1/access-tokens')
|
||||||
|
.send({
|
||||||
|
email: 'incorrect@email.com',
|
||||||
|
password: 'incorrectpassword',
|
||||||
|
})
|
||||||
|
.expect(422);
|
||||||
|
|
||||||
|
expect(response.body.errors.general).toEqual([
|
||||||
|
'Incorrect email or password.',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,52 +0,0 @@
|
|||||||
import { vi, 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.js';
|
|
||||||
import { createUser } from '../../../../../../test/factories/user.js';
|
|
||||||
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.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/:appAuthClientId', () => {
|
|
||||||
let currentUser, currentUserRole, currentAppAuthClient, 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 });
|
|
||||||
currentAppAuthClient = await createAppAuthClient();
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return specified app auth client info', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/admin/app-auth-clients/${currentAppAuthClient.id}`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAdminAppAuthClientMock(currentAppAuthClient);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for not existing app auth client UUID', async () => {
|
|
||||||
const notExistingAppAuthClientUUID = Crypto.randomUUID();
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/api/v1/admin/app-auth-clients/${notExistingAppAuthClientUUID}`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return bad request response for invalid UUID', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/admin/app-auth-clients/invalidAppAuthClientUUID')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(400);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -4,6 +4,7 @@ import AppAuthClient from '../../../../../models/app-auth-client.js';
|
|||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const appAuthClient = await AppAuthClient.query()
|
const appAuthClient = await AppAuthClient.query()
|
||||||
.findById(request.params.appAuthClientId)
|
.findById(request.params.appAuthClientId)
|
||||||
|
.where({ app_key: request.params.appKey })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
renderObject(response, appAuthClient);
|
renderObject(response, appAuthClient);
|
@@ -0,0 +1,55 @@
|
|||||||
|
import { vi, 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.js';
|
||||||
|
import { createUser } from '../../../../../../test/factories/user.js';
|
||||||
|
import { createRole } from '../../../../../../test/factories/role.js';
|
||||||
|
import getAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-client.js';
|
||||||
|
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
|
||||||
|
import * as license from '../../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
|
||||||
|
let currentUser, adminRole, currentAppAuthClient, token;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
|
adminRole = await createRole({ key: 'admin' });
|
||||||
|
currentUser = await createUser({ roleId: adminRole.id });
|
||||||
|
|
||||||
|
currentAppAuthClient = await createAppAuthClient({
|
||||||
|
appKey: 'deepl',
|
||||||
|
});
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return specified app auth client', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/v1/admin/apps/deepl/auth-clients/${currentAppAuthClient.id}`)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return not found response for not existing app auth client ID', async () => {
|
||||||
|
const notExistingAppAuthClientUUID = Crypto.randomUUID();
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.get(
|
||||||
|
`/api/v1/admin/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`
|
||||||
|
)
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return bad request response for invalid UUID', async () => {
|
||||||
|
await request(app)
|
||||||
|
.get('/api/v1/admin/apps/deepl/auth-clients/invalidAppAuthClientUUID')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
|
});
|
@@ -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({ app_key: request.params.appKey })
|
||||||
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
renderObject(response, appAuthClients);
|
||||||
|
};
|
@@ -0,0 +1,44 @@
|
|||||||
|
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 { createRole } from '../../../../../../test/factories/role.js';
|
||||||
|
import getAuthClientsMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-clients.js';
|
||||||
|
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
|
||||||
|
import * as license from '../../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
|
||||||
|
let currentUser, adminRole, token;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
|
adminRole = await createRole({ key: 'admin' });
|
||||||
|
currentUser = await createUser({ roleId: adminRole.id });
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return specified app auth client info', async () => {
|
||||||
|
const appAuthClientOne = await createAppAuthClient({
|
||||||
|
appKey: 'deepl',
|
||||||
|
});
|
||||||
|
|
||||||
|
const appAuthClientTwo = await createAppAuthClient({
|
||||||
|
appKey: 'deepl',
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/v1/admin/apps/deepl/auth-clients')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = getAuthClientsMock([
|
||||||
|
appAuthClientTwo,
|
||||||
|
appAuthClientOne,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
});
|
@@ -4,7 +4,7 @@ import AppAuthClient from '../../../../models/app-auth-client.js';
|
|||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const appAuthClient = await AppAuthClient.query()
|
const appAuthClient = await AppAuthClient.query()
|
||||||
.findById(request.params.appAuthClientId)
|
.findById(request.params.appAuthClientId)
|
||||||
.where({ active: true })
|
.where({ app_key: request.params.appKey, active: true })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
renderObject(response, appAuthClient);
|
renderObject(response, appAuthClient);
|
@@ -4,25 +4,27 @@ 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/apps/get-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';
|
||||||
|
|
||||||
describe('GET /api/v1/app-auth-clients/:id', () => {
|
describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => {
|
||||||
let currentUser, currentAppAuthClient, token;
|
let currentUser, currentAppAuthClient, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
|
|
||||||
currentUser = await createUser();
|
currentUser = await createUser();
|
||||||
currentAppAuthClient = await createAppAuthClient();
|
currentAppAuthClient = await createAppAuthClient({
|
||||||
|
appKey: 'deepl',
|
||||||
|
});
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return specified app auth client info', async () => {
|
it('should return specified app auth client', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`)
|
.get(`/api/v1/apps/deepl/auth-clients/${currentAppAuthClient.id}`)
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
@@ -34,14 +36,14 @@ describe('GET /api/v1/app-auth-clients/:id', () => {
|
|||||||
const notExistingAppAuthClientUUID = Crypto.randomUUID();
|
const notExistingAppAuthClientUUID = Crypto.randomUUID();
|
||||||
|
|
||||||
await request(app)
|
await request(app)
|
||||||
.get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`)
|
.get(`/api/v1/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`)
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return bad request response for invalid UUID', async () => {
|
it('should return bad request response for invalid UUID', async () => {
|
||||||
await request(app)
|
await request(app)
|
||||||
.get('/api/v1/app-auth-clients/invalidAppAuthClientUUID')
|
.get('/api/v1/apps/deepl/auth-clients/invalidAppAuthClientUUID')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
@@ -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({ app_key: request.params.appKey, active: true })
|
||||||
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
renderObject(response, appAuthClients);
|
||||||
|
};
|
@@ -0,0 +1,42 @@
|
|||||||
|
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 getAuthClientsMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth-clients.js';
|
||||||
|
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
|
||||||
|
import * as license from '../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
describe('GET /api/v1/apps/:appKey/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({
|
||||||
|
appKey: 'deepl',
|
||||||
|
});
|
||||||
|
|
||||||
|
const appAuthClientTwo = await createAppAuthClient({
|
||||||
|
appKey: 'deepl',
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/v1/apps/deepl/auth-clients')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedPayload = getAuthClientsMock([
|
||||||
|
appAuthClientTwo,
|
||||||
|
appAuthClientOne,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
|
});
|
||||||
|
});
|
@@ -3,11 +3,11 @@ import request from 'supertest';
|
|||||||
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 getAppConfigMock from '../../../../../test/mocks/rest/api/v1/app-configs/get-app-config.js';
|
import getAppConfigMock from '../../../../../test/mocks/rest/api/v1/apps/get-config.js';
|
||||||
import { createAppConfig } from '../../../../../test/factories/app-config.js';
|
import { createAppConfig } from '../../../../../test/factories/app-config.js';
|
||||||
import * as license from '../../../../helpers/license.ee.js';
|
import * as license from '../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
describe('GET /api/v1/app-configs/:appKey', () => {
|
describe('GET /api/v1/apps/:appKey/config', () => {
|
||||||
let currentUser, appConfig, token;
|
let currentUser, appConfig, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -27,7 +27,7 @@ describe('GET /api/v1/app-configs/:appKey', () => {
|
|||||||
|
|
||||||
it('should return specified app config info', async () => {
|
it('should return specified app config info', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.get(`/api/v1/app-configs/${appConfig.key}`)
|
.get(`/api/v1/apps/${appConfig.key}/config`)
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ describe('GET /api/v1/app-configs/:appKey', () => {
|
|||||||
|
|
||||||
it('should return not found response for not existing app key', async () => {
|
it('should return not found response for not existing app key', async () => {
|
||||||
await request(app)
|
await request(app)
|
||||||
.get('/api/v1/app-configs/not-existing-app-key')
|
.get('/api/v1/apps/not-existing-app-key/config')
|
||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
@@ -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);
|
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
export async function up(knex) {
|
||||||
|
await knex.schema.table('app_auth_clients', (table) => {
|
||||||
|
table.string('app_key');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex) {
|
||||||
|
await knex.schema.table('app_auth_clients', (table) => {
|
||||||
|
table.dropColumn('app_key');
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
export async function up(knex) {
|
||||||
|
const appAuthClients = await knex('app_auth_clients').select('*');
|
||||||
|
|
||||||
|
for (const appAuthClient of appAuthClients) {
|
||||||
|
const appConfig = await knex('app_configs')
|
||||||
|
.where('id', appAuthClient.app_config_id)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
await knex('app_auth_clients')
|
||||||
|
.where('id', appAuthClient.id)
|
||||||
|
.update({ app_key: appConfig.key });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down() {
|
||||||
|
// void
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
export async function up(knex) {
|
||||||
|
await knex.schema.table('app_auth_clients', (table) => {
|
||||||
|
table.dropColumn('app_config_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex) {
|
||||||
|
await knex.schema.table('app_auth_clients', (table) => {
|
||||||
|
table
|
||||||
|
.uuid('app_config_id')
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('app_configs');
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
export async function up(knex) {
|
||||||
|
await knex.schema.table('app_auth_clients', (table) => {
|
||||||
|
table.string('app_key').notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex) {
|
||||||
|
await knex.schema.table('app_auth_clients', (table) => {
|
||||||
|
table.string('app_key').nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
export async function up(knex) {
|
||||||
|
await knex.schema.table('app_configs', (table) => {
|
||||||
|
table.boolean('can_connect').defaultTo(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex) {
|
||||||
|
await knex.schema.table('app_configs', (table) => {
|
||||||
|
table.dropColumn('can_connect');
|
||||||
|
});
|
||||||
|
}
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,40 +0,0 @@
|
|||||||
import App from '../../models/app.js';
|
|
||||||
import Step from '../../models/step.js';
|
|
||||||
import globalVariable from '../../helpers/global-variable.js';
|
|
||||||
|
|
||||||
const getDynamicFields = async (_parent, params, context) => {
|
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
|
||||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
|
||||||
const allSteps = Step.query();
|
|
||||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
|
||||||
|
|
||||||
const step = await stepBaseQuery
|
|
||||||
.clone()
|
|
||||||
.withGraphFetched({
|
|
||||||
connection: true,
|
|
||||||
flow: true,
|
|
||||||
})
|
|
||||||
.findById(params.stepId);
|
|
||||||
|
|
||||||
if (!step) return null;
|
|
||||||
|
|
||||||
const connection = step.connection;
|
|
||||||
|
|
||||||
if (!step.appKey) return null;
|
|
||||||
|
|
||||||
const app = await App.findOneByKey(step.appKey);
|
|
||||||
const $ = await globalVariable({ connection, app, flow: step.flow, step });
|
|
||||||
|
|
||||||
const command = app.dynamicFields.find((data) => data.key === params.key);
|
|
||||||
|
|
||||||
for (const parameterKey in params.parameters) {
|
|
||||||
const parameterValue = params.parameters[parameterKey];
|
|
||||||
$.step.parameters[parameterKey] = parameterValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalFields = (await command.run($)) || [];
|
|
||||||
|
|
||||||
return additionalFields;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getDynamicFields;
|
|
@@ -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,10 @@ 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 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 +13,10 @@ const queryResolvers = {
|
|||||||
getAppAuthClient,
|
getAppAuthClient,
|
||||||
getAppAuthClients,
|
getAppAuthClients,
|
||||||
getBillingAndUsage,
|
getBillingAndUsage,
|
||||||
getConfig,
|
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
getDynamicData,
|
getDynamicData,
|
||||||
getDynamicFields,
|
|
||||||
getFlow,
|
getFlow,
|
||||||
getFlows,
|
|
||||||
getNotifications,
|
|
||||||
getStepWithTestExecutions,
|
getStepWithTestExecutions,
|
||||||
getUsers,
|
|
||||||
testConnection,
|
testConnection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -5,28 +5,13 @@ 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!
|
||||||
key: String!
|
key: String!
|
||||||
parameters: JSONObject
|
parameters: JSONObject
|
||||||
): JSONObject
|
): JSONObject
|
||||||
getDynamicFields(
|
|
||||||
stepId: String!
|
|
||||||
key: String!
|
|
||||||
parameters: JSONObject
|
|
||||||
): [SubstepArgument]
|
|
||||||
getBillingAndUsage: GetBillingAndUsage
|
getBillingAndUsage: GetBillingAndUsage
|
||||||
getConfig(keys: [String]): JSONObject
|
|
||||||
getNotifications: [Notification]
|
|
||||||
getUsers(limit: Int!, offset: Int!): UserConnection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
@@ -257,15 +242,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 +280,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 +658,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',
|
||||||
|
@@ -9,7 +9,7 @@ const stream = {
|
|||||||
const registerGraphQLToken = () => {
|
const registerGraphQLToken = () => {
|
||||||
morgan.token('graphql-query', (req) => {
|
morgan.token('graphql-query', (req) => {
|
||||||
if (req.body.query) {
|
if (req.body.query) {
|
||||||
return `GraphQL ${req.body.query}`;
|
return `\n GraphQL ${req.body.query}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -17,7 +17,7 @@ const registerGraphQLToken = () => {
|
|||||||
registerGraphQLToken();
|
registerGraphQLToken();
|
||||||
|
|
||||||
const morganMiddleware = morgan(
|
const morganMiddleware = morgan(
|
||||||
':method :url :status :res[content-length] - :response-time ms\n:graphql-query',
|
':method :url :status :res[content-length] - :response-time ms :graphql-query',
|
||||||
{ stream }
|
{ stream }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -44,4 +44,22 @@ const renderObject = (response, object, options) => {
|
|||||||
return response.json(computedPayload);
|
return response.json(computedPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { renderObject };
|
const renderError = (response, errors, status, type) => {
|
||||||
|
const errorStatus = status || 422;
|
||||||
|
const errorType = type || 'ValidationError';
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
errors: errors.reduce((acc, error) => {
|
||||||
|
const key = Object.keys(error)[0];
|
||||||
|
acc[key] = error[key];
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
meta: {
|
||||||
|
type: errorType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return response.status(errorStatus).send(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { renderObject, renderError };
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,11 @@ class AppAuthClient extends Base {
|
|||||||
|
|
||||||
static jsonSchema = {
|
static jsonSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['name', 'appConfigId', 'formattedAuthDefaults'],
|
required: ['name', 'appKey', 'formattedAuthDefaults'],
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
appConfigId: { type: 'string', format: 'uuid' },
|
appKey: { type: 'string' },
|
||||||
active: { type: 'boolean' },
|
active: { type: 'boolean' },
|
||||||
authDefaults: { type: ['string', 'null'] },
|
authDefaults: { type: ['string', 'null'] },
|
||||||
formattedAuthDefaults: { type: 'object' },
|
formattedAuthDefaults: { type: 'object' },
|
||||||
@@ -22,17 +22,6 @@ class AppAuthClient extends Base {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static relationMappings = () => ({
|
|
||||||
appConfig: {
|
|
||||||
relation: Base.BelongsToOneRelation,
|
|
||||||
modelClass: AppConfig,
|
|
||||||
join: {
|
|
||||||
from: 'app_auth_clients.app_config_id',
|
|
||||||
to: 'app_configs.id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
encryptData() {
|
encryptData() {
|
||||||
if (!this.eligibleForEncryption()) return;
|
if (!this.eligibleForEncryption()) return;
|
||||||
|
|
||||||
@@ -71,6 +60,21 @@ class AppAuthClient extends Base {
|
|||||||
this.encryptData();
|
this.encryptData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async assignCanConnectForAppConfig() {
|
||||||
|
const appConfig = await AppConfig.query().findOne({ key: this.appKey });
|
||||||
|
await appConfig?.assignCanConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async $afterInsert(queryContext) {
|
||||||
|
await super.$afterInsert(queryContext);
|
||||||
|
await this.assignCanConnectForAppConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
async $afterUpdate(opt, queryContext) {
|
||||||
|
await super.$afterUpdate(opt, queryContext);
|
||||||
|
await this.assignCanConnectForAppConfig();
|
||||||
|
}
|
||||||
|
|
||||||
async $afterFind() {
|
async $afterFind() {
|
||||||
this.decryptData();
|
this.decryptData();
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import App from './app.js';
|
import App from './app.js';
|
||||||
import Base from './base.js';
|
|
||||||
import AppAuthClient from './app-auth-client.js';
|
import AppAuthClient from './app-auth-client.js';
|
||||||
|
import Base from './base.js';
|
||||||
|
|
||||||
class AppConfig extends Base {
|
class AppConfig extends Base {
|
||||||
static tableName = 'app_configs';
|
static tableName = 'app_configs';
|
||||||
@@ -15,45 +15,48 @@ class AppConfig extends Base {
|
|||||||
allowCustomConnection: { type: 'boolean', default: false },
|
allowCustomConnection: { type: 'boolean', default: false },
|
||||||
shared: { type: 'boolean', default: false },
|
shared: { type: 'boolean', default: false },
|
||||||
disabled: { type: 'boolean', default: false },
|
disabled: { type: 'boolean', default: false },
|
||||||
|
canConnect: { type: 'boolean', default: false },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static get virtualAttributes() {
|
|
||||||
return ['canConnect', 'canCustomConnect'];
|
|
||||||
}
|
|
||||||
|
|
||||||
static relationMappings = () => ({
|
|
||||||
appAuthClients: {
|
|
||||||
relation: Base.HasManyRelation,
|
|
||||||
modelClass: AppAuthClient,
|
|
||||||
join: {
|
|
||||||
from: 'app_configs.id',
|
|
||||||
to: 'app_auth_clients.app_config_id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
get canCustomConnect() {
|
|
||||||
return !this.disabled && this.allowCustomConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canConnect() {
|
|
||||||
const hasSomeActiveAppAuthClients = !!this.appAuthClients?.some(
|
|
||||||
(appAuthClient) => appAuthClient.active
|
|
||||||
);
|
|
||||||
const shared = this.shared;
|
|
||||||
const active = this.disabled === false;
|
|
||||||
|
|
||||||
const conditions = [hasSomeActiveAppAuthClients, shared, active];
|
|
||||||
|
|
||||||
return conditions.every(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getApp() {
|
async getApp() {
|
||||||
if (!this.key) return null;
|
if (!this.key) return null;
|
||||||
|
|
||||||
return await App.findOneByKey(this.key);
|
return await App.findOneByKey(this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async hasActiveAppAuthClients() {
|
||||||
|
const appAuthClients = await AppAuthClient.query().where({
|
||||||
|
appKey: this.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasSomeActiveAppAuthClients = !!appAuthClients?.some(
|
||||||
|
(appAuthClient) => appAuthClient.active
|
||||||
|
);
|
||||||
|
|
||||||
|
return hasSomeActiveAppAuthClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
async assignCanConnect() {
|
||||||
|
const shared = this.shared;
|
||||||
|
const active = this.disabled === false;
|
||||||
|
const hasSomeActiveAppAuthClients = await this.hasActiveAppAuthClients();
|
||||||
|
|
||||||
|
const conditions = [hasSomeActiveAppAuthClients, shared, active];
|
||||||
|
const canConnect = conditions.every(Boolean);
|
||||||
|
|
||||||
|
this.canConnect = canConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $beforeInsert(queryContext) {
|
||||||
|
await super.$beforeInsert(queryContext);
|
||||||
|
await this.assignCanConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async $beforeUpdate(opt, queryContext) {
|
||||||
|
await super.$beforeUpdate(opt, queryContext);
|
||||||
|
await this.assignCanConnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppConfig;
|
export default AppConfig;
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import crypto from 'node:crypto';
|
|||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
import { hasValidLicense } from '../helpers/license.ee.js';
|
import { hasValidLicense } from '../helpers/license.ee.js';
|
||||||
import userAbility from '../helpers/user-ability.js';
|
import userAbility from '../helpers/user-ability.js';
|
||||||
|
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
|
||||||
import Base from './base.js';
|
import Base from './base.js';
|
||||||
import Connection from './connection.js';
|
import Connection from './connection.js';
|
||||||
import Execution from './execution.js';
|
import Execution from './execution.js';
|
||||||
@@ -161,6 +162,17 @@ class User extends Base {
|
|||||||
: Execution.query();
|
: Execution.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async authenticate(email, password) {
|
||||||
|
const user = await User.query().findOne({
|
||||||
|
email: email?.toLowerCase() || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user && (await user.login(password))) {
|
||||||
|
const token = createAuthTokenByUserId(user.id);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
login(password) {
|
login(password) {
|
||||||
return bcrypt.compare(password, this.password);
|
return bcrypt.compare(password, this.password);
|
||||||
}
|
}
|
||||||
|
9
packages/backend/src/routes/api/v1/access-tokens.js
Normal file
9
packages/backend/src/routes/api/v1/access-tokens.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import asyncHandler from 'express-async-handler';
|
||||||
|
import createAccessTokenAction from '../../../controllers/api/v1/access-tokens/create-access-token.js';
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post('/', asyncHandler(createAccessTokenAction));
|
||||||
|
|
||||||
|
export default router;
|
@@ -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 getAuthClientsAction from '../../../../controllers/api/v1/admin/apps/get-auth-clients.ee.js';
|
||||||
|
import getAuthClientAction from '../../../../controllers/api/v1/admin/apps/get-auth-client.ee.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:appAuthClientId',
|
'/:appKey/auth-clients',
|
||||||
authenticateUser,
|
authenticateUser,
|
||||||
authorizeAdmin,
|
authorizeAdmin,
|
||||||
checkIsEnterprise,
|
checkIsEnterprise,
|
||||||
asyncHandler(getAdminAppAuthClientsAction)
|
asyncHandler(getAuthClientsAction)
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:appKey/auth-clients/:appAuthClientId',
|
||||||
|
authenticateUser,
|
||||||
|
authorizeAdmin,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(getAuthClientAction)
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@@ -1,16 +0,0 @@
|
|||||||
import { Router } from 'express';
|
|
||||||
import asyncHandler from 'express-async-handler';
|
|
||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
|
||||||
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
|
|
||||||
import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:appAuthClientId',
|
|
||||||
authenticateUser,
|
|
||||||
checkIsEnterprise,
|
|
||||||
asyncHandler(getAppAuthClientAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
|
@@ -1,16 +0,0 @@
|
|||||||
import { Router } from 'express';
|
|
||||||
import asyncHandler from 'express-async-handler';
|
|
||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
|
||||||
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
|
|
||||||
import getAppConfigAction from '../../../controllers/api/v1/app-configs/get-app-config.ee.js';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:appKey',
|
|
||||||
authenticateUser,
|
|
||||||
checkIsEnterprise,
|
|
||||||
asyncHandler(getAppConfigAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
|
@@ -2,9 +2,13 @@ import { Router } from 'express';
|
|||||||
import asyncHandler from 'express-async-handler';
|
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 { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
|
||||||
import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
|
import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
|
||||||
import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js';
|
import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js';
|
||||||
import getAuthAction from '../../../controllers/api/v1/apps/get-auth.js';
|
import getAuthAction from '../../../controllers/api/v1/apps/get-auth.js';
|
||||||
|
import getConfigAction from '../../../controllers/api/v1/apps/get-config.ee.js';
|
||||||
|
import getAuthClientsAction from '../../../controllers/api/v1/apps/get-auth-clients.ee.js';
|
||||||
|
import getAuthClientAction from '../../../controllers/api/v1/apps/get-auth-client.ee.js';
|
||||||
import getTriggersAction from '../../../controllers/api/v1/apps/get-triggers.js';
|
import getTriggersAction from '../../../controllers/api/v1/apps/get-triggers.js';
|
||||||
import getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.js';
|
import getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.js';
|
||||||
import getActionsAction from '../../../controllers/api/v1/apps/get-actions.js';
|
import getActionsAction from '../../../controllers/api/v1/apps/get-actions.js';
|
||||||
@@ -17,6 +21,27 @@ router.get('/', authenticateUser, asyncHandler(getAppsAction));
|
|||||||
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
|
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
|
||||||
router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction));
|
router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction));
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:appKey/config',
|
||||||
|
authenticateUser,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(getConfigAction)
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:appKey/auth-clients',
|
||||||
|
authenticateUser,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(getAuthClientsAction)
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:appKey/auth-clients/:appAuthClientId',
|
||||||
|
authenticateUser,
|
||||||
|
checkIsEnterprise,
|
||||||
|
asyncHandler(getAuthClientAction)
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:appKey/triggers',
|
'/:appKey/triggers',
|
||||||
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;
|
||||||
|
@@ -4,21 +4,20 @@ import webhooksRouter from './webhooks.js';
|
|||||||
import paddleRouter from './paddle.ee.js';
|
import paddleRouter from './paddle.ee.js';
|
||||||
import healthcheckRouter from './healthcheck.js';
|
import healthcheckRouter from './healthcheck.js';
|
||||||
import automatischRouter from './api/v1/automatisch.js';
|
import automatischRouter from './api/v1/automatisch.js';
|
||||||
|
import accessTokensRouter from './api/v1/access-tokens.js';
|
||||||
import usersRouter from './api/v1/users.js';
|
import usersRouter from './api/v1/users.js';
|
||||||
import paymentRouter from './api/v1/payment.ee.js';
|
import paymentRouter from './api/v1/payment.ee.js';
|
||||||
import appAuthClientsRouter from './api/v1/app-auth-clients.js';
|
|
||||||
import appConfigsRouter from './api/v1/app-configs.ee.js';
|
|
||||||
import flowsRouter from './api/v1/flows.js';
|
import flowsRouter from './api/v1/flows.js';
|
||||||
import stepsRouter from './api/v1/steps.js';
|
import stepsRouter from './api/v1/steps.js';
|
||||||
import appsRouter from './api/v1/apps.js';
|
import appsRouter from './api/v1/apps.js';
|
||||||
import connectionsRouter from './api/v1/connections.js';
|
import connectionsRouter from './api/v1/connections.js';
|
||||||
import executionsRouter from './api/v1/executions.js';
|
import executionsRouter from './api/v1/executions.js';
|
||||||
import samlAuthProvidersRouter from './api/v1/saml-auth-providers.ee.js';
|
import samlAuthProvidersRouter from './api/v1/saml-auth-providers.ee.js';
|
||||||
|
import adminAppsRouter from './api/v1/admin/apps.ee.js';
|
||||||
import adminSamlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
|
import adminSamlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
|
||||||
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';
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -27,20 +26,19 @@ router.use('/webhooks', webhooksRouter);
|
|||||||
router.use('/paddle', paddleRouter);
|
router.use('/paddle', paddleRouter);
|
||||||
router.use('/healthcheck', healthcheckRouter);
|
router.use('/healthcheck', healthcheckRouter);
|
||||||
router.use('/api/v1/automatisch', automatischRouter);
|
router.use('/api/v1/automatisch', automatischRouter);
|
||||||
|
router.use('/api/v1/access-tokens', accessTokensRouter);
|
||||||
router.use('/api/v1/users', usersRouter);
|
router.use('/api/v1/users', usersRouter);
|
||||||
router.use('/api/v1/payment', paymentRouter);
|
router.use('/api/v1/payment', paymentRouter);
|
||||||
router.use('/api/v1/app-auth-clients', appAuthClientsRouter);
|
|
||||||
router.use('/api/v1/app-configs', appConfigsRouter);
|
|
||||||
router.use('/api/v1/flows', flowsRouter);
|
|
||||||
router.use('/api/v1/steps', stepsRouter);
|
|
||||||
router.use('/api/v1/apps', appsRouter);
|
router.use('/api/v1/apps', appsRouter);
|
||||||
router.use('/api/v1/connections', connectionsRouter);
|
router.use('/api/v1/connections', connectionsRouter);
|
||||||
|
router.use('/api/v1/flows', flowsRouter);
|
||||||
|
router.use('/api/v1/steps', stepsRouter);
|
||||||
router.use('/api/v1/executions', executionsRouter);
|
router.use('/api/v1/executions', executionsRouter);
|
||||||
router.use('/api/v1/saml-auth-providers', samlAuthProvidersRouter);
|
router.use('/api/v1/saml-auth-providers', samlAuthProvidersRouter);
|
||||||
router.use('/api/v1/admin/saml-auth-providers', adminSamlAuthProvidersRouter);
|
router.use('/api/v1/admin/apps', adminAppsRouter);
|
||||||
|
router.use('/api/v1/admin/users', adminUsersRouter);
|
||||||
router.use('/api/v1/admin/roles', rolesRouter);
|
router.use('/api/v1/admin/roles', rolesRouter);
|
||||||
router.use('/api/v1/admin/permissions', permissionsRouter);
|
router.use('/api/v1/admin/permissions', permissionsRouter);
|
||||||
router.use('/api/v1/admin/users', adminUsersRouter);
|
router.use('/api/v1/admin/saml-auth-providers', adminSamlAuthProvidersRouter);
|
||||||
router.use('/api/v1/admin/app-auth-clients', adminAppAuthClientsRouter);
|
|
||||||
|
|
||||||
export default router;
|
export default 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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { createAppConfig } from './app-config.js';
|
|
||||||
import AppAuthClient from '../../src/models/app-auth-client';
|
import AppAuthClient from '../../src/models/app-auth-client';
|
||||||
|
|
||||||
const formattedAuthDefaults = {
|
const formattedAuthDefaults = {
|
||||||
@@ -12,14 +11,12 @@ const formattedAuthDefaults = {
|
|||||||
export const createAppAuthClient = async (params = {}) => {
|
export const createAppAuthClient = async (params = {}) => {
|
||||||
params.name = params?.name || faker.person.fullName();
|
params.name = params?.name || faker.person.fullName();
|
||||||
params.id = params?.id || faker.string.uuid();
|
params.id = params?.id || faker.string.uuid();
|
||||||
params.appConfigId = params?.appConfigId || (await createAppConfig()).id;
|
params.appKey = params?.appKey || 'deepl';
|
||||||
params.active = params?.active ?? true;
|
params.active = params?.active ?? true;
|
||||||
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,18 @@
|
|||||||
|
const getAdminAppAuthClientsMock = (appAuthClients) => {
|
||||||
|
return {
|
||||||
|
data: appAuthClients.map((appAuthClient) => ({
|
||||||
|
name: appAuthClient.name,
|
||||||
|
id: appAuthClient.id,
|
||||||
|
active: appAuthClient.active,
|
||||||
|
})),
|
||||||
|
meta: {
|
||||||
|
count: appAuthClients.length,
|
||||||
|
currentPage: null,
|
||||||
|
isArray: true,
|
||||||
|
totalPages: null,
|
||||||
|
type: 'AppAuthClient',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAdminAppAuthClientsMock;
|
@@ -1,9 +1,9 @@
|
|||||||
const getAdminAppAuthClientMock = (appAuthClient) => {
|
const getAppAuthClientMock = (appAuthClient) => {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
appConfigId: appAuthClient.appConfigId,
|
|
||||||
name: appAuthClient.name,
|
name: appAuthClient.name,
|
||||||
id: appAuthClient.id,
|
id: appAuthClient.id,
|
||||||
|
appConfigId: appAuthClient.appConfigId,
|
||||||
active: appAuthClient.active,
|
active: appAuthClient.active,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
@@ -16,4 +16,4 @@ const getAdminAppAuthClientMock = (appAuthClient) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getAdminAppAuthClientMock;
|
export default getAppAuthClientMock;
|
@@ -0,0 +1,18 @@
|
|||||||
|
const getAppAuthClientsMock = (appAuthClients) => {
|
||||||
|
return {
|
||||||
|
data: appAuthClients.map((appAuthClient) => ({
|
||||||
|
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;
|
@@ -5,8 +5,6 @@ items:
|
|||||||
desc: Get value from the persistent datastore.
|
desc: Get value from the persistent datastore.
|
||||||
- name: Set value
|
- name: Set value
|
||||||
desc: Set value to the persistent datastore.
|
desc: Set value to the persistent datastore.
|
||||||
- name: Update value
|
|
||||||
desc: Update value in the persistent datastore.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -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
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import MuiTextField from '@mui/material/TextField';
|
import MuiTextField from '@mui/material/TextField';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
import useDynamicFields from 'hooks/useDynamicFields';
|
import useDynamicFields from 'hooks/useDynamicFields';
|
||||||
import useDynamicData from 'hooks/useDynamicData';
|
import useDynamicData from 'hooks/useDynamicData';
|
||||||
import PowerInput from 'components/PowerInput';
|
import PowerInput from 'components/PowerInput';
|
||||||
@@ -8,8 +9,10 @@ import TextField from 'components/TextField';
|
|||||||
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||||
import ControlledCustomAutocomplete from 'components/ControlledCustomAutocomplete';
|
import ControlledCustomAutocomplete from 'components/ControlledCustomAutocomplete';
|
||||||
import DynamicField from 'components/DynamicField';
|
import DynamicField from 'components/DynamicField';
|
||||||
|
|
||||||
const optionGenerator = (options) =>
|
const optionGenerator = (options) =>
|
||||||
options?.map(({ name, value }) => ({ label: name, value: value }));
|
options?.map(({ name, value }) => ({ label: name, value: value }));
|
||||||
|
|
||||||
export default function InputCreator(props) {
|
export default function InputCreator(props) {
|
||||||
const {
|
const {
|
||||||
onChange,
|
onChange,
|
||||||
@@ -31,9 +34,12 @@ export default function InputCreator(props) {
|
|||||||
type,
|
type,
|
||||||
} = schema;
|
} = schema;
|
||||||
const { data, loading } = useDynamicData(stepId, schema);
|
const { data, loading } = useDynamicData(stepId, schema);
|
||||||
const { data: additionalFields, loading: additionalFieldsLoading } =
|
const { data: additionalFieldsData, isLoading: isDynamicFieldsLoading } =
|
||||||
useDynamicFields(stepId, schema);
|
useDynamicFields(stepId, schema);
|
||||||
|
const additionalFields = additionalFieldsData?.data;
|
||||||
|
|
||||||
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
||||||
|
|
||||||
if (type === 'dynamic') {
|
if (type === 'dynamic') {
|
||||||
return (
|
return (
|
||||||
<DynamicField
|
<DynamicField
|
||||||
@@ -50,8 +56,10 @@ export default function InputCreator(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'dropdown') {
|
if (type === 'dropdown') {
|
||||||
const preparedOptions = schema.options || optionGenerator(data);
|
const preparedOptions = schema.options || optionGenerator(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!schema.variables && (
|
{!schema.variables && (
|
||||||
@@ -63,7 +71,9 @@ export default function InputCreator(props) {
|
|||||||
disablePortal
|
disablePortal
|
||||||
disableClearable={required}
|
disableClearable={required}
|
||||||
options={preparedOptions}
|
options={preparedOptions}
|
||||||
renderInput={(params) => <MuiTextField {...params} label={label} required={required}/>}
|
renderInput={(params) => (
|
||||||
|
<MuiTextField {...params} label={label} required={required} />
|
||||||
|
)}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
description={description}
|
description={description}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -93,7 +103,7 @@ export default function InputCreator(props) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{additionalFieldsLoading && !additionalFields?.length && (
|
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
</div>
|
</div>
|
||||||
@@ -113,6 +123,7 @@ export default function InputCreator(props) {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'string') {
|
if (type === 'string') {
|
||||||
if (schema.variables) {
|
if (schema.variables) {
|
||||||
return (
|
return (
|
||||||
@@ -127,7 +138,7 @@ export default function InputCreator(props) {
|
|||||||
shouldUnregister={shouldUnregister}
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{additionalFieldsLoading && !additionalFields?.length && (
|
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
sx={{ display: 'block', margin: '20px auto' }}
|
sx={{ display: 'block', margin: '20px auto' }}
|
||||||
@@ -149,6 +160,7 @@ export default function InputCreator(props) {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -168,7 +180,7 @@ export default function InputCreator(props) {
|
|||||||
shouldUnregister={shouldUnregister}
|
shouldUnregister={shouldUnregister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{additionalFieldsLoading && !additionalFields?.length && (
|
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -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;
|
||||||
|
}
|
@@ -5,7 +5,7 @@ export default function useAppConfig(appKey) {
|
|||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['appConfig', appKey],
|
queryKey: ['appConfig', appKey],
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
const { data } = await api.get(`/v1/app-configs/${appKey}`, {
|
const { data } = await api.get(`/v1/apps/${appKey}/config`, {
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
@@ -1,27 +1,35 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useLazyQuery } from '@apollo/client';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import { GET_DYNAMIC_FIELDS } from 'graphql/queries/get-dynamic-fields';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
const variableRegExp = /({.*?})/;
|
const variableRegExp = /({.*?})/;
|
||||||
// TODO: extract this function to a separate file
|
// TODO: extract this function to a separate file
|
||||||
function computeArguments(args, getValues) {
|
function computeArguments(args, getValues) {
|
||||||
const initialValue = {};
|
const initialValue = {};
|
||||||
|
|
||||||
return args.reduce((result, { name, value }) => {
|
return args.reduce((result, { name, value }) => {
|
||||||
const isVariable = variableRegExp.test(value);
|
const isVariable = variableRegExp.test(value);
|
||||||
|
|
||||||
if (isVariable) {
|
if (isVariable) {
|
||||||
const sanitizedFieldPath = value.replace(/{|}/g, '');
|
const sanitizedFieldPath = value.replace(/{|}/g, '');
|
||||||
const computedValue = getValues(sanitizedFieldPath);
|
const computedValue = getValues(sanitizedFieldPath);
|
||||||
|
|
||||||
if (computedValue === undefined || computedValue === '')
|
if (computedValue === undefined || computedValue === '')
|
||||||
throw new Error(`The ${sanitizedFieldPath} field is required.`);
|
throw new Error(`The ${sanitizedFieldPath} field is required.`);
|
||||||
|
|
||||||
set(result, name, computedValue);
|
set(result, name, computedValue);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(result, name, value);
|
set(result, name, value);
|
||||||
return result;
|
return result;
|
||||||
}, initialValue);
|
}, initialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the dynamic fields for the given step.
|
* Fetch the dynamic fields for the given step.
|
||||||
* This hook must be within a react-hook-form context.
|
* This hook must be within a react-hook-form context.
|
||||||
@@ -31,10 +39,9 @@ function computeArguments(args, getValues) {
|
|||||||
*/
|
*/
|
||||||
function useDynamicFields(stepId, schema) {
|
function useDynamicFields(stepId, schema) {
|
||||||
const lastComputedVariables = React.useRef({});
|
const lastComputedVariables = React.useRef({});
|
||||||
const [getDynamicFields, { called, data, loading }] =
|
|
||||||
useLazyQuery(GET_DYNAMIC_FIELDS);
|
|
||||||
const { getValues } = useFormContext();
|
const { getValues } = useFormContext();
|
||||||
const formValues = getValues();
|
const formValues = getValues();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return `null` when even a field is missing value.
|
* Return `null` when even a field is missing value.
|
||||||
*
|
*
|
||||||
@@ -58,31 +65,28 @@ function useDynamicFields(stepId, schema) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
/**
|
/**
|
||||||
* `formValues` is to trigger recomputation when form is updated.
|
* `formValues` is to trigger recomputation when form is updated.
|
||||||
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
|
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
|
||||||
*/
|
*/
|
||||||
}, [schema, formValues, getValues]);
|
}, [schema, formValues, getValues]);
|
||||||
React.useEffect(() => {
|
|
||||||
if (
|
const query = useQuery({
|
||||||
schema.type === 'dropdown' &&
|
queryKey: ['dynamicFields', stepId, computedVariables],
|
||||||
stepId &&
|
queryFn: async () => {
|
||||||
schema.additionalFields &&
|
const { data } = await api.post(`/v1/steps/${stepId}/dynamic-fields`, {
|
||||||
computedVariables
|
dynamicFieldsKey: computedVariables.key,
|
||||||
) {
|
parameters: computedVariables.parameters,
|
||||||
getDynamicFields({
|
|
||||||
variables: {
|
|
||||||
stepId,
|
|
||||||
...computedVariables,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}, [getDynamicFields, stepId, schema, computedVariables]);
|
return data;
|
||||||
return {
|
},
|
||||||
called,
|
enabled: !!stepId && !!computedVariables,
|
||||||
data: data?.getDynamicFields,
|
});
|
||||||
loading,
|
|
||||||
};
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useDynamicFields;
|
export default useDynamicFields;
|
||||||
|
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,
|
|
||||||
};
|
|
||||||
}
|
|
@@ -79,7 +79,7 @@ export default function useUserTrial() {
|
|||||||
[checkoutCompleted, hasTrial, setIsPolling],
|
[checkoutCompleted, hasTrial, setIsPolling],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isUserTrialLoading || !userTrial) return null;
|
if (isUserTrialLoading || !hasTrial) return null;
|
||||||
|
|
||||||
const expireAt = DateTime.fromISO(userTrial?.expireAt).startOf('day');
|
const expireAt = DateTime.fromISO(userTrial?.expireAt).startOf('day');
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user