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',
|
||||
},
|
||||
{
|
||||
label: 'JSON body',
|
||||
key: 'stringifiedJsonBody',
|
||||
label: 'Headers',
|
||||
key: 'headers',
|
||||
type: 'dynamic',
|
||||
required: false,
|
||||
description: 'Add or remove headers as needed',
|
||||
fields: [
|
||||
{
|
||||
label: 'Key',
|
||||
key: 'key',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The content of the JSON body. It must be a valid JSON.',
|
||||
description: 'Header key',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Value',
|
||||
key: 'value',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Header value',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Body',
|
||||
key: 'body',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The content of the response body.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const parsedStatusCode = parseInt($.step.parameters.statusCode, 10);
|
||||
const stringifiedJsonBody = $.step.parameters.stringifiedJsonBody;
|
||||
const parsedJsonBody = JSON.parse(stringifiedJsonBody);
|
||||
const statusCode = parseInt($.step.parameters.statusCode, 10);
|
||||
const body = $.step.parameters.body;
|
||||
const headers = $.step.parameters.headers.reduce((result, entry) => {
|
||||
return {
|
||||
...result,
|
||||
[entry.key]: entry.value,
|
||||
};
|
||||
}, {});
|
||||
|
||||
$.setActionItem({
|
||||
raw: {
|
||||
body: parsedJsonBody,
|
||||
statusCode: parsedStatusCode,
|
||||
headers,
|
||||
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) => {
|
||||
const appAuthClient = await AppAuthClient.query()
|
||||
.findById(request.params.appAuthClientId)
|
||||
.where({ app_key: request.params.appKey })
|
||||
.throwIfNotFound();
|
||||
|
||||
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) => {
|
||||
const appAuthClient = await AppAuthClient.query()
|
||||
.findById(request.params.appAuthClientId)
|
||||
.where({ active: true })
|
||||
.where({ app_key: request.params.appKey, active: true })
|
||||
.throwIfNotFound();
|
||||
|
||||
renderObject(response, appAuthClient);
|
@@ -4,25 +4,27 @@ 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 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 * 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;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||
|
||||
currentUser = await createUser();
|
||||
currentAppAuthClient = await createAppAuthClient();
|
||||
currentAppAuthClient = await createAppAuthClient({
|
||||
appKey: 'deepl',
|
||||
});
|
||||
|
||||
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)
|
||||
.get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`)
|
||||
.get(`/api/v1/apps/deepl/auth-clients/${currentAppAuthClient.id}`)
|
||||
.set('Authorization', token)
|
||||
.expect(200);
|
||||
|
||||
@@ -34,14 +36,14 @@ describe('GET /api/v1/app-auth-clients/:id', () => {
|
||||
const notExistingAppAuthClientUUID = Crypto.randomUUID();
|
||||
|
||||
await request(app)
|
||||
.get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`)
|
||||
.get(`/api/v1/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/app-auth-clients/invalidAppAuthClientUUID')
|
||||
.get('/api/v1/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, 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 createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.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 * 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;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -27,7 +27,7 @@ describe('GET /api/v1/app-configs/:appKey', () => {
|
||||
|
||||
it('should return specified app config info', async () => {
|
||||
const response = await request(app)
|
||||
.get(`/api/v1/app-configs/${appConfig.key}`)
|
||||
.get(`/api/v1/apps/${appConfig.key}/config`)
|
||||
.set('Authorization', token)
|
||||
.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 () => {
|
||||
await request(app)
|
||||
.get('/api/v1/app-configs/not-existing-app-key')
|
||||
.get('/api/v1/apps/not-existing-app-key/config')
|
||||
.set('Authorization', token)
|
||||
.expect(404);
|
||||
});
|
@@ -42,9 +42,12 @@ describe('GET /api/v1/executions', () => {
|
||||
|
||||
const currentUserExecutionTwo = await createExecution({
|
||||
flowId: currentUserFlow.id,
|
||||
deletedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await currentUserExecutionTwo
|
||||
.$query()
|
||||
.patchAndFetch({ deletedAt: new Date().toISOString() });
|
||||
|
||||
await createPermission({
|
||||
action: 'read',
|
||||
subject: 'Execution',
|
||||
@@ -87,9 +90,12 @@ describe('GET /api/v1/executions', () => {
|
||||
|
||||
const anotherUserExecutionTwo = await createExecution({
|
||||
flowId: anotherUserFlow.id,
|
||||
deletedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await anotherUserExecutionTwo
|
||||
.$query()
|
||||
.patchAndFetch({ deletedAt: new Date().toISOString() });
|
||||
|
||||
await createPermission({
|
||||
action: 'read',
|
||||
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);
|
||||
|
||||
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,
|
||||
key: 'translateText',
|
||||
parameters: {},
|
||||
position: 1,
|
||||
position: 2,
|
||||
status: actionStep.status,
|
||||
type: 'action',
|
||||
webhookUrl: 'http://localhost:3000/null',
|
||||
@@ -223,7 +223,7 @@ describe('graphQL getFlow query', () => {
|
||||
id: actionStep.id,
|
||||
key: 'translateText',
|
||||
parameters: {},
|
||||
position: 1,
|
||||
position: 2,
|
||||
status: actionStep.status,
|
||||
type: 'action',
|
||||
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 getAppAuthClients from './queries/get-app-auth-clients.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 getDynamicData from './queries/get-dynamic-data.js';
|
||||
import getDynamicFields from './queries/get-dynamic-fields.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 getUsers from './queries/get-users.js';
|
||||
import testConnection from './queries/test-connection.js';
|
||||
|
||||
const queryResolvers = {
|
||||
@@ -18,15 +13,10 @@ const queryResolvers = {
|
||||
getAppAuthClient,
|
||||
getAppAuthClients,
|
||||
getBillingAndUsage,
|
||||
getConfig,
|
||||
getConnectedApps,
|
||||
getDynamicData,
|
||||
getDynamicFields,
|
||||
getFlow,
|
||||
getFlows,
|
||||
getNotifications,
|
||||
getStepWithTestExecutions,
|
||||
getUsers,
|
||||
testConnection,
|
||||
};
|
||||
|
||||
|
@@ -5,28 +5,13 @@ type Query {
|
||||
getConnectedApps(name: String): [App]
|
||||
testConnection(id: String!): Connection
|
||||
getFlow(id: String!): Flow
|
||||
getFlows(
|
||||
limit: Int!
|
||||
offset: Int!
|
||||
appKey: String
|
||||
connectionId: String
|
||||
name: String
|
||||
): FlowConnection
|
||||
getStepWithTestExecutions(stepId: String!): [Step]
|
||||
getDynamicData(
|
||||
stepId: String!
|
||||
key: String!
|
||||
parameters: JSONObject
|
||||
): JSONObject
|
||||
getDynamicFields(
|
||||
stepId: String!
|
||||
key: String!
|
||||
parameters: JSONObject
|
||||
): [SubstepArgument]
|
||||
getBillingAndUsage: GetBillingAndUsage
|
||||
getConfig(keys: [String]): JSONObject
|
||||
getNotifications: [Notification]
|
||||
getUsers(limit: Int!, offset: Int!): UserConnection
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -257,15 +242,6 @@ type Field {
|
||||
options: [SubstepArgumentOption]
|
||||
}
|
||||
|
||||
type FlowConnection {
|
||||
edges: [FlowEdge]
|
||||
pageInfo: PageInfo
|
||||
}
|
||||
|
||||
type FlowEdge {
|
||||
node: Flow
|
||||
}
|
||||
|
||||
enum FlowStatus {
|
||||
paused
|
||||
published
|
||||
@@ -304,16 +280,6 @@ type SamlAuthProvidersRoleMapping {
|
||||
remoteRoleName: String
|
||||
}
|
||||
|
||||
type UserConnection {
|
||||
edges: [UserEdge]
|
||||
pageInfo: PageInfo
|
||||
totalCount: Int
|
||||
}
|
||||
|
||||
type UserEdge {
|
||||
node: User
|
||||
}
|
||||
|
||||
input CreateConnectionInput {
|
||||
key: String!
|
||||
appAuthClientId: String
|
||||
@@ -692,13 +658,6 @@ input UpdateAppAuthClientInput {
|
||||
active: Boolean
|
||||
}
|
||||
|
||||
type Notification {
|
||||
name: String
|
||||
createdAt: String
|
||||
documentationUrl: String
|
||||
description: String
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
|
@@ -42,8 +42,6 @@ const isAuthenticatedRule = rule()(isAuthenticated);
|
||||
export const authenticationRules = {
|
||||
Query: {
|
||||
'*': isAuthenticatedRule,
|
||||
getConfig: allow,
|
||||
getNotifications: allow,
|
||||
},
|
||||
Mutation: {
|
||||
'*': isAuthenticatedRule,
|
||||
|
@@ -19,6 +19,14 @@ const authorizationList = {
|
||||
action: 'read',
|
||||
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': {
|
||||
action: 'read',
|
||||
subject: 'Flow',
|
||||
|
@@ -9,7 +9,7 @@ const stream = {
|
||||
const registerGraphQLToken = () => {
|
||||
morgan.token('graphql-query', (req) => {
|
||||
if (req.body.query) {
|
||||
return `GraphQL ${req.body.query}`;
|
||||
return `\n GraphQL ${req.body.query}`;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -17,7 +17,7 @@ const registerGraphQLToken = () => {
|
||||
registerGraphQLToken();
|
||||
|
||||
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 }
|
||||
);
|
||||
|
||||
|
@@ -44,4 +44,22 @@ const renderObject = (response, object, options) => {
|
||||
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) {
|
||||
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.
|
||||
response.status(actionExecutionStep.dataOut.statusCode);
|
||||
response.send(actionExecutionStep.dataOut.body);
|
||||
response.status(statusCode);
|
||||
response.send(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,11 @@ class AppAuthClient extends Base {
|
||||
|
||||
static jsonSchema = {
|
||||
type: 'object',
|
||||
required: ['name', 'appConfigId', 'formattedAuthDefaults'],
|
||||
required: ['name', 'appKey', 'formattedAuthDefaults'],
|
||||
|
||||
properties: {
|
||||
id: { type: 'string', format: 'uuid' },
|
||||
appConfigId: { type: 'string', format: 'uuid' },
|
||||
appKey: { type: 'string' },
|
||||
active: { type: 'boolean' },
|
||||
authDefaults: { type: ['string', 'null'] },
|
||||
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() {
|
||||
if (!this.eligibleForEncryption()) return;
|
||||
|
||||
@@ -71,6 +60,21 @@ class AppAuthClient extends Base {
|
||||
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() {
|
||||
this.decryptData();
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import App from './app.js';
|
||||
import Base from './base.js';
|
||||
import AppAuthClient from './app-auth-client.js';
|
||||
import Base from './base.js';
|
||||
|
||||
class AppConfig extends Base {
|
||||
static tableName = 'app_configs';
|
||||
@@ -15,45 +15,48 @@ class AppConfig extends Base {
|
||||
allowCustomConnection: { type: 'boolean', default: false },
|
||||
shared: { 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() {
|
||||
if (!this.key) return null;
|
||||
|
||||
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;
|
||||
|
@@ -7,6 +7,7 @@ import Connection from './connection.js';
|
||||
import ExecutionStep from './execution-step.js';
|
||||
import Telemetry from '../helpers/telemetry/index.js';
|
||||
import appConfig from '../config/app.js';
|
||||
import globalVariable from '../helpers/global-variable.js';
|
||||
|
||||
class Step extends Base {
|
||||
static tableName = 'steps';
|
||||
@@ -196,6 +197,26 @@ class Step extends Base {
|
||||
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() {
|
||||
if (this.isAction) return this;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import crypto from 'node:crypto';
|
||||
import appConfig from '../config/app.js';
|
||||
import { hasValidLicense } from '../helpers/license.ee.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 Connection from './connection.js';
|
||||
import Execution from './execution.js';
|
||||
@@ -161,6 +162,17 @@ class User extends Base {
|
||||
: 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) {
|
||||
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 { authorizeAdmin } from '../../../../helpers/authorization.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();
|
||||
|
||||
router.get(
|
||||
'/:appAuthClientId',
|
||||
'/:appKey/auth-clients',
|
||||
authenticateUser,
|
||||
authorizeAdmin,
|
||||
checkIsEnterprise,
|
||||
asyncHandler(getAdminAppAuthClientsAction)
|
||||
asyncHandler(getAuthClientsAction)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:appKey/auth-clients/:appAuthClientId',
|
||||
authenticateUser,
|
||||
authorizeAdmin,
|
||||
checkIsEnterprise,
|
||||
asyncHandler(getAuthClientAction)
|
||||
);
|
||||
|
||||
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 { authenticateUser } from '../../../helpers/authentication.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 getAppsAction from '../../../controllers/api/v1/apps/get-apps.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 getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.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/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(
|
||||
'/:appKey/triggers',
|
||||
authenticateUser,
|
||||
|
@@ -3,6 +3,8 @@ import asyncHandler from 'express-async-handler';
|
||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
||||
import { authorizeUser } from '../../../helpers/authorization.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();
|
||||
|
||||
@@ -13,4 +15,18 @@ router.get(
|
||||
asyncHandler(getConnectionAction)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:stepId/previous-steps',
|
||||
authenticateUser,
|
||||
authorizeUser,
|
||||
asyncHandler(getPreviousStepsAction)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:stepId/dynamic-fields',
|
||||
authenticateUser,
|
||||
authorizeUser,
|
||||
asyncHandler(createDynamicFieldsAction)
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@@ -4,21 +4,20 @@ import webhooksRouter from './webhooks.js';
|
||||
import paddleRouter from './paddle.ee.js';
|
||||
import healthcheckRouter from './healthcheck.js';
|
||||
import automatischRouter from './api/v1/automatisch.js';
|
||||
import accessTokensRouter from './api/v1/access-tokens.js';
|
||||
import usersRouter from './api/v1/users.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 stepsRouter from './api/v1/steps.js';
|
||||
import appsRouter from './api/v1/apps.js';
|
||||
import connectionsRouter from './api/v1/connections.js';
|
||||
import executionsRouter from './api/v1/executions.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 rolesRouter from './api/v1/admin/roles.ee.js';
|
||||
import permissionsRouter from './api/v1/admin/permissions.ee.js';
|
||||
import adminUsersRouter from './api/v1/admin/users.ee.js';
|
||||
import adminAppAuthClientsRouter from './api/v1/admin/app-auth-clients.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -27,20 +26,19 @@ router.use('/webhooks', webhooksRouter);
|
||||
router.use('/paddle', paddleRouter);
|
||||
router.use('/healthcheck', healthcheckRouter);
|
||||
router.use('/api/v1/automatisch', automatischRouter);
|
||||
router.use('/api/v1/access-tokens', accessTokensRouter);
|
||||
router.use('/api/v1/users', usersRouter);
|
||||
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/connections', connectionsRouter);
|
||||
router.use('/api/v1/flows', flowsRouter);
|
||||
router.use('/api/v1/steps', stepsRouter);
|
||||
router.use('/api/v1/executions', executionsRouter);
|
||||
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/permissions', permissionsRouter);
|
||||
router.use('/api/v1/admin/users', adminUsersRouter);
|
||||
router.use('/api/v1/admin/app-auth-clients', adminAppAuthClientsRouter);
|
||||
router.use('/api/v1/admin/saml-auth-providers', adminSamlAuthProvidersRouter);
|
||||
|
||||
export default router;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import executionStepSerializer from './execution-step.js';
|
||||
|
||||
const stepSerializer = (step) => {
|
||||
return {
|
||||
let stepData = {
|
||||
id: step.id,
|
||||
type: step.type,
|
||||
key: step.key,
|
||||
@@ -10,6 +12,14 @@ const stepSerializer = (step) => {
|
||||
position: step.position,
|
||||
parameters: step.parameters,
|
||||
};
|
||||
|
||||
if (step.executionSteps?.length > 0) {
|
||||
stepData.executionSteps = step.executionSteps.map((executionStep) =>
|
||||
executionStepSerializer(executionStep)
|
||||
);
|
||||
}
|
||||
|
||||
return stepData;
|
||||
};
|
||||
|
||||
export default stepSerializer;
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { createStep } from '../../test/factories/step';
|
||||
import { createExecutionStep } from '../../test/factories/execution-step';
|
||||
import stepSerializer from './step';
|
||||
import executionStepSerializer from './execution-step';
|
||||
|
||||
describe('stepSerializer', () => {
|
||||
let step;
|
||||
@@ -24,4 +26,20 @@ describe('stepSerializer', () => {
|
||||
|
||||
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 { createAppConfig } from './app-config.js';
|
||||
import AppAuthClient from '../../src/models/app-auth-client';
|
||||
|
||||
const formattedAuthDefaults = {
|
||||
@@ -12,14 +11,12 @@ const formattedAuthDefaults = {
|
||||
export const createAppAuthClient = async (params = {}) => {
|
||||
params.name = params?.name || faker.person.fullName();
|
||||
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.formattedAuthDefaults =
|
||||
params?.formattedAuthDefaults || formattedAuthDefaults;
|
||||
|
||||
const appAuthClient = await AppAuthClient.query()
|
||||
.insert(params)
|
||||
.returning('*');
|
||||
const appAuthClient = await AppAuthClient.query().insertAndFetch(params);
|
||||
|
||||
return appAuthClient;
|
||||
};
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import AppConfig from '../../src/models/app-config.js';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
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;
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ export const createConfig = async (params = {}) => {
|
||||
value: params?.value || { data: 'sampleConfig' },
|
||||
};
|
||||
|
||||
const config = await Config.query().insert(configData).returning('*');
|
||||
const config = await Config.query().insertAndFetch(configData);
|
||||
|
||||
return config;
|
||||
};
|
||||
|
@@ -9,9 +9,7 @@ export const createExecutionStep = async (params = {}) => {
|
||||
params.dataIn = params?.dataIn || { dataIn: 'dataIn' };
|
||||
params.dataOut = params?.dataOut || { dataOut: 'dataOut' };
|
||||
|
||||
const executionStep = await ExecutionStep.query()
|
||||
.insert(params)
|
||||
.returning('*');
|
||||
const executionStep = await ExecutionStep.query().insertAndFetch(params);
|
||||
|
||||
return executionStep;
|
||||
};
|
||||
|
@@ -4,10 +4,8 @@ import { createFlow } from './flow';
|
||||
export const createExecution = async (params = {}) => {
|
||||
params.flowId = params?.flowId || (await createFlow()).id;
|
||||
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;
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ export const createFlow = async (params = {}) => {
|
||||
params.createdAt = params?.createdAt || 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;
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ export const createPermission = async (params = {}) => {
|
||||
params.subject = params?.subject || 'User';
|
||||
params.conditions = params?.conditions || ['isCreator'];
|
||||
|
||||
const permission = await Permission.query().insert(params).returning('*');
|
||||
const permission = await Permission.query().insertAndFetch(params);
|
||||
|
||||
return permission;
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ export const createRole = async (params = {}) => {
|
||||
params.name = params?.name || 'Viewer';
|
||||
params.key = params?.key || 'viewer';
|
||||
|
||||
const role = await Role.query().insert(params).returning('*');
|
||||
const role = await Role.query().insertAndFetch(params);
|
||||
|
||||
return role;
|
||||
};
|
||||
|
@@ -25,9 +25,9 @@ export const createSamlAuthProvider = async (params = {}) => {
|
||||
params.defaultRoleId = params?.defaultRoleId || (await createRole()).id;
|
||||
params.active = params?.active || true;
|
||||
|
||||
const samlAuthProvider = await SamlAuthProvider.query()
|
||||
.insert(params)
|
||||
.returning('*');
|
||||
const samlAuthProvider = await SamlAuthProvider.query().insertAndFetch(
|
||||
params
|
||||
);
|
||||
|
||||
return samlAuthProvider;
|
||||
};
|
||||
|
@@ -5,19 +5,21 @@ export const createStep = async (params = {}) => {
|
||||
params.flowId = params?.flowId || (await createFlow()).id;
|
||||
params.type = params?.type || 'action';
|
||||
|
||||
const lastStep = await global.knex
|
||||
.table('steps')
|
||||
.where('flowId', params.flowId)
|
||||
.andWhere('deletedAt', '!=', null)
|
||||
.orderBy('createdAt', 'desc')
|
||||
const lastStep = await Step.query()
|
||||
.where('flow_id', params.flowId)
|
||||
.andWhere('deleted_at', null)
|
||||
.orderBy('position', 'desc')
|
||||
.limit(1)
|
||||
.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.appKey =
|
||||
params?.appKey || (params.type === 'action' ? 'deepl' : 'webhook');
|
||||
|
||||
const step = await Step.query().insert(params).returning('*');
|
||||
const step = await Step.query().insertAndFetch(params);
|
||||
|
||||
return step;
|
||||
};
|
||||
|
@@ -15,7 +15,7 @@ export const createSubscription = async (params = {}) => {
|
||||
params.nextBillDate =
|
||||
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;
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ export const createUser = async (params = {}) => {
|
||||
params.email = params?.email || faker.internet.email();
|
||||
params.password = params?.password || faker.internet.password();
|
||||
|
||||
const user = await User.query().insert(params).returning('*');
|
||||
const user = await User.query().insertAndFetch(params);
|
||||
|
||||
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 {
|
||||
data: {
|
||||
appConfigId: appAuthClient.appConfigId,
|
||||
name: appAuthClient.name,
|
||||
id: appAuthClient.id,
|
||||
appConfigId: appAuthClient.appConfigId,
|
||||
active: appAuthClient.active,
|
||||
},
|
||||
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.
|
||||
- name: Set value
|
||||
desc: Set value to the persistent datastore.
|
||||
- name: Update value
|
||||
desc: Update value in the persistent datastore.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
|
@@ -1,35 +1,39 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { GET_FLOWS } from 'graphql/queries/get-flows';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import PaginationItem from '@mui/material/PaginationItem';
|
||||
|
||||
import * as URLS from 'config/urls';
|
||||
import AppFlowRow from 'components/FlowRow';
|
||||
import NoResultFound from 'components/NoResultFound';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
const FLOW_PER_PAGE = 10;
|
||||
const getLimitAndOffset = (page) => ({
|
||||
limit: FLOW_PER_PAGE,
|
||||
offset: (page - 1) * FLOW_PER_PAGE,
|
||||
});
|
||||
import useConnectionFlows from 'hooks/useConnectionFlows';
|
||||
import useAppFlows from 'hooks/useAppFlows';
|
||||
|
||||
function AppFlows(props) {
|
||||
const { appKey } = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const connectionId = searchParams.get('connectionId') || undefined;
|
||||
const page = parseInt(searchParams.get('page') || '', 10) || 1;
|
||||
const { data } = useQuery(GET_FLOWS, {
|
||||
variables: {
|
||||
appKey,
|
||||
connectionId,
|
||||
...getLimitAndOffset(page),
|
||||
},
|
||||
});
|
||||
const getFlows = data?.getFlows || {};
|
||||
const { pageInfo, edges } = getFlows;
|
||||
const flows = edges?.map(({ node }) => node);
|
||||
const isConnectionFlowEnabled = !!connectionId;
|
||||
const isAppFlowEnabled = !!appKey && !connectionId;
|
||||
|
||||
const connectionFlows = useConnectionFlows(
|
||||
{ connectionId, page },
|
||||
{ enabled: isConnectionFlowEnabled },
|
||||
);
|
||||
|
||||
const appFlows = useAppFlows({ appKey, page }, { enabled: isAppFlowEnabled });
|
||||
|
||||
const flows = isConnectionFlowEnabled
|
||||
? connectionFlows?.data?.data || []
|
||||
: appFlows?.data?.data || [];
|
||||
const pageInfo = isConnectionFlowEnabled
|
||||
? connectionFlows?.data?.meta || []
|
||||
: appFlows?.data?.meta || [];
|
||||
const hasFlows = flows?.length;
|
||||
|
||||
if (!hasFlows) {
|
||||
return (
|
||||
<NoResultFound
|
||||
@@ -39,6 +43,7 @@ function AppFlows(props) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{flows?.map((appFlow) => (
|
||||
|
@@ -1,10 +1,14 @@
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||
import { LogoImage } from './style.ee';
|
||||
|
||||
const CustomLogo = () => {
|
||||
const { config, loading } = useConfig(['logo.svgData']);
|
||||
if (loading || !config?.['logo.svgData']) return null;
|
||||
const { data: configData, isLoading } = useAutomatischConfig();
|
||||
const config = configData?.data;
|
||||
|
||||
if (isLoading || !config?.['logo.svgData']) return null;
|
||||
|
||||
const logoSvgData = config['logo.svgData'];
|
||||
|
||||
return (
|
||||
<LogoImage
|
||||
data-test="custom-logo"
|
||||
|
@@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||
import * as React from 'react';
|
||||
import ConfirmationDialog from 'components/ConfirmationDialog';
|
||||
@@ -17,9 +19,13 @@ function DeleteUserButton(props) {
|
||||
});
|
||||
const formatMessage = useFormatMessage();
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleConfirm = React.useCallback(async () => {
|
||||
try {
|
||||
await deleteUser();
|
||||
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['admin', 'user', userId] });
|
||||
setShowConfirmation(false);
|
||||
enqueueSnackbar(formatMessage('deleteUserButton.successfullyDeleted'), {
|
||||
variant: 'success',
|
||||
@@ -31,6 +37,7 @@ function DeleteUserButton(props) {
|
||||
throw new Error('Failed while deleting!');
|
||||
}
|
||||
}, [deleteUser]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import MuiTextField from '@mui/material/TextField';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
|
||||
import useDynamicFields from 'hooks/useDynamicFields';
|
||||
import useDynamicData from 'hooks/useDynamicData';
|
||||
import PowerInput from 'components/PowerInput';
|
||||
@@ -8,8 +9,10 @@ import TextField from 'components/TextField';
|
||||
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||
import ControlledCustomAutocomplete from 'components/ControlledCustomAutocomplete';
|
||||
import DynamicField from 'components/DynamicField';
|
||||
|
||||
const optionGenerator = (options) =>
|
||||
options?.map(({ name, value }) => ({ label: name, value: value }));
|
||||
|
||||
export default function InputCreator(props) {
|
||||
const {
|
||||
onChange,
|
||||
@@ -31,9 +34,12 @@ export default function InputCreator(props) {
|
||||
type,
|
||||
} = schema;
|
||||
const { data, loading } = useDynamicData(stepId, schema);
|
||||
const { data: additionalFields, loading: additionalFieldsLoading } =
|
||||
const { data: additionalFieldsData, isLoading: isDynamicFieldsLoading } =
|
||||
useDynamicFields(stepId, schema);
|
||||
const additionalFields = additionalFieldsData?.data;
|
||||
|
||||
const computedName = namePrefix ? `${namePrefix}.${name}` : name;
|
||||
|
||||
if (type === 'dynamic') {
|
||||
return (
|
||||
<DynamicField
|
||||
@@ -50,8 +56,10 @@ export default function InputCreator(props) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'dropdown') {
|
||||
const preparedOptions = schema.options || optionGenerator(data);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{!schema.variables && (
|
||||
@@ -63,7 +71,9 @@ export default function InputCreator(props) {
|
||||
disablePortal
|
||||
disableClearable={required}
|
||||
options={preparedOptions}
|
||||
renderInput={(params) => <MuiTextField {...params} label={label} required={required}/>}
|
||||
renderInput={(params) => (
|
||||
<MuiTextField {...params} label={label} required={required} />
|
||||
)}
|
||||
defaultValue={value}
|
||||
description={description}
|
||||
loading={loading}
|
||||
@@ -93,7 +103,7 @@ export default function InputCreator(props) {
|
||||
/>
|
||||
)}
|
||||
|
||||
{additionalFieldsLoading && !additionalFields?.length && (
|
||||
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||
<div>
|
||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||
</div>
|
||||
@@ -113,6 +123,7 @@ export default function InputCreator(props) {
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
if (schema.variables) {
|
||||
return (
|
||||
@@ -127,7 +138,7 @@ export default function InputCreator(props) {
|
||||
shouldUnregister={shouldUnregister}
|
||||
/>
|
||||
|
||||
{additionalFieldsLoading && !additionalFields?.length && (
|
||||
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||
<div>
|
||||
<CircularProgress
|
||||
sx={{ display: 'block', margin: '20px auto' }}
|
||||
@@ -149,6 +160,7 @@ export default function InputCreator(props) {
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
@@ -168,7 +180,7 @@ export default function InputCreator(props) {
|
||||
shouldUnregister={shouldUnregister}
|
||||
/>
|
||||
|
||||
{additionalFieldsLoading && !additionalFields?.length && (
|
||||
{isDynamicFieldsLoading && !additionalFields?.length && (
|
||||
<div>
|
||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||
</div>
|
||||
|
@@ -15,7 +15,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useVersion from 'hooks/useVersion';
|
||||
import AppBar from 'components/AppBar';
|
||||
import Drawer from 'components/Drawer';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||
|
||||
const drawerLinks = [
|
||||
{
|
||||
@@ -77,11 +77,9 @@ const generateDrawerBottomLinks = async ({
|
||||
|
||||
export default function PublicLayout({ children }) {
|
||||
const version = useVersion();
|
||||
const { config, loading } = useConfig([
|
||||
'disableNotificationsPage',
|
||||
'additionalDrawerLink',
|
||||
'additionalDrawerLinkText',
|
||||
]);
|
||||
const { data: configData, isLoading } = useAutomatischConfig();
|
||||
const config = configData?.data;
|
||||
|
||||
const theme = useTheme();
|
||||
const formatMessage = useFormatMessage();
|
||||
const [bottomLinks, setBottomLinks] = React.useState([]);
|
||||
@@ -102,10 +100,10 @@ export default function PublicLayout({ children }) {
|
||||
setBottomLinks(newBottomLinks);
|
||||
}
|
||||
|
||||
if (loading) return;
|
||||
if (isLoading) return;
|
||||
|
||||
perform();
|
||||
}, [config, loading, version.newVersionCount]);
|
||||
}, [config, isLoading, version.newVersionCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@@ -1,12 +1,19 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import CustomLogo from 'components/CustomLogo/index.ee';
|
||||
import DefaultLogo from 'components/DefaultLogo';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||
|
||||
const Logo = () => {
|
||||
const { config, loading } = useConfig(['logo.svgData']);
|
||||
const { data: configData, isLoading } = useAutomatischConfig();
|
||||
const config = configData?.data;
|
||||
const logoSvgData = config?.['logo.svgData'];
|
||||
if (loading && !logoSvgData) return <React.Fragment />;
|
||||
|
||||
if (isLoading && !logoSvgData) return <React.Fragment />;
|
||||
|
||||
if (logoSvgData) return <CustomLogo />;
|
||||
|
||||
return <DefaultLogo />;
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
@@ -1,15 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
|
||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||
|
||||
const MetadataProvider = ({ children }) => {
|
||||
const { config } = useConfig();
|
||||
const { data: configData } = useAutomatischConfig();
|
||||
const config = configData?.data;
|
||||
|
||||
React.useEffect(() => {
|
||||
document.title = config?.title || 'Automatisch';
|
||||
}, [config?.title]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const existingFaviconElement = document.querySelector("link[rel~='icon']");
|
||||
|
||||
if (config?.disableFavicon === true) {
|
||||
existingFaviconElement?.remove();
|
||||
}
|
||||
|
||||
if (config?.disableFavicon === false) {
|
||||
if (existingFaviconElement) {
|
||||
existingFaviconElement.href = '/browser-tab.ico';
|
||||
@@ -20,7 +27,10 @@ const MetadataProvider = ({ children }) => {
|
||||
newFaviconElement.href = '/browser-tab.ico';
|
||||
}
|
||||
}
|
||||
|
||||
}, [config?.disableFavicon]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default MetadataProvider;
|
||||
|
@@ -6,7 +6,7 @@ import set from 'lodash/set';
|
||||
import * as React from 'react';
|
||||
|
||||
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||
import { defaultTheme, mationTheme } from 'styles/theme';
|
||||
|
||||
const customizeTheme = (theme, config) => {
|
||||
@@ -28,7 +28,8 @@ const ThemeProvider = ({ children, ...props }) => {
|
||||
const { data: automatischInfo, isPending: isAutomatischInfoPending } =
|
||||
useAutomatischInfo();
|
||||
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 installationTheme = isMation ? mationTheme : defaultTheme;
|
||||
@@ -51,4 +52,5 @@ const ThemeProvider = ({ children, ...props }) => {
|
||||
</BaseThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
|
@@ -14,23 +14,23 @@ import EditIcon from '@mui/icons-material/Edit';
|
||||
import TableFooter from '@mui/material/TableFooter';
|
||||
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
||||
import ListLoader from 'components/ListLoader';
|
||||
import useUsers from 'hooks/useUsers';
|
||||
import useAdminUsers from 'hooks/useAdminUsers';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import * as URLS from 'config/urls';
|
||||
import TablePaginationActions from './TablePaginationActions';
|
||||
import { TablePagination } from './style';
|
||||
|
||||
export default function UserList() {
|
||||
const formatMessage = useFormatMessage();
|
||||
const [page, setPage] = React.useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(10);
|
||||
const { users, pageInfo, totalCount, loading } = useUsers(page, rowsPerPage);
|
||||
const { data: usersData, isLoading } = useAdminUsers(page + 1);
|
||||
const users = usersData?.data;
|
||||
const { count } = usersData?.meta || {};
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(+event.target.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
@@ -68,14 +68,14 @@ export default function UserList() {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading && (
|
||||
{isLoading && (
|
||||
<ListLoader
|
||||
data-test="users-list-loader"
|
||||
rowsNumber={3}
|
||||
columnsNumber={2}
|
||||
/>
|
||||
)}
|
||||
{!loading &&
|
||||
{!isLoading &&
|
||||
users.map((user) => (
|
||||
<TableRow
|
||||
key={user.id}
|
||||
@@ -120,18 +120,16 @@ export default function UserList() {
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
{totalCount && (
|
||||
{!isLoading && typeof count === 'number' && (
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
data-total-count={totalCount}
|
||||
data-rows-per-page={rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50, 100]}
|
||||
data-total-count={count}
|
||||
rowsPerPageOptions={[]}
|
||||
page={page}
|
||||
count={totalCount}
|
||||
count={count}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
rowsPerPage={10}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
/>
|
||||
</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 api from 'helpers/api';
|
||||
|
||||
export default function useUser({ userId }) {
|
||||
export default function useAdminUser({ userId }) {
|
||||
const query = useQuery({
|
||||
queryKey: ['user', userId],
|
||||
queryKey: ['admin', 'user', userId],
|
||||
queryFn: async ({ signal }) => {
|
||||
const { data } = await api.get(`/v1/admin/users/${userId}`, {
|
||||
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({
|
||||
queryKey: ['appConfig', appKey],
|
||||
queryFn: async ({ signal }) => {
|
||||
const { data } = await api.get(`/v1/app-configs/${appKey}`, {
|
||||
const { data } = await api.get(`/v1/apps/${appKey}/config`, {
|
||||
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 { useLazyQuery } from '@apollo/client';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import set from 'lodash/set';
|
||||
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 = /({.*?})/;
|
||||
// TODO: extract this function to a separate file
|
||||
function computeArguments(args, getValues) {
|
||||
const initialValue = {};
|
||||
|
||||
return args.reduce((result, { name, value }) => {
|
||||
const isVariable = variableRegExp.test(value);
|
||||
|
||||
if (isVariable) {
|
||||
const sanitizedFieldPath = value.replace(/{|}/g, '');
|
||||
const computedValue = getValues(sanitizedFieldPath);
|
||||
|
||||
if (computedValue === undefined || computedValue === '')
|
||||
throw new Error(`The ${sanitizedFieldPath} field is required.`);
|
||||
|
||||
set(result, name, computedValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
set(result, name, value);
|
||||
return result;
|
||||
}, initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the dynamic fields for the given step.
|
||||
* This hook must be within a react-hook-form context.
|
||||
@@ -31,10 +39,9 @@ function computeArguments(args, getValues) {
|
||||
*/
|
||||
function useDynamicFields(stepId, schema) {
|
||||
const lastComputedVariables = React.useRef({});
|
||||
const [getDynamicFields, { called, data, loading }] =
|
||||
useLazyQuery(GET_DYNAMIC_FIELDS);
|
||||
const { getValues } = useFormContext();
|
||||
const formValues = getValues();
|
||||
|
||||
/**
|
||||
* Return `null` when even a field is missing value.
|
||||
*
|
||||
@@ -58,31 +65,28 @@ function useDynamicFields(stepId, schema) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
/**
|
||||
* `formValues` is to trigger recomputation when form is updated.
|
||||
* `getValues` is for convenience as it supports paths for fields like `getValues('foo.bar.baz')`.
|
||||
*/
|
||||
}, [schema, formValues, getValues]);
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
schema.type === 'dropdown' &&
|
||||
stepId &&
|
||||
schema.additionalFields &&
|
||||
computedVariables
|
||||
) {
|
||||
getDynamicFields({
|
||||
variables: {
|
||||
stepId,
|
||||
...computedVariables,
|
||||
},
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['dynamicFields', stepId, computedVariables],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.post(`/v1/steps/${stepId}/dynamic-fields`, {
|
||||
dynamicFieldsKey: computedVariables.key,
|
||||
parameters: computedVariables.parameters,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
enabled: !!stepId && !!computedVariables,
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
||||
}, [getDynamicFields, stepId, schema, computedVariables]);
|
||||
return {
|
||||
called,
|
||||
data: data?.getDynamicFields,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
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],
|
||||
);
|
||||
|
||||
if (isUserTrialLoading || !userTrial) return null;
|
||||
if (isUserTrialLoading || !hasTrial) return null;
|
||||
|
||||
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 { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import useNotifications from 'hooks/useNotifications';
|
||||
import useAutomatischNotifications from 'hooks/useAutomatischNotifications';
|
||||
import api from 'helpers/api';
|
||||
|
||||
export default function useVersion() {
|
||||
const { notifications } = useNotifications();
|
||||
const { data: notificationsData } = useAutomatischNotifications();
|
||||
const { data } = useQuery({
|
||||
queryKey: ['automatischVersion'],
|
||||
queryFn: async ({ signal }) => {
|
||||
@@ -17,6 +17,7 @@ export default function useVersion() {
|
||||
},
|
||||
});
|
||||
const version = data?.data?.version;
|
||||
const notifications = notificationsData?.data || [];
|
||||
|
||||
const newVersionCount = notifications.reduce((count, notification) => {
|
||||
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