Compare commits
1 Commits
custom-see
...
helix-new-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6c7470472f |
@@ -28,7 +28,7 @@ cd packages/web
|
|||||||
rm -rf .env
|
rm -rf .env
|
||||||
echo "
|
echo "
|
||||||
PORT=$WEB_PORT
|
PORT=$WEB_PORT
|
||||||
REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT
|
REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
|
||||||
" >> .env
|
" >> .env
|
||||||
cd $CURRENT_DIR
|
cd $CURRENT_DIR
|
||||||
|
|
||||||
|
18
.eslintrc.js
Normal file
18
.eslintrc.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier',
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['**/*.test.ts', '**/test/**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/ban-ts-comment': ['off'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- run: cd packages/backend && yarn lint
|
- run: yarn lint
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
start-backend-server:
|
start-backend-server:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
5
.github/workflows/playwright.yml
vendored
5
.github/workflows/playwright.yml
vendored
@@ -62,9 +62,8 @@ jobs:
|
|||||||
run: yarn && yarn lerna bootstrap
|
run: yarn && yarn lerna bootstrap
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
- name: Build Automatisch web
|
- name: Build Automatisch
|
||||||
working-directory: ./packages/web
|
run: yarn lerna run --scope=@*/{web,cli} build
|
||||||
run: yarn build
|
|
||||||
env:
|
env:
|
||||||
# Keep this until we clean up warnings in build processes
|
# Keep this until we clean up warnings in build processes
|
||||||
CI: false
|
CI: false
|
||||||
|
@@ -6,7 +6,7 @@ ENV PORT 3000
|
|||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual build-dependencies python3 build-base git
|
apk --no-cache add --virtual build-dependencies python3 build-base git
|
||||||
|
|
||||||
RUN git clone https://github.com/automatisch/automatisch.git
|
RUN git clone -b helix-new-endpoint https://github.com/automatisch/automatisch.git
|
||||||
|
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||||
"start:web": "lerna run --stream --scope=@*/web dev",
|
"start:web": "lerna run --stream --scope=@*/web dev",
|
||||||
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
||||||
|
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint",
|
||||||
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
@@ -20,6 +21,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||||
|
"@typescript-eslint/parser": "^5.9.1",
|
||||||
"eslint": "^8.13.0",
|
"eslint": "^8.13.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
@@ -18,8 +18,8 @@ async function fetchAdminRole() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(
|
export async function createUser(
|
||||||
email = appConfig.seedUserEmail,
|
email = 'user@automatisch.io',
|
||||||
password = appConfig.seedUserPassword
|
password = 'sample'
|
||||||
) {
|
) {
|
||||||
const UNIQUE_VIOLATION_CODE = '23505';
|
const UNIQUE_VIOLATION_CODE = '23505';
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
"start:worker": "node src/worker.js",
|
"start:worker": "node src/worker.js",
|
||||||
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
||||||
"test": "APP_ENV=test vitest run",
|
"test": "APP_ENV=test vitest run",
|
||||||
"lint": "eslint .",
|
"lint": "eslint . --ignore-path ../../.eslintignore",
|
||||||
"db:create": "node ./bin/database/create.js",
|
"db:create": "node ./bin/database/create.js",
|
||||||
"db:seed:user": "node ./bin/database/seed-user.js",
|
"db:seed:user": "node ./bin/database/seed-user.js",
|
||||||
"db:drop": "node ./bin/database/drop.js",
|
"db:drop": "node ./bin/database/drop.js",
|
||||||
@@ -95,6 +95,7 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/utils": "^7.0.2",
|
||||||
"nodemon": "^2.0.13",
|
"nodemon": "^2.0.13",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"vitest": "^1.1.3"
|
"vitest": "^1.1.3"
|
||||||
|
@@ -5,38 +5,19 @@ export default defineAction({
|
|||||||
key: 'newChat',
|
key: 'newChat',
|
||||||
description: 'Create a new chat session for Helix AI.',
|
description: 'Create a new chat session for Helix AI.',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
|
||||||
label: 'Session ID',
|
|
||||||
key: 'sessionId',
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'ID of the chat session to continue. Leave empty to start a new chat.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'System Prompt',
|
|
||||||
key: 'systemPrompt',
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'Optional system prompt to start the chat with. It will be used only for new chat sessions.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Input',
|
label: 'Input',
|
||||||
key: 'input',
|
key: 'input',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'User input to start the chat with.',
|
description: 'Prompt to start the chat with.',
|
||||||
variables: true,
|
variables: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
async run($) {
|
async run($) {
|
||||||
const response = await $.http.post('/api/v1/sessions/chat', {
|
const response = await $.http.post('/api/v1/sessions/chat', {
|
||||||
session_id: $.step.parameters.sessionId,
|
session_id: '',
|
||||||
system: $.step.parameters.systemPrompt,
|
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
|
@@ -94,8 +94,6 @@ const appConfig = {
|
|||||||
disableFavicon: process.env.DISABLE_FAVICON === 'true',
|
disableFavicon: process.env.DISABLE_FAVICON === 'true',
|
||||||
additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK,
|
additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK,
|
||||||
additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT,
|
additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT,
|
||||||
seedUserEmail: process.env.SEED_USER_EMAIL || 'user@automatisch.io',
|
|
||||||
seedUserPassword: process.env.SEED_USER_PASSWORD || 'sample',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!appConfig.encryptionKey) {
|
if (!appConfig.encryptionKey) {
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
import App from '../../../../models/app.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const substeps = await App.findActionSubsteps(
|
|
||||||
request.params.appKey,
|
|
||||||
request.params.actionKey
|
|
||||||
);
|
|
||||||
|
|
||||||
renderObject(response, substeps);
|
|
||||||
};
|
|
@@ -1,52 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import App from '../../../../models/app';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import getActionSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-action-substeps.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => {
|
|
||||||
let currentUser, exampleApp, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
exampleApp = await App.findOneByKey('github');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the app auth info', async () => {
|
|
||||||
const actions = await App.findActionsByKey('github');
|
|
||||||
const exampleAction = actions.find(
|
|
||||||
(action) => action.key === 'createIssue'
|
|
||||||
);
|
|
||||||
|
|
||||||
const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/${exampleAction.key}/substeps`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(endpointUrl)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getActionSubstepsMock(exampleAction.substeps);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for invalid app key', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/invalid-app-key/actions/invalid-actions-key/substeps')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return empty array for invalid action key', async () => {
|
|
||||||
const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/invalid-action-key/substeps`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(endpointUrl)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.data).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,8 +0,0 @@
|
|||||||
import App from '../../../../models/app.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const actions = await App.findActionsByKey(request.params.appKey);
|
|
||||||
|
|
||||||
renderObject(response, actions, { serializer: 'Action' });
|
|
||||||
};
|
|
@@ -1,35 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import App from '../../../../models/app';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import getActionsMock from '../../../../../test/mocks/rest/api/v1/apps/get-actions.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/actions', () => {
|
|
||||||
let currentUser, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the app actions', async () => {
|
|
||||||
const exampleApp = await App.findOneByKey('github');
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/apps/${exampleApp.key}/actions`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getActionsMock(exampleApp.actions);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for invalid app key', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/invalid-app-key/actions')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,16 +0,0 @@
|
|||||||
import App from '../../../../models/app.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
let apps = await App.findAll(request.query.name);
|
|
||||||
|
|
||||||
if (request.query.onlyWithTriggers) {
|
|
||||||
apps = apps.filter((app) => app.triggers?.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.query.onlyWithActions) {
|
|
||||||
apps = apps.filter((app) => app.actions?.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderObject(response, apps, { serializer: 'App' });
|
|
||||||
};
|
|
@@ -1,63 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import App from '../../../../models/app';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import getAppsMock from '../../../../../test/mocks/rest/api/v1/apps/get-apps.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps', () => {
|
|
||||||
let currentUser, apps, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
apps = await App.findAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return all apps', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/apps')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAppsMock(apps);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return all apps filtered by name', async () => {
|
|
||||||
const appsWithNameGit = apps.filter((app) => app.name.includes('Git'));
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/apps?name=Git')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAppsMock(appsWithNameGit);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return only the apps with triggers', async () => {
|
|
||||||
const appsWithTriggers = apps.filter((app) => app.triggers?.length > 0);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/apps?onlyWithTriggers=true')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAppsMock(appsWithTriggers);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return only the apps with actions', async () => {
|
|
||||||
const appsWithActions = apps.filter((app) => app.actions?.length > 0);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/apps?onlyWithActions=true')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAppsMock(appsWithActions);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,8 +0,0 @@
|
|||||||
import App from '../../../../models/app.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const auth = await App.findAuthByKey(request.params.appKey);
|
|
||||||
|
|
||||||
renderObject(response, auth, { serializer: 'Auth' });
|
|
||||||
};
|
|
@@ -1,35 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import App from '../../../../models/app';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import getAuthMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/auth', () => {
|
|
||||||
let currentUser, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the app auth info', async () => {
|
|
||||||
const exampleApp = await App.findOneByKey('github');
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/apps/${exampleApp.key}/auth`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAuthMock(exampleApp.auth);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for invalid app key', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/invalid-app-key/auth')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,11 +0,0 @@
|
|||||||
import App from '../../../../models/app.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const substeps = await App.findTriggerSubsteps(
|
|
||||||
request.params.appKey,
|
|
||||||
request.params.triggerKey
|
|
||||||
);
|
|
||||||
|
|
||||||
renderObject(response, substeps);
|
|
||||||
};
|
|
@@ -1,52 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import App from '../../../../models/app';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import getTriggerSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-trigger-substeps.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => {
|
|
||||||
let currentUser, exampleApp, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
exampleApp = await App.findOneByKey('github');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the app auth info', async () => {
|
|
||||||
const triggers = await App.findTriggersByKey('github');
|
|
||||||
const exampleTrigger = triggers.find(
|
|
||||||
(trigger) => trigger.key === 'newIssues'
|
|
||||||
);
|
|
||||||
|
|
||||||
const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/${exampleTrigger.key}/substeps`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(endpointUrl)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getTriggerSubstepsMock(exampleTrigger.substeps);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for invalid app key', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/invalid-app-key/triggers/invalid-trigger-key/substeps')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return empty array for invalid trigger key', async () => {
|
|
||||||
const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/invalid-trigger-key/substeps`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(endpointUrl)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.data).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,8 +0,0 @@
|
|||||||
import App from '../../../../models/app.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const triggers = await App.findTriggersByKey(request.params.appKey);
|
|
||||||
|
|
||||||
renderObject(response, triggers, { serializer: 'Trigger' });
|
|
||||||
};
|
|
@@ -1,35 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import App from '../../../../models/app';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import getTriggersMock from '../../../../../test/mocks/rest/api/v1/apps/get-triggers.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/triggers', () => {
|
|
||||||
let currentUser, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the app triggers', async () => {
|
|
||||||
const exampleApp = await App.findOneByKey('github');
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/apps/${exampleApp.key}/triggers`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getTriggersMock(exampleApp.triggers);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for invalid app key', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/invalid-app-key/triggers')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,23 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import paginateRest from '../../../../helpers/pagination-rest.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const execution = await request.currentUser.authorizedExecutions
|
|
||||||
.clone()
|
|
||||||
.withSoftDeleted()
|
|
||||||
.findById(request.params.executionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const executionStepsQuery = execution
|
|
||||||
.$relatedQuery('executionSteps')
|
|
||||||
.withSoftDeleted()
|
|
||||||
.withGraphFetched('step')
|
|
||||||
.orderBy('created_at', 'asc');
|
|
||||||
|
|
||||||
const executionSteps = await paginateRest(
|
|
||||||
executionStepsQuery,
|
|
||||||
request.query.page
|
|
||||||
);
|
|
||||||
|
|
||||||
renderObject(response, executionSteps);
|
|
||||||
};
|
|
@@ -1,153 +0,0 @@
|
|||||||
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.js';
|
|
||||||
import { createStep } from '../../../../../test/factories/step.js';
|
|
||||||
import { createExecution } from '../../../../../test/factories/execution.js';
|
|
||||||
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission';
|
|
||||||
import getExecutionStepsMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution-steps';
|
|
||||||
|
|
||||||
describe('GET /api/v1/executions/:executionId/execution-steps', () => {
|
|
||||||
let currentUser, currentUserRole, anotherUser, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
anotherUser = await createUser();
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution steps of current user execution', async () => {
|
|
||||||
const currentUserFlow = await createFlow({
|
|
||||||
userId: currentUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepOne = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepTwo = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserExecution = await createExecution({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserExecutionStepOne = await createExecutionStep({
|
|
||||||
executionId: currentUserExecution.id,
|
|
||||||
stepId: stepOne.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserExecutionStepTwo = await createExecutionStep({
|
|
||||||
executionId: currentUserExecution.id,
|
|
||||||
stepId: stepTwo.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/executions/${currentUserExecution.id}/execution-steps`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getExecutionStepsMock(
|
|
||||||
[currentUserExecutionStepOne, currentUserExecutionStepTwo],
|
|
||||||
[stepOne, stepTwo]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution steps of another user execution', async () => {
|
|
||||||
const anotherUserFlow = await createFlow({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepOne = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepTwo = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserExecution = await createExecution({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserExecutionStepOne = await createExecutionStep({
|
|
||||||
executionId: anotherUserExecution.id,
|
|
||||||
stepId: stepOne.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserExecutionStepTwo = await createExecutionStep({
|
|
||||||
executionId: anotherUserExecution.id,
|
|
||||||
stepId: stepTwo.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/executions/${anotherUserExecution.id}/execution-steps`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getExecutionStepsMock(
|
|
||||||
[anotherUserExecutionStepOne, anotherUserExecutionStepTwo],
|
|
||||||
[stepOne, stepTwo]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for not existing execution step UUID', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const notExistingExcecutionUUID = Crypto.randomUUID();
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/api/v1/executions/${notExistingExcecutionUUID}/execution-steps`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return bad request response for invalid UUID', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/executions/invalidExecutionUUID/execution-steps')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(400);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,15 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const execution = await request.currentUser.authorizedExecutions
|
|
||||||
.withGraphFetched({
|
|
||||||
flow: {
|
|
||||||
steps: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.withSoftDeleted()
|
|
||||||
.findById(request.params.executionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
renderObject(response, execution);
|
|
||||||
};
|
|
@@ -1,134 +0,0 @@
|
|||||||
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.js';
|
|
||||||
import { createStep } from '../../../../../test/factories/step.js';
|
|
||||||
import { createExecution } from '../../../../../test/factories/execution.js';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission';
|
|
||||||
import getExecutionMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution';
|
|
||||||
|
|
||||||
describe('GET /api/v1/executions/:executionId', () => {
|
|
||||||
let currentUser, currentUserRole, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution data of current user', async () => {
|
|
||||||
const currentUserFlow = await createFlow({
|
|
||||||
userId: currentUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepOne = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepTwo = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserExecution = await createExecution({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/executions/${currentUserExecution.id}`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getExecutionMock(
|
|
||||||
currentUserExecution,
|
|
||||||
currentUserFlow,
|
|
||||||
[stepOne, stepTwo]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution data of another user', async () => {
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
|
|
||||||
const anotherUserFlow = await createFlow({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepOne = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepTwo = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserExecution = await createExecution({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/executions/${anotherUserExecution.id}`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getExecutionMock(
|
|
||||||
anotherUserExecution,
|
|
||||||
anotherUserFlow,
|
|
||||||
[stepOne, stepTwo]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for not existing execution UUID', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const notExistingExcecutionUUID = Crypto.randomUUID();
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/api/v1/executions/${notExistingExcecutionUUID}`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return bad request response for invalid UUID', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/executions/invalidExecutionUUID')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(400);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,26 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import paginateRest from '../../../../helpers/pagination-rest.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const executionsQuery = request.currentUser.authorizedExecutions
|
|
||||||
.withSoftDeleted()
|
|
||||||
.orderBy('created_at', 'desc')
|
|
||||||
.withGraphFetched({
|
|
||||||
flow: {
|
|
||||||
steps: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const executions = await paginateRest(executionsQuery, request.query.page);
|
|
||||||
|
|
||||||
for (const execution of executions.records) {
|
|
||||||
const executionSteps = await execution.$relatedQuery('executionSteps');
|
|
||||||
const status = executionSteps.some((step) => step.status === 'failure')
|
|
||||||
? 'failure'
|
|
||||||
: 'success';
|
|
||||||
|
|
||||||
execution.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderObject(response, executions);
|
|
||||||
};
|
|
@@ -1,113 +0,0 @@
|
|||||||
import { 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';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
|
||||||
import { createFlow } from '../../../../../test/factories/flow.js';
|
|
||||||
import { createStep } from '../../../../../test/factories/step.js';
|
|
||||||
import { createExecution } from '../../../../../test/factories/execution.js';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission';
|
|
||||||
import getExecutionsMock from '../../../../../test/mocks/rest/api/v1/executions/get-executions';
|
|
||||||
|
|
||||||
describe('GET /api/v1/executions', () => {
|
|
||||||
let currentUser, currentUserRole, anotherUser, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
anotherUser = await createUser();
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the executions of current user', async () => {
|
|
||||||
const currentUserFlow = await createFlow({
|
|
||||||
userId: currentUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepOne = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepTwo = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserExecutionOne = await createExecution({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserExecutionTwo = await createExecution({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
deletedAt: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/executions')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getExecutionsMock(
|
|
||||||
[currentUserExecutionTwo, currentUserExecutionOne],
|
|
||||||
currentUserFlow,
|
|
||||||
[stepOne, stepTwo]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the executions of another user', async () => {
|
|
||||||
const anotherUserFlow = await createFlow({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepOne = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
|
|
||||||
const stepTwo = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserExecutionOne = await createExecution({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserExecutionTwo = await createExecution({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
deletedAt: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/executions')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getExecutionsMock(
|
|
||||||
[anotherUserExecutionTwo, anotherUserExecutionOne],
|
|
||||||
anotherUserFlow,
|
|
||||||
[stepOne, stepTwo]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -2,29 +2,15 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import app from '../../../../app.js';
|
import app from '../../../../app.js';
|
||||||
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
|
||||||
import { createPermission } from '../../../../../test/factories/permission';
|
|
||||||
import { createRole } from '../../../../../test/factories/role';
|
|
||||||
import { createUser } from '../../../../../test/factories/user';
|
import { createUser } from '../../../../../test/factories/user';
|
||||||
import getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user';
|
import getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user';
|
||||||
|
|
||||||
describe('GET /api/v1/users/me', () => {
|
describe('GET /api/v1/users/me', () => {
|
||||||
let role, permissionOne, permissionTwo, currentUser, token;
|
let role, currentUser, token;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
role = await createRole();
|
currentUser = await createUser();
|
||||||
|
role = await currentUser.$relatedQuery('role');
|
||||||
permissionOne = await createPermission({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
permissionTwo = await createPermission({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,11 +20,7 @@ describe('GET /api/v1/users/me', () => {
|
|||||||
.set('Authorization', token)
|
.set('Authorization', token)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedPayload = getCurrentUserMock(currentUser, role, [
|
const expectedPayload = getCurrentUserMock(currentUser, role);
|
||||||
permissionOne,
|
|
||||||
permissionTwo,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
expect(response.body).toEqual(expectedPayload);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
21
packages/backend/src/graphql/queries/get-automatisch-info.js
Normal file
21
packages/backend/src/graphql/queries/get-automatisch-info.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import appConfig from '../../config/app.js';
|
||||||
|
import { getLicense } from '../../helpers/license.ee.js';
|
||||||
|
|
||||||
|
const getAutomatischInfo = async () => {
|
||||||
|
const license = await getLicense();
|
||||||
|
|
||||||
|
const computedLicense = {
|
||||||
|
id: license ? license.id : null,
|
||||||
|
name: license ? license.name : null,
|
||||||
|
expireAt: license ? license.expireAt : null,
|
||||||
|
verified: license ? true : false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCloud: appConfig.isCloud,
|
||||||
|
isMation: appConfig.isMation,
|
||||||
|
license: computedLicense,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAutomatischInfo;
|
@@ -0,0 +1,190 @@
|
|||||||
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import request from 'supertest';
|
||||||
|
import app from '../../app';
|
||||||
|
import * as license from '../../helpers/license.ee';
|
||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
describe('graphQL getAutomatischInfo query', () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getAutomatischInfo {
|
||||||
|
isCloud
|
||||||
|
isMation
|
||||||
|
license {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
expireAt
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('and without valid license', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(license, 'getLicense').mockResolvedValue(false);
|
||||||
|
|
||||||
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||||
|
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
|
license: {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
expireAt: null,
|
||||||
|
verified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with valid license', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const mockedLicense = {
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
verified: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.spyOn(license, 'getLicense').mockResolvedValue(mockedLicense);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with cloud flag enabled', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isCloud: true,
|
||||||
|
isMation: false,
|
||||||
|
license: {
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with cloud flag disabled', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
|
license: {
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with mation flag enabled', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||||
|
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isCloud: false,
|
||||||
|
isMation: true,
|
||||||
|
license: {
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with mation flag disabled', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
|
||||||
|
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isMation: false,
|
||||||
|
isCloud: false,
|
||||||
|
license: {
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -3,6 +3,7 @@ import getAppAuthClient from './queries/get-app-auth-client.ee.js';
|
|||||||
import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
|
import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
|
||||||
import getAppConfig from './queries/get-app-config.ee.js';
|
import getAppConfig from './queries/get-app-config.ee.js';
|
||||||
import getApps from './queries/get-apps.js';
|
import getApps from './queries/get-apps.js';
|
||||||
|
import getAutomatischInfo from './queries/get-automatisch-info.js';
|
||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
||||||
import getConfig from './queries/get-config.ee.js';
|
import getConfig from './queries/get-config.ee.js';
|
||||||
import getConnectedApps from './queries/get-connected-apps.js';
|
import getConnectedApps from './queries/get-connected-apps.js';
|
||||||
@@ -38,6 +39,7 @@ const queryResolvers = {
|
|||||||
getAppAuthClients,
|
getAppAuthClients,
|
||||||
getAppConfig,
|
getAppConfig,
|
||||||
getApps,
|
getApps,
|
||||||
|
getAutomatischInfo,
|
||||||
getBillingAndUsage,
|
getBillingAndUsage,
|
||||||
getConfig,
|
getConfig,
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
|
@@ -40,6 +40,7 @@ type Query {
|
|||||||
key: String!
|
key: String!
|
||||||
parameters: JSONObject
|
parameters: JSONObject
|
||||||
): [SubstepArgument]
|
): [SubstepArgument]
|
||||||
|
getAutomatischInfo: GetAutomatischInfo
|
||||||
getBillingAndUsage: GetBillingAndUsage
|
getBillingAndUsage: GetBillingAndUsage
|
||||||
getCurrentUser: User
|
getCurrentUser: User
|
||||||
getConfig(keys: [String]): JSONObject
|
getConfig(keys: [String]): JSONObject
|
||||||
@@ -643,6 +644,12 @@ type AppHealth {
|
|||||||
version: String
|
version: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetAutomatischInfo {
|
||||||
|
isCloud: Boolean
|
||||||
|
isMation: Boolean
|
||||||
|
license: License
|
||||||
|
}
|
||||||
|
|
||||||
type License {
|
type License {
|
||||||
id: String
|
id: String
|
||||||
name: String
|
name: String
|
||||||
|
@@ -42,6 +42,7 @@ const isAuthenticatedRule = rule()(isAuthenticated);
|
|||||||
export const authenticationRules = {
|
export const authenticationRules = {
|
||||||
Query: {
|
Query: {
|
||||||
'*': isAuthenticatedRule,
|
'*': isAuthenticatedRule,
|
||||||
|
getAutomatischInfo: allow,
|
||||||
getConfig: allow,
|
getConfig: allow,
|
||||||
getNotifications: allow,
|
getNotifications: allow,
|
||||||
healthcheck: allow,
|
healthcheck: allow,
|
||||||
|
@@ -11,18 +11,6 @@ const authorizationList = {
|
|||||||
action: 'read',
|
action: 'read',
|
||||||
subject: 'Flow',
|
subject: 'Flow',
|
||||||
},
|
},
|
||||||
'GET /api/v1/executions/:executionId': {
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
},
|
|
||||||
'GET /api/v1/executions/': {
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
},
|
|
||||||
'GET /api/v1/executions/:executionId/execution-steps': {
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Execution',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authorizeUser = async (request, response, next) => {
|
export const authorizeUser = async (request, response, next) => {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import logger from './logger.js';
|
import logger from './logger.js';
|
||||||
import objection from 'objection';
|
import objection from 'objection';
|
||||||
import * as Sentry from './sentry.ee.js';
|
|
||||||
const { NotFoundError, DataError } = objection;
|
const { NotFoundError, DataError } = objection;
|
||||||
|
|
||||||
// Do not remove `next` argument as the function signature will not fit for an error handler middleware
|
// Do not remove `next` argument as the function signature will not fit for an error handler middleware
|
||||||
@@ -18,21 +17,8 @@ const errorHandler = (error, request, response, next) => {
|
|||||||
response.status(400).end();
|
response.status(400).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusCode = error.statusCode || 500;
|
logger.error(error.message + '\n' + error.stack);
|
||||||
|
response.status(error.statusCode || 500).end();
|
||||||
logger.error(request.method + ' ' + request.url + ' ' + statusCode);
|
|
||||||
logger.error(error.stack);
|
|
||||||
|
|
||||||
Sentry.captureException(error, {
|
|
||||||
tags: { rest: true },
|
|
||||||
extra: {
|
|
||||||
url: request?.url,
|
|
||||||
method: request?.method,
|
|
||||||
params: request?.params,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
response.status(statusCode).end();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const notFoundAppError = (error) => {
|
const notFoundAppError = (error) => {
|
||||||
|
@@ -3,13 +3,10 @@ import * as Tracing from '@sentry/tracing';
|
|||||||
|
|
||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
|
|
||||||
const isSentryEnabled = () => {
|
const isSentryEnabled = !!appConfig.sentryDsn;
|
||||||
if (appConfig.isDev || appConfig.isTest) return false;
|
|
||||||
return !!appConfig.sentryDsn;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function init(app) {
|
export function init(app) {
|
||||||
if (!isSentryEnabled()) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
return Sentry.init({
|
return Sentry.init({
|
||||||
enabled: !!appConfig.sentryDsn,
|
enabled: !!appConfig.sentryDsn,
|
||||||
@@ -24,19 +21,19 @@ export function init(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function attachRequestHandler(app) {
|
export function attachRequestHandler(app) {
|
||||||
if (!isSentryEnabled()) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
app.use(Sentry.Handlers.requestHandler());
|
app.use(Sentry.Handlers.requestHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachTracingHandler(app) {
|
export function attachTracingHandler(app) {
|
||||||
if (!isSentryEnabled()) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
app.use(Sentry.Handlers.tracingHandler());
|
app.use(Sentry.Handlers.tracingHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachErrorHandler(app) {
|
export function attachErrorHandler(app) {
|
||||||
if (!isSentryEnabled()) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
Sentry.Handlers.errorHandler({
|
Sentry.Handlers.errorHandler({
|
||||||
@@ -49,7 +46,7 @@ export function attachErrorHandler(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function captureException(exception, captureContext) {
|
export function captureException(exception, captureContext) {
|
||||||
if (!isSentryEnabled()) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
return Sentry.captureException(exception, captureContext);
|
return Sentry.captureException(exception, captureContext);
|
||||||
}
|
}
|
||||||
|
@@ -39,47 +39,6 @@ class App {
|
|||||||
return appInfoConverter(rawAppData);
|
return appInfoConverter(rawAppData);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async findAuthByKey(key, stripFuncs = false) {
|
|
||||||
const rawAppData = await getApp(key, stripFuncs);
|
|
||||||
const appData = appInfoConverter(rawAppData);
|
|
||||||
|
|
||||||
return appData?.auth || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
static async findTriggersByKey(key, stripFuncs = false) {
|
|
||||||
const rawAppData = await getApp(key, stripFuncs);
|
|
||||||
const appData = appInfoConverter(rawAppData);
|
|
||||||
|
|
||||||
return appData?.triggers || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static async findTriggerSubsteps(appKey, triggerKey, stripFuncs = false) {
|
|
||||||
const rawAppData = await getApp(appKey, stripFuncs);
|
|
||||||
const appData = appInfoConverter(rawAppData);
|
|
||||||
|
|
||||||
const trigger = appData?.triggers?.find(
|
|
||||||
(trigger) => trigger.key === triggerKey
|
|
||||||
);
|
|
||||||
|
|
||||||
return trigger?.substeps || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static async findActionsByKey(key, stripFuncs = false) {
|
|
||||||
const rawAppData = await getApp(key, stripFuncs);
|
|
||||||
const appData = appInfoConverter(rawAppData);
|
|
||||||
|
|
||||||
return appData?.actions || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static async findActionSubsteps(appKey, actionKey, stripFuncs = false) {
|
|
||||||
const rawAppData = await getApp(appKey, stripFuncs);
|
|
||||||
const appData = appInfoConverter(rawAppData);
|
|
||||||
|
|
||||||
const action = appData?.actions?.find((action) => action.key === actionKey);
|
|
||||||
|
|
||||||
return action?.substeps || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static async checkAppAndAction(appKey, actionKey) {
|
static async checkAppAndAction(appKey, actionKey) {
|
||||||
const app = await this.findOneByKey(appKey);
|
const app = await this.findOneByKey(appKey);
|
||||||
|
|
||||||
|
@@ -149,13 +149,6 @@ class User extends Base {
|
|||||||
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
|
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
get authorizedExecutions() {
|
|
||||||
const conditions = this.can('read', 'Execution');
|
|
||||||
return conditions.isCreator
|
|
||||||
? this.$relatedQuery('executions')
|
|
||||||
: Execution.query();
|
|
||||||
}
|
|
||||||
|
|
||||||
login(password) {
|
login(password) {
|
||||||
return bcrypt.compare(password, this.password);
|
return bcrypt.compare(password, this.password);
|
||||||
}
|
}
|
||||||
|
@@ -2,41 +2,9 @@ import { Router } from 'express';
|
|||||||
import asyncHandler from 'express-async-handler';
|
import asyncHandler from 'express-async-handler';
|
||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
import { authenticateUser } from '../../../helpers/authentication.js';
|
||||||
import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
|
import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
|
||||||
import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js';
|
|
||||||
import getAuthAction from '../../../controllers/api/v1/apps/get-auth.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';
|
|
||||||
import getActionSubstepsAction from '../../../controllers/api/v1/apps/get-action-substeps.js';
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/', authenticateUser, asyncHandler(getAppsAction));
|
|
||||||
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
|
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
|
||||||
router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction));
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:appKey/triggers',
|
|
||||||
authenticateUser,
|
|
||||||
asyncHandler(getTriggersAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:appKey/triggers/:triggerKey/substeps',
|
|
||||||
authenticateUser,
|
|
||||||
asyncHandler(getTriggerSubstepsAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:appKey/actions',
|
|
||||||
authenticateUser,
|
|
||||||
asyncHandler(getActionsAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:appKey/actions/:actionKey/substeps',
|
|
||||||
authenticateUser,
|
|
||||||
asyncHandler(getActionSubstepsAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
import { Router } from 'express';
|
|
||||||
import asyncHandler from 'express-async-handler';
|
|
||||||
import { authenticateUser } from '../../../helpers/authentication.js';
|
|
||||||
import { authorizeUser } from '../../../helpers/authorization.js';
|
|
||||||
import getExecutionsAction from '../../../controllers/api/v1/executions/get-executions.js';
|
|
||||||
import getExecutionAction from '../../../controllers/api/v1/executions/get-execution.js';
|
|
||||||
import getExecutionStepsAction from '../../../controllers/api/v1/executions/get-execution-steps.js';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/',
|
|
||||||
authenticateUser,
|
|
||||||
authorizeUser,
|
|
||||||
asyncHandler(getExecutionsAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:executionId',
|
|
||||||
authenticateUser,
|
|
||||||
authorizeUser,
|
|
||||||
asyncHandler(getExecutionAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:executionId/execution-steps',
|
|
||||||
authenticateUser,
|
|
||||||
authorizeUser,
|
|
||||||
asyncHandler(getExecutionStepsAction)
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
|
@@ -9,7 +9,6 @@ import paymentRouter from './api/v1/payment.ee.js';
|
|||||||
import appAuthClientsRouter from './api/v1/app-auth-clients.js';
|
import appAuthClientsRouter from './api/v1/app-auth-clients.js';
|
||||||
import flowsRouter from './api/v1/flows.js';
|
import flowsRouter from './api/v1/flows.js';
|
||||||
import appsRouter from './api/v1/apps.js';
|
import appsRouter from './api/v1/apps.js';
|
||||||
import executionsRouter from './api/v1/executions.js';
|
|
||||||
import samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
|
import samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
|
||||||
import rolesRouter from './api/v1/admin/roles.ee.js';
|
import rolesRouter from './api/v1/admin/roles.ee.js';
|
||||||
import permissionsRouter from './api/v1/admin/permissions.ee.js';
|
import permissionsRouter from './api/v1/admin/permissions.ee.js';
|
||||||
@@ -28,7 +27,6 @@ router.use('/api/v1/payment', paymentRouter);
|
|||||||
router.use('/api/v1/app-auth-clients', appAuthClientsRouter);
|
router.use('/api/v1/app-auth-clients', appAuthClientsRouter);
|
||||||
router.use('/api/v1/flows', flowsRouter);
|
router.use('/api/v1/flows', flowsRouter);
|
||||||
router.use('/api/v1/apps', appsRouter);
|
router.use('/api/v1/apps', appsRouter);
|
||||||
router.use('/api/v1/executions', executionsRouter);
|
|
||||||
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
|
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
|
||||||
router.use('/api/v1/admin/roles', rolesRouter);
|
router.use('/api/v1/admin/roles', rolesRouter);
|
||||||
router.use('/api/v1/admin/permissions', permissionsRouter);
|
router.use('/api/v1/admin/permissions', permissionsRouter);
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
const actionSerializer = (action) => {
|
|
||||||
return {
|
|
||||||
name: action.name,
|
|
||||||
key: action.key,
|
|
||||||
description: action.description,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default actionSerializer;
|
|
@@ -1,21 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import App from '../models/app';
|
|
||||||
import actionSerializer from './action';
|
|
||||||
|
|
||||||
describe('actionSerializer', () => {
|
|
||||||
it('should return the action data', async () => {
|
|
||||||
const actions = await App.findActionsByKey('github');
|
|
||||||
const action = actions[0];
|
|
||||||
|
|
||||||
const expectedPayload = {
|
|
||||||
description: action.description,
|
|
||||||
key: action.key,
|
|
||||||
name: action.name,
|
|
||||||
pollInterval: action.pollInterval,
|
|
||||||
showWebhookUrl: action.showWebhookUrl,
|
|
||||||
type: action.type,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(actionSerializer(action)).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -3,7 +3,7 @@ import App from '../models/app';
|
|||||||
import appSerializer from './app';
|
import appSerializer from './app';
|
||||||
|
|
||||||
describe('appSerializer', () => {
|
describe('appSerializer', () => {
|
||||||
it('should return app data', async () => {
|
it('should return permission data', async () => {
|
||||||
const app = await App.findOneByKey('deepl');
|
const app = await App.findOneByKey('deepl');
|
||||||
|
|
||||||
const expectedPayload = {
|
const expectedPayload = {
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
const authSerializer = (auth) => {
|
|
||||||
return {
|
|
||||||
fields: auth.fields,
|
|
||||||
authenticationSteps: auth.authenticationSteps,
|
|
||||||
reconnectionSteps: auth.reconnectionSteps,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default authSerializer;
|
|
@@ -1,17 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import App from '../models/app';
|
|
||||||
import authSerializer from './auth';
|
|
||||||
|
|
||||||
describe('authSerializer', () => {
|
|
||||||
it('should return auth data', async () => {
|
|
||||||
const auth = await App.findAuthByKey('deepl');
|
|
||||||
|
|
||||||
const expectedPayload = {
|
|
||||||
fields: auth.fields,
|
|
||||||
authenticationSteps: auth.authenticationSteps,
|
|
||||||
reconnectionSteps: auth.reconnectionSteps,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(authSerializer(auth)).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,21 +0,0 @@
|
|||||||
import stepSerializer from './step.js';
|
|
||||||
|
|
||||||
const executionStepSerializer = (executionStep) => {
|
|
||||||
let executionStepData = {
|
|
||||||
id: executionStep.id,
|
|
||||||
dataIn: executionStep.dataIn,
|
|
||||||
dataOut: executionStep.dataOut,
|
|
||||||
errorDetails: executionStep.errorDetails,
|
|
||||||
status: executionStep.status,
|
|
||||||
createdAt: executionStep.createdAt.getTime(),
|
|
||||||
updatedAt: executionStep.updatedAt.getTime(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (executionStep.step) {
|
|
||||||
executionStepData.step = stepSerializer(executionStep.step);
|
|
||||||
}
|
|
||||||
|
|
||||||
return executionStepData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default executionStepSerializer;
|
|
@@ -1,43 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import executionStepSerializer from './execution-step';
|
|
||||||
import stepSerializer from './step';
|
|
||||||
import { createExecutionStep } from '../../test/factories/execution-step';
|
|
||||||
import { createStep } from '../../test/factories/step';
|
|
||||||
|
|
||||||
describe('executionStepSerializer', () => {
|
|
||||||
let executionStep, step;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
step = await createStep();
|
|
||||||
|
|
||||||
executionStep = await createExecutionStep({
|
|
||||||
stepId: step.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution step data', async () => {
|
|
||||||
const expectedPayload = {
|
|
||||||
id: executionStep.id,
|
|
||||||
dataIn: executionStep.dataIn,
|
|
||||||
dataOut: executionStep.dataOut,
|
|
||||||
errorDetails: executionStep.errorDetails,
|
|
||||||
status: executionStep.status,
|
|
||||||
createdAt: executionStep.createdAt.getTime(),
|
|
||||||
updatedAt: executionStep.updatedAt.getTime(),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(executionStepSerializer(executionStep)).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution step data with the step', async () => {
|
|
||||||
executionStep.step = step;
|
|
||||||
|
|
||||||
const expectedPayload = {
|
|
||||||
step: stepSerializer(step),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(executionStepSerializer(executionStep)).toMatchObject(
|
|
||||||
expectedPayload
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,22 +0,0 @@
|
|||||||
import flowSerializer from './flow.js';
|
|
||||||
|
|
||||||
const executionSerializer = (execution) => {
|
|
||||||
let executionData = {
|
|
||||||
id: execution.id,
|
|
||||||
testRun: execution.testRun,
|
|
||||||
createdAt: execution.createdAt.getTime(),
|
|
||||||
updatedAt: execution.updatedAt.getTime(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (execution.status) {
|
|
||||||
executionData.status = execution.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (execution.flow) {
|
|
||||||
executionData.flow = flowSerializer(execution.flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
return executionData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default executionSerializer;
|
|
@@ -1,52 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import executionSerializer from './execution';
|
|
||||||
import flowSerializer from './flow';
|
|
||||||
import { createExecution } from '../../test/factories/execution';
|
|
||||||
import { createFlow } from '../../test/factories/flow';
|
|
||||||
|
|
||||||
describe('executionSerializer', () => {
|
|
||||||
let flow, execution;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
flow = await createFlow();
|
|
||||||
|
|
||||||
execution = await createExecution({
|
|
||||||
flowId: flow.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution data', async () => {
|
|
||||||
const expectedPayload = {
|
|
||||||
id: execution.id,
|
|
||||||
testRun: execution.testRun,
|
|
||||||
createdAt: execution.createdAt.getTime(),
|
|
||||||
updatedAt: execution.updatedAt.getTime(),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(executionSerializer(execution)).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution data with status', async () => {
|
|
||||||
execution.status = 'success';
|
|
||||||
|
|
||||||
const expectedPayload = {
|
|
||||||
id: execution.id,
|
|
||||||
testRun: execution.testRun,
|
|
||||||
createdAt: execution.createdAt.getTime(),
|
|
||||||
updatedAt: execution.updatedAt.getTime(),
|
|
||||||
status: 'success',
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(executionSerializer(execution)).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the execution data with the flow', async () => {
|
|
||||||
execution.flow = flow;
|
|
||||||
|
|
||||||
const expectedPayload = {
|
|
||||||
flow: flowSerializer(flow),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(executionSerializer(execution)).toMatchObject(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -8,7 +8,7 @@ const flowSerializer = (flow) => {
|
|||||||
status: flow.status,
|
status: flow.status,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (flow.steps?.length > 0) {
|
if (flow.steps) {
|
||||||
flowData.steps = flow.steps.map((step) => stepSerializer(step));
|
flowData.steps = flow.steps.map((step) => stepSerializer(step));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,11 +6,6 @@ import appAuthClientSerializer from './app-auth-client.js';
|
|||||||
import flowSerializer from './flow.js';
|
import flowSerializer from './flow.js';
|
||||||
import stepSerializer from './step.js';
|
import stepSerializer from './step.js';
|
||||||
import appSerializer from './app.js';
|
import appSerializer from './app.js';
|
||||||
import authSerializer from './auth.js';
|
|
||||||
import triggerSerializer from './trigger.js';
|
|
||||||
import actionSerializer from './action.js';
|
|
||||||
import executionSerializer from './execution.js';
|
|
||||||
import executionStepSerializer from './execution-step.js';
|
|
||||||
|
|
||||||
const serializers = {
|
const serializers = {
|
||||||
User: userSerializer,
|
User: userSerializer,
|
||||||
@@ -21,11 +16,6 @@ const serializers = {
|
|||||||
Flow: flowSerializer,
|
Flow: flowSerializer,
|
||||||
Step: stepSerializer,
|
Step: stepSerializer,
|
||||||
App: appSerializer,
|
App: appSerializer,
|
||||||
Auth: authSerializer,
|
|
||||||
Trigger: triggerSerializer,
|
|
||||||
Action: actionSerializer,
|
|
||||||
Execution: executionSerializer,
|
|
||||||
ExecutionStep: executionStepSerializer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default serializers;
|
export default serializers;
|
||||||
|
@@ -11,7 +11,7 @@ const roleSerializer = (role) => {
|
|||||||
isAdmin: role.isAdmin,
|
isAdmin: role.isAdmin,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (role.permissions?.length > 0) {
|
if (role.permissions) {
|
||||||
roleData.permissions = role.permissions.map((permission) =>
|
roleData.permissions = role.permissions.map((permission) =>
|
||||||
permissionSerializer(permission)
|
permissionSerializer(permission)
|
||||||
);
|
);
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
const triggerSerializer = (trigger) => {
|
|
||||||
return {
|
|
||||||
description: trigger.description,
|
|
||||||
key: trigger.key,
|
|
||||||
name: trigger.name,
|
|
||||||
pollInterval: trigger.pollInterval,
|
|
||||||
showWebhookUrl: trigger.showWebhookUrl,
|
|
||||||
type: trigger.type,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default triggerSerializer;
|
|
@@ -1,21 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import App from '../models/app';
|
|
||||||
import triggerSerializer from './trigger';
|
|
||||||
|
|
||||||
describe('triggerSerializer', () => {
|
|
||||||
it('should return the trigger data', async () => {
|
|
||||||
const triggers = await App.findTriggersByKey('github');
|
|
||||||
const trigger = triggers[0];
|
|
||||||
|
|
||||||
const expectedPayload = {
|
|
||||||
description: trigger.description,
|
|
||||||
key: trigger.key,
|
|
||||||
name: trigger.name,
|
|
||||||
pollInterval: trigger.pollInterval,
|
|
||||||
showWebhookUrl: trigger.showWebhookUrl,
|
|
||||||
type: trigger.type,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(triggerSerializer(trigger)).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -15,7 +15,7 @@ const userSerializer = (user) => {
|
|||||||
userData.role = roleSerializer(user.role);
|
userData.role = roleSerializer(user.role);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.permissions?.length > 0) {
|
if (user.permissions) {
|
||||||
userData.permissions = user.permissions.map((permission) =>
|
userData.permissions = user.permissions.map((permission) =>
|
||||||
permissionSerializer(permission)
|
permissionSerializer(permission)
|
||||||
);
|
);
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
const getActionSubstepsMock = (substeps) => {
|
|
||||||
return {
|
|
||||||
data: substeps,
|
|
||||||
meta: {
|
|
||||||
count: substeps.length,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getActionSubstepsMock;
|
|
@@ -1,22 +0,0 @@
|
|||||||
const getActionsMock = (actions) => {
|
|
||||||
const actionsData = actions.map((trigger) => {
|
|
||||||
return {
|
|
||||||
name: trigger.name,
|
|
||||||
key: trigger.key,
|
|
||||||
description: trigger.description,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: actionsData,
|
|
||||||
meta: {
|
|
||||||
count: actions.length,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getActionsMock;
|
|
@@ -1,23 +0,0 @@
|
|||||||
const getAppsMock = (apps) => {
|
|
||||||
const appsData = apps.map((app) => ({
|
|
||||||
authDocUrl: app.authDocUrl,
|
|
||||||
iconUrl: app.iconUrl,
|
|
||||||
key: app.key,
|
|
||||||
name: app.name,
|
|
||||||
primaryColor: app.primaryColor,
|
|
||||||
supportsConnections: app.supportsConnections,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: appsData,
|
|
||||||
meta: {
|
|
||||||
count: appsData.length,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getAppsMock;
|
|
@@ -1,18 +0,0 @@
|
|||||||
const getAuthMock = (auth) => {
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
fields: auth.fields,
|
|
||||||
authenticationSteps: auth.authenticationSteps,
|
|
||||||
reconnectionSteps: auth.reconnectionSteps,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
count: 1,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: false,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getAuthMock;
|
|
@@ -1,14 +0,0 @@
|
|||||||
const getTriggerSubstepsMock = (substeps) => {
|
|
||||||
return {
|
|
||||||
data: substeps,
|
|
||||||
meta: {
|
|
||||||
count: substeps.length,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getTriggerSubstepsMock;
|
|
@@ -1,25 +0,0 @@
|
|||||||
const getTriggersMock = (triggers) => {
|
|
||||||
const triggersData = triggers.map((trigger) => {
|
|
||||||
return {
|
|
||||||
description: trigger.description,
|
|
||||||
key: trigger.key,
|
|
||||||
name: trigger.name,
|
|
||||||
pollInterval: trigger.pollInterval,
|
|
||||||
showWebhookUrl: trigger.showWebhookUrl,
|
|
||||||
type: trigger.type,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: triggersData,
|
|
||||||
meta: {
|
|
||||||
count: triggers.length,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getTriggersMock;
|
|
@@ -1,39 +0,0 @@
|
|||||||
const getExecutionStepsMock = async (executionSteps, steps) => {
|
|
||||||
const data = executionSteps.map((executionStep) => {
|
|
||||||
const step = steps.find((step) => step.id === executionStep.stepId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: executionStep.id,
|
|
||||||
dataIn: executionStep.dataIn,
|
|
||||||
dataOut: executionStep.dataOut,
|
|
||||||
errorDetails: executionStep.errorDetails,
|
|
||||||
status: executionStep.status,
|
|
||||||
createdAt: executionStep.createdAt.getTime(),
|
|
||||||
updatedAt: executionStep.updatedAt.getTime(),
|
|
||||||
step: {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: data,
|
|
||||||
meta: {
|
|
||||||
count: executionSteps.length,
|
|
||||||
currentPage: 1,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: 1,
|
|
||||||
type: 'ExecutionStep',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getExecutionStepsMock;
|
|
@@ -1,38 +0,0 @@
|
|||||||
const getExecutionMock = async (execution, flow, steps) => {
|
|
||||||
const data = {
|
|
||||||
id: execution.id,
|
|
||||||
testRun: execution.testRun,
|
|
||||||
createdAt: execution.createdAt.getTime(),
|
|
||||||
updatedAt: execution.updatedAt.getTime(),
|
|
||||||
flow: {
|
|
||||||
id: flow.id,
|
|
||||||
name: flow.name,
|
|
||||||
active: flow.active,
|
|
||||||
status: flow.active ? 'published' : 'draft',
|
|
||||||
steps: steps.map((step) => ({
|
|
||||||
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,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: data,
|
|
||||||
meta: {
|
|
||||||
count: 1,
|
|
||||||
currentPage: null,
|
|
||||||
isArray: false,
|
|
||||||
totalPages: null,
|
|
||||||
type: 'Execution',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getExecutionMock;
|
|
@@ -1,39 +0,0 @@
|
|||||||
const getExecutionsMock = async (executions, flow, steps) => {
|
|
||||||
const data = executions.map((execution) => ({
|
|
||||||
id: execution.id,
|
|
||||||
testRun: execution.testRun,
|
|
||||||
createdAt: execution.createdAt.getTime(),
|
|
||||||
updatedAt: execution.updatedAt.getTime(),
|
|
||||||
status: 'success',
|
|
||||||
flow: {
|
|
||||||
id: flow.id,
|
|
||||||
name: flow.name,
|
|
||||||
active: flow.active,
|
|
||||||
status: flow.active ? 'published' : 'draft',
|
|
||||||
steps: steps.map((step) => ({
|
|
||||||
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,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: data,
|
|
||||||
meta: {
|
|
||||||
count: executions.length,
|
|
||||||
currentPage: 1,
|
|
||||||
isArray: true,
|
|
||||||
totalPages: 1,
|
|
||||||
type: 'Execution',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getExecutionsMock;
|
|
@@ -1,19 +1,11 @@
|
|||||||
const getCurrentUserMock = (currentUser, role, permissions) => {
|
const getCurrentUserMock = (currentUser, role) => {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
createdAt: currentUser.createdAt.getTime(),
|
createdAt: currentUser.createdAt.getTime(),
|
||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
fullName: currentUser.fullName,
|
fullName: currentUser.fullName,
|
||||||
id: currentUser.id,
|
id: currentUser.id,
|
||||||
permissions: permissions.map((permission) => ({
|
permissions: [],
|
||||||
id: permission.id,
|
|
||||||
roleId: permission.roleId,
|
|
||||||
action: permission.action,
|
|
||||||
subject: permission.subject,
|
|
||||||
conditions: permission.conditions,
|
|
||||||
createdAt: permission.createdAt.getTime(),
|
|
||||||
updatedAt: permission.updatedAt.getTime(),
|
|
||||||
})),
|
|
||||||
role: {
|
role: {
|
||||||
createdAt: role.createdAt.getTime(),
|
createdAt: role.createdAt.getTime(),
|
||||||
description: null,
|
description: null,
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
The following integrations are currently supported by Automatisch.
|
The following integrations are currently supported by Automatisch.
|
||||||
|
|
||||||
- [Carbone](/apps/carbone/actions)
|
- [Carbone](/apps/carbone/actions)
|
||||||
- [Datastore](/apps/datastore/actions)
|
|
||||||
- [DeepL](/apps/deepl/actions)
|
- [DeepL](/apps/deepl/actions)
|
||||||
- [Delay](/apps/delay/actions)
|
- [Delay](/apps/delay/actions)
|
||||||
- [Discord](/apps/discord/actions)
|
- [Discord](/apps/discord/actions)
|
||||||
|
@@ -31,7 +31,7 @@ export class AdminRolesPage extends AuthenticatedPage {
|
|||||||
await this.roleDrawerLink.click();
|
await this.roleDrawerLink.click();
|
||||||
await this.isMounted();
|
await this.isMounted();
|
||||||
await this.rolesLoader.waitFor({
|
await this.rolesLoader.waitFor({
|
||||||
state: 'detached',
|
state: 'detached'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,9 @@ export class AdminRolesPage extends AuthenticatedPage {
|
|||||||
state: 'detached',
|
state: 'detached',
|
||||||
});
|
});
|
||||||
return this.roleRow.filter({
|
return this.roleRow.filter({
|
||||||
has: this.page.getByTestId('role-name').getByText(name, { exact: true }),
|
has: this.page.getByTestId('role-name').filter({
|
||||||
|
hasText: name,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,6 +28,8 @@
|
|||||||
"@playwright/test": "^1.36.2"
|
"@playwright/test": "^1.36.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||||
|
"@typescript-eslint/parser": "^5.9.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint": "^8.13.0",
|
"eslint": "^8.13.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
@@ -197,7 +197,7 @@ test.describe('Role management page', () => {
|
|||||||
await adminCreateUserPage.passwordInput.fill('sample');
|
await adminCreateUserPage.passwordInput.fill('sample');
|
||||||
await adminCreateUserPage.roleInput.click();
|
await adminCreateUserPage.roleInput.click();
|
||||||
await adminCreateUserPage.page
|
await adminCreateUserPage.page
|
||||||
.getByRole('option', { name: 'Delete Role', exact: true })
|
.getByRole('option', { name: 'Delete Role' })
|
||||||
.click();
|
.click();
|
||||||
await adminCreateUserPage.createButton.click();
|
await adminCreateUserPage.createButton.click();
|
||||||
await adminUsersPage.snackbar.waitFor({
|
await adminUsersPage.snackbar.waitFor({
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
PORT=3001
|
PORT=3001
|
||||||
REACT_APP_BACKEND_URL=http://localhost:3000
|
REACT_APP_GRAPHQL_URL=http://localhost:3000/graphql
|
||||||
# HTTPS=true
|
# HTTPS=true
|
||||||
REACT_APP_BASE_URL=http://localhost:3001
|
REACT_APP_BASE_URL=http://localhost:3001
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
build
|
|
||||||
source
|
|
||||||
.eslintrc.js
|
|
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: [
|
|
||||||
'react-app',
|
|
||||||
'plugin:@tanstack/eslint-plugin-query/recommended',
|
|
||||||
'prettier',
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
'react/prop-types': 'warn',
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "src"
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
@@ -13,10 +13,17 @@
|
|||||||
"@mui/icons-material": "^5.11.9",
|
"@mui/icons-material": "^5.11.9",
|
||||||
"@mui/lab": "^5.0.0-alpha.120",
|
"@mui/lab": "^5.0.0-alpha.120",
|
||||||
"@mui/material": "^5.11.10",
|
"@mui/material": "^5.11.10",
|
||||||
"@tanstack/react-query": "^5.24.1",
|
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
"@types/jest": "^26.0.15",
|
||||||
|
"@types/lodash": "^4.14.182",
|
||||||
|
"@types/luxon": "^2.0.8",
|
||||||
|
"@types/node": "^12.0.0",
|
||||||
|
"@types/react": "^17.0.0",
|
||||||
|
"@types/react-dom": "^17.0.0",
|
||||||
|
"@types/react-window": "^1.8.5",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
"clipboard-copy": "^4.0.1",
|
"clipboard-copy": "^4.0.1",
|
||||||
"compare-versions": "^4.1.3",
|
"compare-versions": "^4.1.3",
|
||||||
"graphql": "^15.6.0",
|
"graphql": "^15.6.0",
|
||||||
@@ -24,8 +31,8 @@
|
|||||||
"luxon": "^2.3.1",
|
"luxon": "^2.3.1",
|
||||||
"mui-color-input": "^2.0.0",
|
"mui-color-input": "^2.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-hook-form": "^7.45.2",
|
"react-hook-form": "^7.45.2",
|
||||||
"react-intl": "^5.20.12",
|
"react-intl": "^5.20.12",
|
||||||
"react-json-tree": "^0.16.2",
|
"react-json-tree": "^0.16.2",
|
||||||
@@ -35,6 +42,7 @@
|
|||||||
"slate": "^0.94.1",
|
"slate": "^0.94.1",
|
||||||
"slate-history": "^0.93.0",
|
"slate-history": "^0.93.0",
|
||||||
"slate-react": "^0.94.2",
|
"slate-react": "^0.94.2",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"web-vitals": "^1.0.1",
|
"web-vitals": "^1.0.1",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
@@ -46,8 +54,8 @@
|
|||||||
"build:watch": "yarn nodemon --exec react-scripts build --watch 'src/**/*.ts' --watch 'public/**/*' --ext ts,html",
|
"build:watch": "yarn nodemon --exec react-scripts build --watch 'src/**/*.ts' --watch 'public/**/*' --ext ts,html",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"lint": "eslint src --ext .js,.jsx",
|
"lint": "eslint . --ignore-path ../../.eslintignore",
|
||||||
"prepack": "yarn build"
|
"prepack": "REACT_APP_GRAPHQL_URL=/graphql yarn build"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"/build"
|
"/build"
|
||||||
@@ -79,17 +87,5 @@
|
|||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tanstack/eslint-plugin-query": "^5.20.1",
|
|
||||||
"@tanstack/react-query-devtools": "^5.24.1",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-config-react-app": "^7.0.1",
|
|
||||||
"prettier": "^3.2.5"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"./.eslintrc.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Route, Navigate } from 'react-router-dom';
|
import { Route, Navigate } from 'react-router-dom';
|
||||||
|
import AdminSettingsLayout from 'components/AdminSettingsLayout';
|
||||||
import Users from 'pages/Users';
|
import Users from 'pages/Users';
|
||||||
import EditUser from 'pages/EditUser';
|
import EditUser from 'pages/EditUser';
|
||||||
import CreateUser from 'pages/CreateUser';
|
import CreateUser from 'pages/CreateUser';
|
||||||
@@ -8,10 +8,12 @@ import CreateRole from 'pages/CreateRole/index.ee';
|
|||||||
import EditRole from 'pages/EditRole/index.ee';
|
import EditRole from 'pages/EditRole/index.ee';
|
||||||
import Authentication from 'pages/Authentication';
|
import Authentication from 'pages/Authentication';
|
||||||
import UserInterface from 'pages/UserInterface';
|
import UserInterface from 'pages/UserInterface';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import AdminApplications from 'pages/AdminApplications';
|
import AdminApplications from 'pages/AdminApplications';
|
||||||
import AdminApplication from 'pages/AdminApplication';
|
import AdminApplication from 'pages/AdminApplication';
|
||||||
|
|
||||||
// TODO: consider introducing redirections to `/` as fallback
|
// TODO: consider introducing redirections to `/` as fallback
|
||||||
export default (
|
export default (
|
||||||
<>
|
<>
|
||||||
@@ -19,7 +21,9 @@ export default (
|
|||||||
path={URLS.USERS}
|
path={URLS.USERS}
|
||||||
element={
|
element={
|
||||||
<Can I="read" a="User">
|
<Can I="read" a="User">
|
||||||
<Users />
|
<AdminSettingsLayout>
|
||||||
|
<Users />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -28,7 +32,9 @@ export default (
|
|||||||
path={URLS.CREATE_USER}
|
path={URLS.CREATE_USER}
|
||||||
element={
|
element={
|
||||||
<Can I="create" a="User">
|
<Can I="create" a="User">
|
||||||
<CreateUser />
|
<AdminSettingsLayout>
|
||||||
|
<CreateUser />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -37,7 +43,9 @@ export default (
|
|||||||
path={URLS.USER_PATTERN}
|
path={URLS.USER_PATTERN}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="User">
|
<Can I="update" a="User">
|
||||||
<EditUser />
|
<AdminSettingsLayout>
|
||||||
|
<EditUser />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -46,7 +54,9 @@ export default (
|
|||||||
path={URLS.ROLES}
|
path={URLS.ROLES}
|
||||||
element={
|
element={
|
||||||
<Can I="read" a="Role">
|
<Can I="read" a="Role">
|
||||||
<Roles />
|
<AdminSettingsLayout>
|
||||||
|
<Roles />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -55,7 +65,9 @@ export default (
|
|||||||
path={URLS.CREATE_ROLE}
|
path={URLS.CREATE_ROLE}
|
||||||
element={
|
element={
|
||||||
<Can I="create" a="Role">
|
<Can I="create" a="Role">
|
||||||
<CreateRole />
|
<AdminSettingsLayout>
|
||||||
|
<CreateRole />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -64,7 +76,9 @@ export default (
|
|||||||
path={URLS.ROLE_PATTERN}
|
path={URLS.ROLE_PATTERN}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="Role">
|
<Can I="update" a="Role">
|
||||||
<EditRole />
|
<AdminSettingsLayout>
|
||||||
|
<EditRole />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -73,7 +87,9 @@ export default (
|
|||||||
path={URLS.USER_INTERFACE}
|
path={URLS.USER_INTERFACE}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="Config">
|
<Can I="update" a="Config">
|
||||||
<UserInterface />
|
<AdminSettingsLayout>
|
||||||
|
<UserInterface />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -84,7 +100,9 @@ export default (
|
|||||||
<Can I="read" a="SamlAuthProvider">
|
<Can I="read" a="SamlAuthProvider">
|
||||||
<Can I="update" a="SamlAuthProvider">
|
<Can I="update" a="SamlAuthProvider">
|
||||||
<Can I="create" a="SamlAuthProvider">
|
<Can I="create" a="SamlAuthProvider">
|
||||||
<Authentication />
|
<AdminSettingsLayout>
|
||||||
|
<Authentication />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
</Can>
|
</Can>
|
||||||
</Can>
|
</Can>
|
||||||
@@ -95,7 +113,9 @@ export default (
|
|||||||
path={URLS.ADMIN_APPS}
|
path={URLS.ADMIN_APPS}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="App">
|
<Can I="update" a="App">
|
||||||
<AdminApplications />
|
<AdminSettingsLayout>
|
||||||
|
<AdminApplications />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -104,7 +124,9 @@ export default (
|
|||||||
path={`${URLS.ADMIN_APP_PATTERN}/*`}
|
path={`${URLS.ADMIN_APP_PATTERN}/*`}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="App">
|
<Can I="update" a="App">
|
||||||
<AdminApplication />
|
<AdminSettingsLayout>
|
||||||
|
<AdminApplication />
|
||||||
|
</AdminSettingsLayout>
|
||||||
</Can>
|
</Can>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
@@ -1,25 +1,40 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Menu from '@mui/material/Menu';
|
import Menu, { MenuProps } from '@mui/material/Menu';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import apolloClient from 'graphql/client';
|
import apolloClient from 'graphql/client';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useAuthentication from 'hooks/useAuthentication';
|
import useAuthentication from 'hooks/useAuthentication';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
function AccountDropdownMenu(props) {
|
|
||||||
|
type AccountDropdownMenuProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
anchorEl: MenuProps['anchorEl'];
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function AccountDropdownMenu(
|
||||||
|
props: AccountDropdownMenuProps
|
||||||
|
): React.ReactElement {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const authentication = useAuthentication();
|
const authentication = useAuthentication();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { open, onClose, anchorEl, id } = props;
|
const { open, onClose, anchorEl, id } = props;
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
authentication.updateToken('');
|
authentication.updateToken('');
|
||||||
await apolloClient.clearStore();
|
await apolloClient.clearStore();
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
navigate(URLS.LOGIN);
|
navigate(URLS.LOGIN);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
@@ -36,7 +51,7 @@ function AccountDropdownMenu(props) {
|
|||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<MenuItem component={Link} to={URLS.SETTINGS_DASHBOARD} onClick={onClose}>
|
<MenuItem component={Link} to={URLS.SETTINGS_DASHBOARD}>
|
||||||
{formatMessage('accountDropdownMenu.settings')}
|
{formatMessage('accountDropdownMenu.settings')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
@@ -44,7 +59,6 @@ function AccountDropdownMenu(props) {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.ADMIN_SETTINGS_DASHBOARD}
|
to={URLS.ADMIN_SETTINGS_DASHBOARD}
|
||||||
onClick={onClose}
|
|
||||||
>
|
>
|
||||||
{formatMessage('accountDropdownMenu.adminSettings')}
|
{formatMessage('accountDropdownMenu.adminSettings')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -57,11 +71,4 @@ function AccountDropdownMenu(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountDropdownMenu.propTypes = {
|
|
||||||
open: PropTypes.bool.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
anchorEl: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AccountDropdownMenu;
|
export default AccountDropdownMenu;
|
@@ -1,4 +1,4 @@
|
|||||||
import PropTypes from 'prop-types';
|
import type { IApp, IField, IJSONObject } from 'types';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
@@ -6,24 +6,32 @@ import DialogContent from '@mui/material/DialogContent';
|
|||||||
import DialogContentText from '@mui/material/DialogContentText';
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { AppPropType } from 'propTypes/propTypes';
|
|
||||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
||||||
import InputCreator from 'components/InputCreator';
|
import InputCreator from 'components/InputCreator';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useAuthenticateApp from 'hooks/useAuthenticateApp.ee';
|
import useAuthenticateApp from 'hooks/useAuthenticateApp.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { generateExternalLink } from 'helpers/translationValues';
|
import { generateExternalLink } from '../../helpers/translationValues';
|
||||||
import { Form } from './style';
|
import { Form } from './style';
|
||||||
|
|
||||||
function AddAppConnection(props) {
|
type AddAppConnectionProps = {
|
||||||
|
onClose: (response: Record<string, unknown>) => void;
|
||||||
|
application: IApp;
|
||||||
|
connectionId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AddAppConnection(
|
||||||
|
props: AddAppConnectionProps
|
||||||
|
): React.ReactElement {
|
||||||
const { application, connectionId, onClose } = props;
|
const { application, connectionId, onClose } = props;
|
||||||
const { name, authDocUrl, key, auth } = application;
|
const { name, authDocUrl, key, auth } = application;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState<IJSONObject | null>(null);
|
||||||
const [inProgress, setInProgress] = React.useState(false);
|
const [inProgress, setInProgress] = React.useState(false);
|
||||||
const hasConnection = Boolean(connectionId);
|
const hasConnection = Boolean(connectionId);
|
||||||
const useShared = searchParams.get('shared') === 'true';
|
const useShared = searchParams.get('shared') === 'true';
|
||||||
@@ -34,6 +42,7 @@ function AddAppConnection(props) {
|
|||||||
appAuthClientId,
|
appAuthClientId,
|
||||||
useShared: !!appAuthClientId,
|
useShared: !!appAuthClientId,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(function relayProviderData() {
|
React.useEffect(function relayProviderData() {
|
||||||
if (window.opener) {
|
if (window.opener) {
|
||||||
window.opener.postMessage({
|
window.opener.postMessage({
|
||||||
@@ -43,41 +52,51 @@ function AddAppConnection(props) {
|
|||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function initiateSharedAuthenticationForGivenAuthClient() {
|
function initiateSharedAuthenticationForGivenAuthClient() {
|
||||||
if (!appAuthClientId) return;
|
if (!appAuthClientId) return;
|
||||||
if (!authenticate) return;
|
if (!authenticate) return;
|
||||||
|
|
||||||
const asyncAuthenticate = async () => {
|
const asyncAuthenticate = async () => {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
|
|
||||||
navigate(URLS.APP_CONNECTIONS(key));
|
navigate(URLS.APP_CONNECTIONS(key));
|
||||||
};
|
};
|
||||||
|
|
||||||
asyncAuthenticate();
|
asyncAuthenticate();
|
||||||
},
|
},
|
||||||
[appAuthClientId, authenticate],
|
[appAuthClientId, authenticate]
|
||||||
);
|
);
|
||||||
const handleClientClick = (appAuthClientId) =>
|
|
||||||
|
const handleClientClick = (appAuthClientId: string) =>
|
||||||
navigate(URLS.APP_ADD_CONNECTION_WITH_AUTH_CLIENT_ID(key, appAuthClientId));
|
navigate(URLS.APP_ADD_CONNECTION_WITH_AUTH_CLIENT_ID(key, appAuthClientId));
|
||||||
|
|
||||||
const handleAuthClientsDialogClose = () =>
|
const handleAuthClientsDialogClose = () =>
|
||||||
navigate(URLS.APP_CONNECTIONS(key));
|
navigate(URLS.APP_CONNECTIONS(key));
|
||||||
const submitHandler = React.useCallback(
|
|
||||||
|
const submitHandler: SubmitHandler<FieldValues> = React.useCallback(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
if (!authenticate) return;
|
if (!authenticate) return;
|
||||||
|
|
||||||
setInProgress(true);
|
setInProgress(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await authenticate({
|
const response = await authenticate({
|
||||||
fields: data,
|
fields: data,
|
||||||
});
|
});
|
||||||
onClose(response);
|
onClose(response as Record<string, unknown>);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err;
|
const error = err as IJSONObject;
|
||||||
console.log(error);
|
console.log(error);
|
||||||
setError(error.graphQLErrors?.[0]);
|
setError((error.graphQLErrors as IJSONObject[])?.[0]);
|
||||||
} finally {
|
} finally {
|
||||||
setInProgress(false);
|
setInProgress(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[authenticate],
|
[authenticate]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (useShared)
|
if (useShared)
|
||||||
return (
|
return (
|
||||||
<AppAuthClientsDialog
|
<AppAuthClientsDialog
|
||||||
@@ -86,7 +105,9 @@ function AddAppConnection(props) {
|
|||||||
onClientClick={handleClientClick}
|
onClientClick={handleClientClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (appAuthClientId) return <React.Fragment />;
|
if (appAuthClientId) return <React.Fragment />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onClose={onClose} data-test="add-app-connection-dialog">
|
<Dialog open={true} onClose={onClose} data-test="add-app-connection-dialog">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@@ -121,7 +142,7 @@ function AddAppConnection(props) {
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText tabIndex={-1} component="div">
|
<DialogContentText tabIndex={-1} component="div">
|
||||||
<Form onSubmit={submitHandler}>
|
<Form onSubmit={submitHandler}>
|
||||||
{auth?.fields?.map((field) => (
|
{auth?.fields?.map((field: IField) => (
|
||||||
<InputCreator key={field.key} schema={field} />
|
<InputCreator key={field.key} schema={field} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -141,11 +162,3 @@ function AddAppConnection(props) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddAppConnection.propTypes = {
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
application: AppPropType.isRequired,
|
|
||||||
connectionId: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddAppConnection;
|
|
@@ -1,5 +1,6 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import BaseForm from 'components/Form';
|
import BaseForm from 'components/Form';
|
||||||
|
|
||||||
export const Form = styled(BaseForm)(({ theme }) => ({
|
export const Form = styled(BaseForm)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
@@ -1,5 +1,4 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useLazyQuery } from '@apollo/client';
|
import { useLazyQuery } from '@apollo/client';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
@@ -20,53 +19,67 @@ import InputLabel from '@mui/material/InputLabel';
|
|||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import type { IApp } from 'types';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import AppIcon from 'components/AppIcon';
|
import AppIcon from 'components/AppIcon';
|
||||||
import { GET_APPS } from 'graphql/queries/get-apps';
|
import { GET_APPS } from 'graphql/queries/get-apps';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
function createConnectionOrFlow(appKey, supportsConnections = false) {
|
function createConnectionOrFlow(appKey: string, supportsConnections = false) {
|
||||||
if (!supportsConnections) {
|
if (!supportsConnections) {
|
||||||
return URLS.CREATE_FLOW_WITH_APP(appKey);
|
return URLS.CREATE_FLOW_WITH_APP(appKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return URLS.APP_ADD_CONNECTION(appKey);
|
return URLS.APP_ADD_CONNECTION(appKey);
|
||||||
}
|
}
|
||||||
function AddNewAppConnection(props) {
|
|
||||||
|
type AddNewAppConnectionProps = {
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AddNewAppConnection(
|
||||||
|
props: AddNewAppConnectionProps
|
||||||
|
): React.ReactElement {
|
||||||
const { onClose } = props;
|
const { onClose } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('sm'));
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [appName, setAppName] = React.useState(null);
|
const [appName, setAppName] = React.useState<string | null>(null);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [getApps, { data }] = useLazyQuery(GET_APPS, {
|
const [getApps, { data }] = useLazyQuery(GET_APPS, {
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchData = React.useMemo(
|
const fetchData = React.useMemo(
|
||||||
() => debounce((name) => getApps({ variables: { name } }), 300),
|
() => debounce((name) => getApps({ variables: { name } }), 300),
|
||||||
[getApps],
|
[getApps]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function fetchAppsOnAppNameChange() {
|
function fetchAppsOnAppNameChange() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
fetchData(appName);
|
fetchData(appName);
|
||||||
},
|
},
|
||||||
[fetchData, appName],
|
[fetchData, appName]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(function cancelDebounceOnUnmount() {
|
React.useEffect(function cancelDebounceOnUnmount() {
|
||||||
return () => {
|
return () => {
|
||||||
fetchData.cancel();
|
fetchData.cancel();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={true}
|
open={true}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
maxWidth="sm"
|
maxWidth="sm"
|
||||||
fullWidth
|
fullWidth
|
||||||
data-test="add-app-connection-dialog"
|
data-test="add-app-connection-dialog">
|
||||||
>
|
|
||||||
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
||||||
|
|
||||||
<Box px={3}>
|
<Box px={3}>
|
||||||
@@ -110,7 +123,7 @@ function AddNewAppConnection(props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
data?.getApps?.map((app) => (
|
data?.getApps?.map((app: IApp) => (
|
||||||
<ListItem disablePadding key={app.name} data-test="app-list-item">
|
<ListItem disablePadding key={app.name} data-test="app-list-item">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
component={Link}
|
component={Link}
|
||||||
@@ -138,9 +151,3 @@ function AddNewAppConnection(props) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddNewAppConnection.propTypes = {
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddNewAppConnection;
|
|
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import type { IField } from 'types';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
@@ -7,16 +7,32 @@ import DialogContent from '@mui/material/DialogContent';
|
|||||||
import DialogContentText from '@mui/material/DialogContentText';
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import { ApolloError } from '@apollo/client';
|
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||||
|
import type { UseFormProps } from 'react-hook-form';
|
||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
|
||||||
import { FieldPropType } from 'propTypes/propTypes';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import InputCreator from 'components/InputCreator';
|
import InputCreator from 'components/InputCreator';
|
||||||
import Switch from 'components/Switch';
|
import Switch from 'components/Switch';
|
||||||
import TextField from 'components/TextField';
|
import TextField from 'components/TextField';
|
||||||
|
|
||||||
import { Form } from './style';
|
import { Form } from './style';
|
||||||
|
|
||||||
function AdminApplicationAuthClientDialog(props) {
|
type AdminApplicationAuthClientDialogProps = {
|
||||||
|
title: string;
|
||||||
|
authFields?: IField[];
|
||||||
|
defaultValues: UseFormProps['defaultValues'];
|
||||||
|
loading: boolean;
|
||||||
|
submitting: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
error?: ApolloError;
|
||||||
|
submitHandler: SubmitHandler<FieldValues>;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AdminApplicationAuthClientDialog(
|
||||||
|
props: AdminApplicationAuthClientDialogProps
|
||||||
|
): React.ReactElement {
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -29,6 +45,7 @@ function AdminApplicationAuthClientDialog(props) {
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
} = props;
|
} = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onClose={onClose}>
|
<Dialog open={true} onClose={onClose}>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
@@ -63,7 +80,7 @@ function AdminApplicationAuthClientDialog(props) {
|
|||||||
label={formatMessage('authClient.inputName')}
|
label={formatMessage('authClient.inputName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
{authFields?.map((field) => (
|
{authFields?.map((field: IField) => (
|
||||||
<InputCreator key={field.key} schema={field} />
|
<InputCreator key={field.key} schema={field} />
|
||||||
))}
|
))}
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@@ -85,17 +102,3 @@ function AdminApplicationAuthClientDialog(props) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdminApplicationAuthClientDialog.propTypes = {
|
|
||||||
error: PropTypes.instanceOf(ApolloError),
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
loading: PropTypes.bool.isRequired,
|
|
||||||
submitHandler: PropTypes.func.isRequired,
|
|
||||||
authFields: PropTypes.arrayOf(FieldPropType),
|
|
||||||
submitting: PropTypes.bool.isRequired,
|
|
||||||
defaultValues: PropTypes.object.isRequired,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminApplicationAuthClientDialog;
|
|
@@ -1,5 +1,6 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import BaseForm from 'components/Form';
|
import BaseForm from 'components/Form';
|
||||||
|
|
||||||
export const Form = styled(BaseForm)(({ theme }) => ({
|
export const Form = styled(BaseForm)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
@@ -1,4 +1,3 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
@@ -8,17 +7,27 @@ import CardContent from '@mui/material/CardContent';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
|
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
|
||||||
|
|
||||||
import NoResultFound from 'components/NoResultFound';
|
import NoResultFound from 'components/NoResultFound';
|
||||||
|
|
||||||
function AdminApplicationAuthClients(props) {
|
type AdminApplicationAuthClientsProps = {
|
||||||
|
appKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function AdminApplicationAuthClients(
|
||||||
|
props: AdminApplicationAuthClientsProps
|
||||||
|
): React.ReactElement {
|
||||||
const { appKey } = props;
|
const { appKey } = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { appAuthClients, loading } = useAppAuthClients({ appKey });
|
const { appAuthClients, loading } = useAppAuthClients({ appKey });
|
||||||
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return <CircularProgress sx={{ display: 'block', margin: '20px auto' }} />;
|
return <CircularProgress sx={{ display: 'block', margin: '20px auto' }} />;
|
||||||
|
|
||||||
if (!appAuthClients?.length) {
|
if (!appAuthClients?.length) {
|
||||||
return (
|
return (
|
||||||
<NoResultFound
|
<NoResultFound
|
||||||
@@ -27,6 +36,7 @@ function AdminApplicationAuthClients(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedAuthClients = appAuthClients.slice().sort((a, b) => {
|
const sortedAuthClients = appAuthClients.slice().sort((a, b) => {
|
||||||
if (a.id < b.id) {
|
if (a.id < b.id) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -36,6 +46,7 @@ function AdminApplicationAuthClients(props) {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{sortedAuthClients.map((client) => (
|
{sortedAuthClients.map((client) => (
|
||||||
@@ -56,7 +67,7 @@ function AdminApplicationAuthClients(props) {
|
|||||||
label={formatMessage(
|
label={formatMessage(
|
||||||
client?.active
|
client?.active
|
||||||
? 'adminAppsAuthClients.statusActive'
|
? 'adminAppsAuthClients.statusActive'
|
||||||
: 'adminAppsAuthClients.statusInactive',
|
: 'adminAppsAuthClients.statusInactive'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -75,8 +86,4 @@ function AdminApplicationAuthClients(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdminApplicationAuthClients.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminApplicationAuthClients;
|
export default AdminApplicationAuthClients;
|
@@ -1,15 +1,24 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import type { IApp } from 'types';
|
||||||
|
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
|
||||||
import { AppPropType } from 'propTypes/propTypes';
|
|
||||||
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
||||||
import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client';
|
import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client';
|
||||||
|
|
||||||
import useAppConfig from 'hooks/useAppConfig.ee';
|
import useAppConfig from 'hooks/useAppConfig.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
||||||
|
|
||||||
function AdminApplicationCreateAuthClient(props) {
|
type AdminApplicationCreateAuthClientProps = {
|
||||||
|
appKey: string;
|
||||||
|
application: IApp;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AdminApplicationCreateAuthClient(
|
||||||
|
props: AdminApplicationCreateAuthClientProps
|
||||||
|
): React.ReactElement {
|
||||||
const { appKey, application, onClose } = props;
|
const { appKey, application, onClose } = props;
|
||||||
const { auth } = application;
|
const { auth } = application;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
@@ -28,8 +37,10 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
refetchQueries: ['GetAppAuthClients'],
|
refetchQueries: ['GetAppAuthClients'],
|
||||||
context: { autoSnackbar: false },
|
context: { autoSnackbar: false },
|
||||||
});
|
});
|
||||||
const submitHandler = async (values) => {
|
|
||||||
|
const submitHandler: SubmitHandler<FieldValues> = async (values) => {
|
||||||
let appConfigId = appConfig?.id;
|
let appConfigId = appConfig?.id;
|
||||||
|
|
||||||
if (!appConfigId) {
|
if (!appConfigId) {
|
||||||
const { data: appConfigData } = await createAppConfig({
|
const { data: appConfigData } = await createAppConfig({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -43,7 +54,9 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
});
|
});
|
||||||
appConfigId = appConfigData.createAppConfig.id;
|
appConfigId = appConfigData.createAppConfig.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, active, ...formattedAuthDefaults } = values;
|
const { name, active, ...formattedAuthDefaults } = values;
|
||||||
|
|
||||||
await createAppAuthClient({
|
await createAppAuthClient({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
@@ -54,13 +67,17 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAuthFieldsDefaultValues = useCallback(() => {
|
const getAuthFieldsDefaultValues = useCallback(() => {
|
||||||
if (!auth?.fields) {
|
if (!auth?.fields) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const defaultValues = {};
|
const defaultValues: {
|
||||||
|
[key: string]: any;
|
||||||
|
} = {};
|
||||||
auth.fields.forEach((field) => {
|
auth.fields.forEach((field) => {
|
||||||
if (field.value || field.type !== 'string') {
|
if (field.value || field.type !== 'string') {
|
||||||
defaultValues[field.key] = field.value;
|
defaultValues[field.key] = field.value;
|
||||||
@@ -70,14 +87,16 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
});
|
});
|
||||||
return defaultValues;
|
return defaultValues;
|
||||||
}, [auth?.fields]);
|
}, [auth?.fields]);
|
||||||
|
|
||||||
const defaultValues = useMemo(
|
const defaultValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
name: '',
|
name: '',
|
||||||
active: false,
|
active: false,
|
||||||
...getAuthFieldsDefaultValues(),
|
...getAuthFieldsDefaultValues(),
|
||||||
}),
|
}),
|
||||||
[getAuthFieldsDefaultValues],
|
[getAuthFieldsDefaultValues]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminApplicationAuthClientDialog
|
<AdminApplicationAuthClientDialog
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
@@ -91,11 +110,3 @@ function AdminApplicationCreateAuthClient(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdminApplicationCreateAuthClient.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
application: AppPropType.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminApplicationCreateAuthClient;
|
|
@@ -1,4 +1,3 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import useAppConfig from 'hooks/useAppConfig.ee';
|
import useAppConfig from 'hooks/useAppConfig.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
@@ -7,29 +6,39 @@ import Paper from '@mui/material/Paper';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
|
||||||
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
|
||||||
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
|
||||||
|
|
||||||
import Form from 'components/Form';
|
import Form from 'components/Form';
|
||||||
import { Switch } from './style';
|
import { Switch } from './style';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
|
|
||||||
function AdminApplicationSettings(props) {
|
type AdminApplicationSettingsProps = {
|
||||||
|
appKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function AdminApplicationSettings(
|
||||||
|
props: AdminApplicationSettingsProps
|
||||||
|
): React.ReactElement {
|
||||||
const { appConfig, loading } = useAppConfig(props.appKey);
|
const { appConfig, loading } = useAppConfig(props.appKey);
|
||||||
const [createAppConfig, { loading: loadingCreateAppConfig }] = useMutation(
|
const [createAppConfig, { loading: loadingCreateAppConfig }] = useMutation(
|
||||||
CREATE_APP_CONFIG,
|
CREATE_APP_CONFIG,
|
||||||
{
|
{
|
||||||
refetchQueries: ['GetAppConfig'],
|
refetchQueries: ['GetAppConfig'],
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
const [updateAppConfig, { loading: loadingUpdateAppConfig }] = useMutation(
|
const [updateAppConfig, { loading: loadingUpdateAppConfig }] = useMutation(
|
||||||
UPDATE_APP_CONFIG,
|
UPDATE_APP_CONFIG,
|
||||||
{
|
{
|
||||||
refetchQueries: ['GetAppConfig'],
|
refetchQueries: ['GetAppConfig'],
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const handleSubmit = async (values) => {
|
|
||||||
|
const handleSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
if (!appConfig) {
|
if (!appConfig) {
|
||||||
await createAppConfig({
|
await createAppConfig({
|
||||||
@@ -47,21 +56,23 @@ function AdminApplicationSettings(props) {
|
|||||||
enqueueSnackbar(formatMessage('adminAppsSettings.successfullySaved'), {
|
enqueueSnackbar(formatMessage('adminAppsSettings.successfullySaved'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
'data-test': 'snackbar-save-admin-apps-settings-success',
|
'data-test': 'snackbar-save-admin-apps-settings-success'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Failed while saving!');
|
throw new Error('Failed while saving!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues = useMemo(
|
const defaultValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
allowCustomConnection: appConfig?.allowCustomConnection || false,
|
allowCustomConnection: appConfig?.allowCustomConnection || false,
|
||||||
shared: appConfig?.shared || false,
|
shared: appConfig?.shared || false,
|
||||||
disabled: appConfig?.disabled || false,
|
disabled: appConfig?.disabled || false,
|
||||||
}),
|
}),
|
||||||
[appConfig],
|
[appConfig]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
defaultValues={defaultValues}
|
defaultValues={defaultValues}
|
||||||
@@ -112,8 +123,4 @@ function AdminApplicationSettings(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdminApplicationSettings.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminApplicationSettings;
|
export default AdminApplicationSettings;
|
@@ -1,5 +1,6 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import SwitchBase from 'components/Switch';
|
import SwitchBase from 'components/Switch';
|
||||||
|
|
||||||
export const Switch = styled(SwitchBase)`
|
export const Switch = styled(SwitchBase)`
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 0;
|
margin: 0;
|
@@ -1,52 +1,55 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import type { IApp } from 'types';
|
||||||
|
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
|
||||||
import { AppPropType } from 'propTypes/propTypes';
|
|
||||||
import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client';
|
import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client';
|
||||||
|
|
||||||
|
import useAppAuthClient from 'hooks/useAppAuthClient.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
|
||||||
import useAdminAppAuthClient from 'hooks/useAdminAppAuthClient.ee';
|
|
||||||
|
|
||||||
function AdminApplicationUpdateAuthClient(props) {
|
type AdminApplicationUpdateAuthClientProps = {
|
||||||
|
application: IApp;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AdminApplicationUpdateAuthClient(
|
||||||
|
props: AdminApplicationUpdateAuthClientProps
|
||||||
|
): React.ReactElement {
|
||||||
const { application, onClose } = props;
|
const { application, onClose } = props;
|
||||||
const { auth } = application;
|
const { auth } = application;
|
||||||
|
const authFields = auth?.fields?.map((field) => ({
|
||||||
|
...field,
|
||||||
|
required: false,
|
||||||
|
}));
|
||||||
|
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
const { clientId } = useParams();
|
const { clientId } = useParams();
|
||||||
|
const { appAuthClient, loading: loadingAuthClient } =
|
||||||
const { data: adminAppAuthClient, isLoading: isAdminAuthClientLoading } =
|
useAppAuthClient(clientId);
|
||||||
useAdminAppAuthClient(clientId);
|
|
||||||
|
|
||||||
const [updateAppAuthClient, { loading: loadingUpdateAppAuthClient, error }] =
|
const [updateAppAuthClient, { loading: loadingUpdateAppAuthClient, error }] =
|
||||||
useMutation(UPDATE_APP_AUTH_CLIENT, {
|
useMutation(UPDATE_APP_AUTH_CLIENT, {
|
||||||
refetchQueries: ['GetAppAuthClients'],
|
refetchQueries: ['GetAppAuthClients'],
|
||||||
context: { autoSnackbar: false },
|
context: { autoSnackbar: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const authFields = auth?.fields?.map((field) => ({
|
const submitHandler: SubmitHandler<FieldValues> = async (values) => {
|
||||||
...field,
|
if (!appAuthClient) {
|
||||||
required: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const submitHandler = async (values) => {
|
|
||||||
if (!adminAppAuthClient) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, active, ...formattedAuthDefaults } = values;
|
const { name, active, ...formattedAuthDefaults } = values;
|
||||||
|
|
||||||
await updateAppAuthClient({
|
await updateAppAuthClient({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
id: adminAppAuthClient.data.id,
|
id: appAuthClient.id,
|
||||||
name,
|
name,
|
||||||
active,
|
active,
|
||||||
formattedAuthDefaults,
|
formattedAuthDefaults,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,8 +57,9 @@ function AdminApplicationUpdateAuthClient(props) {
|
|||||||
if (!authFields) {
|
if (!authFields) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
const defaultValues: {
|
||||||
const defaultValues = {};
|
[key: string]: any;
|
||||||
|
} = {};
|
||||||
authFields.forEach((field) => {
|
authFields.forEach((field) => {
|
||||||
if (field.value || field.type !== 'string') {
|
if (field.value || field.type !== 'string') {
|
||||||
defaultValues[field.key] = field.value;
|
defaultValues[field.key] = field.value;
|
||||||
@@ -68,11 +72,11 @@ function AdminApplicationUpdateAuthClient(props) {
|
|||||||
|
|
||||||
const defaultValues = useMemo(
|
const defaultValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
name: adminAppAuthClient?.data?.name || '',
|
name: appAuthClient?.name || '',
|
||||||
active: adminAppAuthClient?.data?.active || false,
|
active: appAuthClient?.active || false,
|
||||||
...getAuthFieldsDefaultValues(),
|
...getAuthFieldsDefaultValues(),
|
||||||
}),
|
}),
|
||||||
[adminAppAuthClient, getAuthFieldsDefaultValues],
|
[appAuthClient, getAuthFieldsDefaultValues]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -80,19 +84,12 @@ function AdminApplicationUpdateAuthClient(props) {
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
error={error}
|
error={error}
|
||||||
title={formatMessage('updateAuthClient.title')}
|
title={formatMessage('updateAuthClient.title')}
|
||||||
loading={isAdminAuthClientLoading}
|
loading={loadingAuthClient}
|
||||||
submitHandler={submitHandler}
|
submitHandler={submitHandler}
|
||||||
authFields={authFields}
|
authFields={authFields}
|
||||||
submitting={loadingUpdateAppAuthClient}
|
submitting={loadingUpdateAppAuthClient}
|
||||||
defaultValues={defaultValues}
|
defaultValues={defaultValues}
|
||||||
disabled={!adminAppAuthClient}
|
disabled={!appAuthClient}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdminApplicationUpdateAuthClient.propTypes = {
|
|
||||||
application: AppPropType.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminApplicationUpdateAuthClient;
|
|
@@ -4,26 +4,42 @@ import GroupsIcon from '@mui/icons-material/Groups';
|
|||||||
import LockIcon from '@mui/icons-material/LockPerson';
|
import LockIcon from '@mui/icons-material/LockPerson';
|
||||||
import BrushIcon from '@mui/icons-material/Brush';
|
import BrushIcon from '@mui/icons-material/Brush';
|
||||||
import AppsIcon from '@mui/icons-material/Apps';
|
import AppsIcon from '@mui/icons-material/Apps';
|
||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { SvgIconComponent } from '@mui/icons-material';
|
||||||
import AppBar from 'components/AppBar';
|
import AppBar from 'components/AppBar';
|
||||||
import Drawer from 'components/Drawer';
|
import Drawer from 'components/Drawer';
|
||||||
import Can from 'components/Can';
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
|
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
|
||||||
|
|
||||||
|
type SettingsLayoutProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DrawerLink = {
|
||||||
|
Icon: SvgIconComponent;
|
||||||
|
primary: string;
|
||||||
|
to: string;
|
||||||
|
};
|
||||||
|
|
||||||
function createDrawerLinks({
|
function createDrawerLinks({
|
||||||
canReadRole,
|
canReadRole,
|
||||||
canReadUser,
|
canReadUser,
|
||||||
canUpdateConfig,
|
canUpdateConfig,
|
||||||
canManageSamlAuthProvider,
|
canManageSamlAuthProvider,
|
||||||
canUpdateApp,
|
canUpdateApp,
|
||||||
|
}: {
|
||||||
|
canReadRole: boolean;
|
||||||
|
canReadUser: boolean;
|
||||||
|
canUpdateConfig: boolean;
|
||||||
|
canManageSamlAuthProvider: boolean;
|
||||||
|
canUpdateApp: boolean;
|
||||||
}) {
|
}) {
|
||||||
const items = [
|
const items = [
|
||||||
canReadUser
|
canReadUser
|
||||||
@@ -66,16 +82,20 @@ function createDrawerLinks({
|
|||||||
dataTest: 'apps-drawer-link',
|
dataTest: 'apps-drawer-link',
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
].filter(Boolean);
|
].filter(Boolean) as DrawerLink[];
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingsLayout() {
|
export default function SettingsLayout({
|
||||||
|
children,
|
||||||
|
}: SettingsLayoutProps): React.ReactElement {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const currentUserAbility = useCurrentUserAbility();
|
const currentUserAbility = useCurrentUserAbility();
|
||||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
||||||
|
|
||||||
const openDrawer = () => setDrawerOpen(true);
|
const openDrawer = () => setDrawerOpen(true);
|
||||||
const closeDrawer = () => setDrawerOpen(false);
|
const closeDrawer = () => setDrawerOpen(false);
|
||||||
const drawerLinks = createDrawerLinks({
|
const drawerLinks = createDrawerLinks({
|
||||||
@@ -88,6 +108,7 @@ function SettingsLayout() {
|
|||||||
currentUserAbility.can('create', 'SamlAuthProvider'),
|
currentUserAbility.can('create', 'SamlAuthProvider'),
|
||||||
canUpdateApp: currentUserAbility.can('update', 'App'),
|
canUpdateApp: currentUserAbility.can('update', 'App'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const drawerBottomLinks = [
|
const drawerBottomLinks = [
|
||||||
{
|
{
|
||||||
Icon: ArrowBackIosNewIcon,
|
Icon: ArrowBackIosNewIcon,
|
||||||
@@ -96,13 +117,15 @@ function SettingsLayout() {
|
|||||||
dataTest: 'go-back-drawer-link',
|
dataTest: 'go-back-drawer-link',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Can I="read" a="User">
|
<>
|
||||||
<AppBar
|
<AppBar
|
||||||
drawerOpen={isDrawerOpen}
|
drawerOpen={isDrawerOpen}
|
||||||
onDrawerOpen={openDrawer}
|
onDrawerOpen={openDrawer}
|
||||||
onDrawerClose={closeDrawer}
|
onDrawerClose={closeDrawer}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<Drawer
|
<Drawer
|
||||||
links={drawerLinks}
|
links={drawerLinks}
|
||||||
@@ -111,13 +134,13 @@ function SettingsLayout() {
|
|||||||
onOpen={openDrawer}
|
onOpen={openDrawer}
|
||||||
onClose={closeDrawer}
|
onClose={closeDrawer}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<Outlet />
|
|
||||||
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Can>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsLayout;
|
|
@@ -5,7 +5,11 @@ import { mutateAndGetClient } from 'graphql/client';
|
|||||||
import useAuthentication from 'hooks/useAuthentication';
|
import useAuthentication from 'hooks/useAuthentication';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
|
|
||||||
const ApolloProvider = (props) => {
|
type ApolloProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ApolloProvider = (props: ApolloProviderProps): React.ReactElement => {
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const authentication = useAuthentication();
|
const authentication = useAuthentication();
|
||||||
|
|
||||||
@@ -14,11 +18,11 @@ const ApolloProvider = (props) => {
|
|||||||
enqueueSnackbar(message, {
|
enqueueSnackbar(message, {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
'data-test': 'snackbar-error',
|
'data-test': 'snackbar-error'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[enqueueSnackbar],
|
[enqueueSnackbar]
|
||||||
);
|
);
|
||||||
|
|
||||||
const client = React.useMemo(() => {
|
const client = React.useMemo(() => {
|
@@ -1,4 +1,3 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
@@ -6,23 +5,33 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
|
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
function AppAuthClientsDialog(props) {
|
type AppAuthClientsDialogProps = {
|
||||||
|
appKey: string;
|
||||||
|
onClientClick: (appAuthClientId: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AppAuthClientsDialog(props: AppAuthClientsDialogProps) {
|
||||||
const { appKey, onClientClick, onClose } = props;
|
const { appKey, onClientClick, onClose } = props;
|
||||||
const { appAuthClients } = useAppAuthClients({ appKey, active: true });
|
const { appAuthClients } = useAppAuthClients({ appKey, active: true });
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function autoAuthenticateSingleClient() {
|
function autoAuthenticateSingleClient() {
|
||||||
if (appAuthClients?.length === 1) {
|
if (appAuthClients?.length === 1) {
|
||||||
onClientClick(appAuthClients[0].id);
|
onClientClick(appAuthClients[0].id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[appAuthClients],
|
[appAuthClients]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!appAuthClients?.length || appAuthClients?.length === 1)
|
if (!appAuthClients?.length || appAuthClients?.length === 1)
|
||||||
return <React.Fragment />;
|
return <React.Fragment />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={true}>
|
<Dialog onClose={onClose} open={true}>
|
||||||
<DialogTitle>{formatMessage('appAuthClientsDialog.title')}</DialogTitle>
|
<DialogTitle>{formatMessage('appAuthClientsDialog.title')}</DialogTitle>
|
||||||
@@ -39,11 +48,3 @@ function AppAuthClientsDialog(props) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppAuthClientsDialog.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
onClientClick: PropTypes.func.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppAuthClientsDialog;
|
|
@@ -1,35 +1,50 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
import MenuOpenIcon from '@mui/icons-material/MenuOpen';
|
import MenuOpenIcon from '@mui/icons-material/MenuOpen';
|
||||||
import MuiAppBar from '@mui/material/AppBar';
|
import MuiAppBar from '@mui/material/AppBar';
|
||||||
|
import type { ContainerProps } from '@mui/material/Container';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import AccountDropdownMenu from 'components/AccountDropdownMenu';
|
import AccountDropdownMenu from 'components/AccountDropdownMenu';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Logo from 'components/Logo/index';
|
import Logo from 'components/Logo/index';
|
||||||
import TrialStatusBadge from 'components/TrialStatusBadge/index.ee';
|
import TrialStatusBadge from 'components/TrialStatusBadge/index.ee';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
import { Link } from './style';
|
import { Link } from './style';
|
||||||
|
|
||||||
|
type AppBarProps = {
|
||||||
|
drawerOpen: boolean;
|
||||||
|
onDrawerOpen: () => void;
|
||||||
|
onDrawerClose: () => void;
|
||||||
|
maxWidth?: ContainerProps['maxWidth'];
|
||||||
|
};
|
||||||
|
|
||||||
const accountMenuId = 'account-menu';
|
const accountMenuId = 'account-menu';
|
||||||
|
|
||||||
function AppBar(props) {
|
export default function AppBar(props: AppBarProps): React.ReactElement {
|
||||||
const { drawerOpen, onDrawerOpen, onDrawerClose, maxWidth = false } = props;
|
const { drawerOpen, onDrawerOpen, onDrawerClose, maxWidth = false } = props;
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
const [accountMenuAnchorElement, setAccountMenuAnchorElement] =
|
const [accountMenuAnchorElement, setAccountMenuAnchorElement] =
|
||||||
React.useState(null);
|
React.useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
const isMenuOpen = Boolean(accountMenuAnchorElement);
|
const isMenuOpen = Boolean(accountMenuAnchorElement);
|
||||||
const handleAccountMenuOpen = (event) => {
|
|
||||||
|
const handleAccountMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
setAccountMenuAnchorElement(event.currentTarget);
|
setAccountMenuAnchorElement(event.currentTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAccountMenuClose = () => {
|
const handleAccountMenuClose = () => {
|
||||||
setAccountMenuAnchorElement(null);
|
setAccountMenuAnchorElement(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiAppBar data-test="app-bar">
|
<MuiAppBar data-test="app-bar">
|
||||||
<Container maxWidth={maxWidth} disableGutters>
|
<Container maxWidth={maxWidth} disableGutters>
|
||||||
@@ -76,15 +91,3 @@ function AppBar(props) {
|
|||||||
</MuiAppBar>
|
</MuiAppBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppBar.propTypes = {
|
|
||||||
drawerOpen: PropTypes.bool.isRequired,
|
|
||||||
onDrawerOpen: PropTypes.func.isRequired,
|
|
||||||
onDrawerClose: PropTypes.func.isRequired,
|
|
||||||
maxWidth: PropTypes.oneOfType([
|
|
||||||
PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl', false]),
|
|
||||||
PropTypes.string,
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppBar;
|
|
@@ -1,5 +1,6 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
export const Link = styled(RouterLink)(() => ({
|
export const Link = styled(RouterLink)(() => ({
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
color: 'inherit',
|
color: 'inherit',
|
@@ -1,14 +1,29 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
|
import type { PopoverProps } from '@mui/material/Popover';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import type { IConnection } from 'types';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { ConnectionPropType } from 'propTypes/propTypes';
|
|
||||||
|
|
||||||
function ContextMenu(props) {
|
type Action = {
|
||||||
|
type: 'test' | 'reconnect' | 'delete' | 'viewFlows';
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContextMenuProps = {
|
||||||
|
appKey: string;
|
||||||
|
connection: IConnection;
|
||||||
|
onClose: () => void;
|
||||||
|
onMenuItemClick: (event: React.MouseEvent, action: Action) => void;
|
||||||
|
anchorEl: PopoverProps['anchorEl'];
|
||||||
|
disableReconnection: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ContextMenu(
|
||||||
|
props: ContextMenuProps
|
||||||
|
): React.ReactElement {
|
||||||
const {
|
const {
|
||||||
appKey,
|
appKey,
|
||||||
connection,
|
connection,
|
||||||
@@ -18,15 +33,18 @@ function ContextMenu(props) {
|
|||||||
disableReconnection,
|
disableReconnection,
|
||||||
} = props;
|
} = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
const createActionHandler = React.useCallback(
|
const createActionHandler = React.useCallback(
|
||||||
(action) => {
|
(action: Action) => {
|
||||||
return function clickHandler(event) {
|
return function clickHandler(event: React.MouseEvent) {
|
||||||
onMenuItemClick(event, action);
|
onMenuItemClick(event, action);
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[onMenuItemClick, onClose],
|
[onMenuItemClick, onClose]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
open={true}
|
open={true}
|
||||||
@@ -52,7 +70,7 @@ function ContextMenu(props) {
|
|||||||
to={URLS.APP_RECONNECT_CONNECTION(
|
to={URLS.APP_RECONNECT_CONNECTION(
|
||||||
appKey,
|
appKey,
|
||||||
connection.id,
|
connection.id,
|
||||||
connection.appAuthClientId,
|
connection.appAuthClientId
|
||||||
)}
|
)}
|
||||||
onClick={createActionHandler({ type: 'reconnect' })}
|
onClick={createActionHandler({ type: 'reconnect' })}
|
||||||
>
|
>
|
||||||
@@ -65,17 +83,3 @@ function ContextMenu(props) {
|
|||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
connection: ConnectionPropType.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
onMenuItemClick: PropTypes.func.isRequired,
|
|
||||||
anchorEl: PropTypes.oneOfType([
|
|
||||||
PropTypes.func,
|
|
||||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
|
||||||
]),
|
|
||||||
disableReconnection: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ContextMenu;
|
|
@@ -10,20 +10,26 @@ import Stack from '@mui/material/Stack';
|
|||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import type { IConnection } from 'types';
|
||||||
import ConnectionContextMenu from 'components/AppConnectionContextMenu';
|
import ConnectionContextMenu from 'components/AppConnectionContextMenu';
|
||||||
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
|
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
|
||||||
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
|
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { ConnectionPropType } from 'propTypes/propTypes';
|
|
||||||
import { CardContent, Typography } from './style';
|
import { CardContent, Typography } from './style';
|
||||||
|
|
||||||
const countTranslation = (value) => (
|
type AppConnectionRowProps = {
|
||||||
|
connection: IConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
const countTranslation = (value: React.ReactNode) => (
|
||||||
<>
|
<>
|
||||||
<Typography variant="body1">{value}</Typography>
|
<Typography variant="body1">{value}</Typography>
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
function AppConnectionRow(props) {
|
|
||||||
|
function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const [verificationVisible, setVerificationVisible] = React.useState(false);
|
const [verificationVisible, setVerificationVisible] = React.useState(false);
|
||||||
const [testConnection, { called: testCalled, loading: testLoading }] =
|
const [testConnection, { called: testCalled, loading: testLoading }] =
|
||||||
@@ -37,6 +43,7 @@ function AppConnectionRow(props) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [deleteConnection] = useMutation(DELETE_CONNECTION);
|
const [deleteConnection] = useMutation(DELETE_CONNECTION);
|
||||||
|
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -47,14 +54,17 @@ function AppConnectionRow(props) {
|
|||||||
flowCount,
|
flowCount,
|
||||||
reconnectable,
|
reconnectable,
|
||||||
} = props.connection;
|
} = props.connection;
|
||||||
const contextButtonRef = React.useRef(null);
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
const contextButtonRef = React.useRef<SVGSVGElement | null>(null);
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<SVGSVGElement | null>(null);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onContextMenuClick = () => setAnchorEl(contextButtonRef.current);
|
const onContextMenuClick = () => setAnchorEl(contextButtonRef.current);
|
||||||
const onContextMenuAction = React.useCallback(
|
const onContextMenuAction = React.useCallback(
|
||||||
async (event, action) => {
|
async (event, action: { [key: string]: string }) => {
|
||||||
if (action.type === 'delete') {
|
if (action.type === 'delete') {
|
||||||
await deleteConnection({
|
await deleteConnection({
|
||||||
variables: { input: { id } },
|
variables: { input: { id } },
|
||||||
@@ -63,11 +73,13 @@ function AppConnectionRow(props) {
|
|||||||
__typename: 'Connection',
|
__typename: 'Connection',
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
cache.evict({
|
cache.evict({
|
||||||
id: connectionCacheId,
|
id: connectionCacheId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('connection.deletedMessage'), {
|
enqueueSnackbar(formatMessage('connection.deletedMessage'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
@@ -79,11 +91,13 @@ function AppConnectionRow(props) {
|
|||||||
testConnection({ variables: { id } });
|
testConnection({ variables: { id } });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[deleteConnection, id, testConnection, formatMessage, enqueueSnackbar],
|
[deleteConnection, id, testConnection, formatMessage, enqueueSnackbar]
|
||||||
);
|
);
|
||||||
|
|
||||||
const relativeCreatedAt = DateTime.fromMillis(
|
const relativeCreatedAt = DateTime.fromMillis(
|
||||||
parseInt(createdAt, 10),
|
parseInt(createdAt, 10)
|
||||||
).toRelative();
|
).toRelative();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card sx={{ my: 2 }} data-test="app-connection-row">
|
<Card sx={{ my: 2 }} data-test="app-connection-row">
|
||||||
@@ -111,17 +125,14 @@ function AppConnectionRow(props) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{verificationVisible &&
|
{verificationVisible && testCalled && !testLoading && verified && (
|
||||||
testCalled &&
|
<>
|
||||||
!testLoading &&
|
<CheckCircleIcon fontSize="small" color="success" />
|
||||||
verified && (
|
<Typography variant="caption">
|
||||||
<>
|
{formatMessage('connection.testSuccessful')}
|
||||||
<CheckCircleIcon fontSize="small" color="success" />
|
</Typography>
|
||||||
<Typography variant="caption">
|
</>
|
||||||
{formatMessage('connection.testSuccessful')}
|
)}
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{verificationVisible &&
|
{verificationVisible &&
|
||||||
testCalled &&
|
testCalled &&
|
||||||
!testLoading &&
|
!testLoading &&
|
||||||
@@ -169,8 +180,4 @@ function AppConnectionRow(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConnectionRow.propTypes = {
|
|
||||||
connection: ConnectionPropType.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppConnectionRow;
|
export default AppConnectionRow;
|
@@ -1,6 +1,7 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import MuiCardContent from '@mui/material/CardContent';
|
import MuiCardContent from '@mui/material/CardContent';
|
||||||
import MuiTypography from '@mui/material/Typography';
|
import MuiTypography from '@mui/material/Typography';
|
||||||
|
|
||||||
export const CardContent = styled(MuiCardContent)(({ theme }) => ({
|
export const CardContent = styled(MuiCardContent)(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateRows: 'auto',
|
gridTemplateRows: 'auto',
|
||||||
@@ -8,6 +9,7 @@ export const CardContent = styled(MuiCardContent)(({ theme }) => ({
|
|||||||
gridColumnGap: theme.spacing(2),
|
gridColumnGap: theme.spacing(2),
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const Typography = styled(MuiTypography)(() => ({
|
export const Typography = styled(MuiTypography)(() => ({
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
@@ -1,20 +1,29 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
import type { IConnection } from 'types';
|
||||||
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
||||||
import AppConnectionRow from 'components/AppConnectionRow';
|
import AppConnectionRow from 'components/AppConnectionRow';
|
||||||
import NoResultFound from 'components/NoResultFound';
|
import NoResultFound from 'components/NoResultFound';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
function AppConnections(props) {
|
type AppConnectionsProps = {
|
||||||
|
appKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AppConnections(
|
||||||
|
props: AppConnectionsProps
|
||||||
|
): React.ReactElement {
|
||||||
const { appKey } = props;
|
const { appKey } = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { data } = useQuery(GET_APP_CONNECTIONS, {
|
const { data } = useQuery(GET_APP_CONNECTIONS, {
|
||||||
variables: { key: appKey },
|
variables: { key: appKey },
|
||||||
});
|
});
|
||||||
const appConnections = data?.getApp?.connections || [];
|
const appConnections: IConnection[] = data?.getApp?.connections || [];
|
||||||
|
|
||||||
const hasConnections = appConnections?.length;
|
const hasConnections = appConnections?.length;
|
||||||
|
|
||||||
if (!hasConnections) {
|
if (!hasConnections) {
|
||||||
return (
|
return (
|
||||||
<NoResultFound
|
<NoResultFound
|
||||||
@@ -27,15 +36,9 @@ function AppConnections(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{appConnections.map((appConnection) => (
|
{appConnections.map((appConnection: IConnection) => (
|
||||||
<AppConnectionRow key={appConnection.id} connection={appConnection} />
|
<AppConnectionRow key={appConnection.id} connection={appConnection} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConnections.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppConnections;
|
|
@@ -1,19 +1,27 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { GET_FLOWS } from 'graphql/queries/get-flows';
|
import { GET_FLOWS } from 'graphql/queries/get-flows';
|
||||||
import Pagination from '@mui/material/Pagination';
|
import Pagination from '@mui/material/Pagination';
|
||||||
import PaginationItem from '@mui/material/PaginationItem';
|
import PaginationItem from '@mui/material/PaginationItem';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import AppFlowRow from 'components/FlowRow';
|
import AppFlowRow from 'components/FlowRow';
|
||||||
import NoResultFound from 'components/NoResultFound';
|
import NoResultFound from 'components/NoResultFound';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import type { IFlow } from 'types';
|
||||||
|
|
||||||
|
type AppFlowsProps = {
|
||||||
|
appKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
const FLOW_PER_PAGE = 10;
|
const FLOW_PER_PAGE = 10;
|
||||||
const getLimitAndOffset = (page) => ({
|
|
||||||
|
const getLimitAndOffset = (page: number) => ({
|
||||||
limit: FLOW_PER_PAGE,
|
limit: FLOW_PER_PAGE,
|
||||||
offset: (page - 1) * FLOW_PER_PAGE,
|
offset: (page - 1) * FLOW_PER_PAGE,
|
||||||
});
|
});
|
||||||
function AppFlows(props) {
|
|
||||||
|
export default function AppFlows(props: AppFlowsProps): React.ReactElement {
|
||||||
const { appKey } = props;
|
const { appKey } = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -28,8 +36,10 @@ function AppFlows(props) {
|
|||||||
});
|
});
|
||||||
const getFlows = data?.getFlows || {};
|
const getFlows = data?.getFlows || {};
|
||||||
const { pageInfo, edges } = getFlows;
|
const { pageInfo, edges } = getFlows;
|
||||||
const flows = edges?.map(({ node }) => node);
|
|
||||||
|
const flows: IFlow[] = edges?.map(({ node }: { node: IFlow }) => node);
|
||||||
const hasFlows = flows?.length;
|
const hasFlows = flows?.length;
|
||||||
|
|
||||||
if (!hasFlows) {
|
if (!hasFlows) {
|
||||||
return (
|
return (
|
||||||
<NoResultFound
|
<NoResultFound
|
||||||
@@ -39,9 +49,10 @@ function AppFlows(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{flows?.map((appFlow) => (
|
{flows?.map((appFlow: IFlow) => (
|
||||||
<AppFlowRow key={appFlow.id} flow={appFlow} />
|
<AppFlowRow key={appFlow.id} flow={appFlow} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -63,9 +74,3 @@ function AppFlows(props) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppFlows.propTypes = {
|
|
||||||
appKey: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppFlows;
|
|
@@ -1,12 +1,25 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
const inlineImgStyle = {
|
import type { AvatarProps } from '@mui/material/Avatar';
|
||||||
|
|
||||||
|
type AppIconProps = {
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
color?: string;
|
||||||
|
variant?: AvatarProps['variant'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const inlineImgStyle: React.CSSProperties = {
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
};
|
};
|
||||||
function AppIcon(props) {
|
|
||||||
|
export default function AppIcon(
|
||||||
|
props: AppIconProps & AvatarProps
|
||||||
|
): React.ReactElement {
|
||||||
const { name, url, color, sx = {}, variant = 'square', ...restProps } = props;
|
const { name, url, color, sx = {}, variant = 'square', ...restProps } = props;
|
||||||
|
|
||||||
const initialLetter = name?.[0];
|
const initialLetter = name?.[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
component="span"
|
component="span"
|
||||||
@@ -15,22 +28,8 @@ function AppIcon(props) {
|
|||||||
imgProps={{ style: inlineImgStyle }}
|
imgProps={{ style: inlineImgStyle }}
|
||||||
src={url}
|
src={url}
|
||||||
alt={name}
|
alt={name}
|
||||||
|
children={initialLetter}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
/>
|
||||||
{initialLetter}
|
|
||||||
</Avatar>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppIcon.propTypes = {
|
|
||||||
name: PropTypes.string,
|
|
||||||
url: PropTypes.string,
|
|
||||||
color: PropTypes.string,
|
|
||||||
variant: PropTypes.oneOfType([
|
|
||||||
PropTypes.oneOf(['circular', 'rounded', 'square']),
|
|
||||||
PropTypes.string,
|
|
||||||
]),
|
|
||||||
sx: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppIcon;
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user