From dd5f05334b9e284c81baf467b04f4d7890e019ae Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 26 Feb 2024 22:17:21 +0100 Subject: [PATCH 1/2] feat: Allow renderer to use explicitly defined serializers --- packages/backend/src/helpers/renderer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/helpers/renderer.js b/packages/backend/src/helpers/renderer.js index e07c0bf0..94e1682d 100644 --- a/packages/backend/src/helpers/renderer.js +++ b/packages/backend/src/helpers/renderer.js @@ -11,7 +11,7 @@ const isArray = (object) => const totalCount = (object) => isPaginated(object) ? object.totalCount : isArray(object) ? object.length : 1; -const renderObject = (response, object) => { +const renderObject = (response, object, options) => { let data = isPaginated(object) ? object.records : object; const type = isPaginated(object) @@ -20,7 +20,9 @@ const renderObject = (response, object) => { ? object?.[0]?.constructor?.name || 'Object' : object.constructor.name; - const serializer = serializers[type]; + const serializer = options?.serializer + ? serializers[options.serializer] + : serializers[type]; if (serializer) { data = Array.isArray(data) From 89811743027424bc42aa83ff9561d19e688e038e Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 26 Feb 2024 22:25:03 +0100 Subject: [PATCH 2/2] feat: Introduce app serializer --- .../src/controllers/api/v1/apps/get-app.js | 2 +- .../controllers/api/v1/apps/get-app.test.js | 7 +- packages/backend/src/serializers/app.js | 12 + packages/backend/src/serializers/app.test.js | 20 + packages/backend/src/serializers/index.js | 2 + .../test/mocks/rest/api/v1/apps/get-app.js | 597 +----------------- 6 files changed, 47 insertions(+), 593 deletions(-) create mode 100644 packages/backend/src/serializers/app.js create mode 100644 packages/backend/src/serializers/app.test.js diff --git a/packages/backend/src/controllers/api/v1/apps/get-app.js b/packages/backend/src/controllers/api/v1/apps/get-app.js index ccac3bd3..2f27d3cd 100644 --- a/packages/backend/src/controllers/api/v1/apps/get-app.js +++ b/packages/backend/src/controllers/api/v1/apps/get-app.js @@ -4,5 +4,5 @@ import { renderObject } from '../../../../helpers/renderer.js'; export default async (request, response) => { const app = await App.findOneByKey(request.params.appKey); - renderObject(response, app); + renderObject(response, app, { serializer: 'App' }); }; diff --git a/packages/backend/src/controllers/api/v1/apps/get-app.test.js b/packages/backend/src/controllers/api/v1/apps/get-app.test.js index d6c2cc2d..22013c45 100644 --- a/packages/backend/src/controllers/api/v1/apps/get-app.test.js +++ b/packages/backend/src/controllers/api/v1/apps/get-app.test.js @@ -1,5 +1,6 @@ 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'; @@ -14,12 +15,14 @@ describe('GET /api/v1/apps/:appKey', () => { }); it('should return the app info', async () => { + const exampleApp = await App.findOneByKey('github'); + const response = await request(app) - .get('/api/v1/apps/github') + .get(`/api/v1/apps/${exampleApp.key}`) .set('Authorization', token) .expect(200); - const expectedPayload = getAppMock('github'); + const expectedPayload = getAppMock(exampleApp); expect(response.body).toEqual(expectedPayload); }); diff --git a/packages/backend/src/serializers/app.js b/packages/backend/src/serializers/app.js new file mode 100644 index 00000000..4b3a6462 --- /dev/null +++ b/packages/backend/src/serializers/app.js @@ -0,0 +1,12 @@ +const appSerializer = (app) => { + return { + name: app.name, + key: app.key, + iconUrl: app.iconUrl, + authDocUrl: app.authDocUrl, + supportsConnections: app.supportsConnections, + primaryColor: app.primaryColor, + }; +}; + +export default appSerializer; diff --git a/packages/backend/src/serializers/app.test.js b/packages/backend/src/serializers/app.test.js new file mode 100644 index 00000000..8527f103 --- /dev/null +++ b/packages/backend/src/serializers/app.test.js @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import App from '../models/app'; +import appSerializer from './app'; + +describe('appSerializer', () => { + it('should return permission data', async () => { + const app = await App.findOneByKey('deepl'); + + const expectedPayload = { + name: app.name, + key: app.key, + iconUrl: app.iconUrl, + authDocUrl: app.authDocUrl, + supportsConnections: app.supportsConnections, + primaryColor: app.primaryColor, + }; + + expect(appSerializer(app)).toEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/serializers/index.js b/packages/backend/src/serializers/index.js index 2f766949..9a63ca72 100644 --- a/packages/backend/src/serializers/index.js +++ b/packages/backend/src/serializers/index.js @@ -5,6 +5,7 @@ import samlAuthProviderSerializer from './saml-auth-provider.ee.js'; import appAuthClientSerializer from './app-auth-client.js'; import flowSerializer from './flow.js'; import stepSerializer from './step.js'; +import appSerializer from './app.js'; const serializers = { User: userSerializer, @@ -14,6 +15,7 @@ const serializers = { AppAuthClient: appAuthClientSerializer, Flow: flowSerializer, Step: stepSerializer, + App: appSerializer, }; export default serializers; diff --git a/packages/backend/test/mocks/rest/api/v1/apps/get-app.js b/packages/backend/test/mocks/rest/api/v1/apps/get-app.js index 952fa7e1..e5b96c38 100644 --- a/packages/backend/test/mocks/rest/api/v1/apps/get-app.js +++ b/packages/backend/test/mocks/rest/api/v1/apps/get-app.js @@ -1,595 +1,12 @@ -const getAppMock = (appKey) => { - if (!appKey === 'github') return; - +const getAppMock = (app) => { return { data: { - actions: [ - { - description: 'Creates a new issue.', - key: 'createIssue', - name: 'Create issue', - substeps: [ - { - key: 'chooseConnection', - name: 'Choose connection', - }, - { - arguments: [ - { - key: 'repo', - label: 'Repo', - required: false, - source: { - arguments: [ - { - name: 'key', - value: 'listRepos', - }, - ], - name: 'getDynamicData', - type: 'query', - }, - type: 'dropdown', - variables: true, - }, - { - key: 'title', - label: 'Title', - required: true, - type: 'string', - variables: true, - }, - { - key: 'body', - label: 'Body', - required: true, - type: 'string', - variables: true, - }, - ], - key: 'chooseTrigger', - name: 'Set up action', - }, - { - key: 'testStep', - name: 'Test action', - }, - ], - }, - ], - apiBaseUrl: 'https://api.github.com', - auth: { - authenticationSteps: [ - { - arguments: [ - { - name: 'key', - value: '{key}', - }, - { - name: 'formattedData', - value: '{fields.all}', - }, - ], - name: 'createConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{createConnection.id}', - }, - ], - name: 'generateAuthUrl', - type: 'mutation', - }, - { - arguments: [ - { - name: 'url', - value: '{generateAuthUrl.url}', - }, - ], - name: 'openAuthPopup', - type: 'openWithPopup', - }, - { - arguments: [ - { - name: 'id', - value: '{createConnection.id}', - }, - { - name: 'formattedData', - value: '{openAuthPopup.all}', - }, - ], - name: 'updateConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{createConnection.id}', - }, - ], - name: 'verifyConnection', - type: 'mutation', - }, - ], - fields: [ - { - clickToCopy: true, - description: - 'When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.', - docUrl: 'https://automatisch.io/docs/github#oauth-redirect-url', - key: 'oAuthRedirectUrl', - label: 'OAuth Redirect URL', - placeholder: null, - readOnly: true, - required: true, - type: 'string', - value: 'http://localhost:3000/app/github/connections/add', - }, - { - clickToCopy: false, - description: null, - docUrl: 'https://automatisch.io/docs/github#client-id', - key: 'consumerKey', - label: 'Client ID', - placeholder: null, - readOnly: false, - required: true, - type: 'string', - value: null, - }, - { - clickToCopy: false, - description: null, - docUrl: 'https://automatisch.io/docs/github#client-secret', - key: 'consumerSecret', - label: 'Client Secret', - placeholder: null, - readOnly: false, - required: true, - type: 'string', - value: null, - }, - ], - reconnectionSteps: [ - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - ], - name: 'resetConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - { - name: 'formattedData', - value: '{fields.all}', - }, - ], - name: 'updateConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - ], - name: 'generateAuthUrl', - type: 'mutation', - }, - { - arguments: [ - { - name: 'url', - value: '{generateAuthUrl.url}', - }, - ], - name: 'openAuthPopup', - type: 'openWithPopup', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - { - name: 'formattedData', - value: '{openAuthPopup.all}', - }, - ], - name: 'updateConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - ], - name: 'verifyConnection', - type: 'mutation', - }, - ], - sharedAuthenticationSteps: [ - { - arguments: [ - { - name: 'key', - value: '{key}', - }, - { - name: 'appAuthClientId', - value: '{appAuthClientId}', - }, - ], - name: 'createConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{createConnection.id}', - }, - ], - name: 'generateAuthUrl', - type: 'mutation', - }, - { - arguments: [ - { - name: 'url', - value: '{generateAuthUrl.url}', - }, - ], - name: 'openAuthPopup', - type: 'openWithPopup', - }, - { - arguments: [ - { - name: 'id', - value: '{createConnection.id}', - }, - { - name: 'formattedData', - value: '{openAuthPopup.all}', - }, - ], - name: 'updateConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{createConnection.id}', - }, - ], - name: 'verifyConnection', - type: 'mutation', - }, - ], - sharedReconnectionSteps: [ - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - ], - name: 'resetConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - { - name: 'appAuthClientId', - value: '{appAuthClientId}', - }, - ], - name: 'updateConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - ], - name: 'generateAuthUrl', - type: 'mutation', - }, - { - arguments: [ - { - name: 'url', - value: '{generateAuthUrl.url}', - }, - ], - name: 'openAuthPopup', - type: 'openWithPopup', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - { - name: 'formattedData', - value: '{openAuthPopup.all}', - }, - ], - name: 'updateConnection', - type: 'mutation', - }, - { - arguments: [ - { - name: 'id', - value: '{connection.id}', - }, - ], - name: 'verifyConnection', - type: 'mutation', - }, - ], - }, - authDocUrl: 'https://automatisch.io/docs/apps/github/connection', - baseUrl: 'https://github.com', - beforeRequest: [null], - dynamicData: [ - { - key: 'listLabels', - name: 'List labels', - }, - { - key: 'listRepos', - name: 'List repos', - }, - ], - iconUrl: 'http://localhost:3000/apps/github/assets/favicon.svg', - key: 'github', - name: 'GitHub', - primaryColor: '000000', - supportsConnections: true, - triggers: [ - { - description: 'Triggers when a new issue is created', - key: 'newIssues', - name: 'New issues', - pollInterval: 15, - substeps: [ - { - key: 'chooseConnection', - name: 'Choose connection', - }, - { - arguments: [ - { - key: 'repo', - label: 'Repo', - required: false, - source: { - arguments: [ - { - name: 'key', - value: 'listRepos', - }, - ], - name: 'getDynamicData', - type: 'query', - }, - type: 'dropdown', - variables: false, - }, - { - description: 'Defaults to any issue you can see.', - key: 'issueType', - label: 'Which types of issues should this trigger on?', - options: [ - { - label: 'Any issue you can see', - value: 'all', - }, - { - label: 'Only issues assigned to you', - value: 'assigned', - }, - { - label: 'Only issues created by you', - value: 'created', - }, - { - label: "Only issues you're mentioned in", - value: 'mentioned', - }, - { - label: "Only issues you're subscribed to", - value: 'subscribed', - }, - ], - required: true, - type: 'dropdown', - value: 'all', - variables: false, - }, - { - dependsOn: ['parameters.repo'], - description: - 'Only trigger on issues when this label is added.', - key: 'label', - label: 'Label', - required: false, - source: { - arguments: [ - { - name: 'key', - value: 'listLabels', - }, - { - name: 'parameters.repo', - value: '{parameters.repo}', - }, - ], - name: 'getDynamicData', - type: 'query', - }, - type: 'dropdown', - variables: false, - }, - ], - key: 'chooseTrigger', - name: 'Set up a trigger', - }, - { - key: 'testStep', - name: 'Test trigger', - }, - ], - }, - { - description: 'Triggers when a new pull request is created', - key: 'newPullRequests', - name: 'New pull requests', - pollInterval: 15, - substeps: [ - { - key: 'chooseConnection', - name: 'Choose connection', - }, - { - arguments: [ - { - key: 'repo', - label: 'Repo', - required: true, - source: { - arguments: [ - { - name: 'key', - value: 'listRepos', - }, - ], - name: 'getDynamicData', - type: 'query', - }, - type: 'dropdown', - variables: false, - }, - ], - key: 'chooseTrigger', - name: 'Set up a trigger', - }, - { - key: 'testStep', - name: 'Test trigger', - }, - ], - }, - { - description: 'Triggers when a user stars a repository', - key: 'newStargazers', - name: 'New stargazers', - pollInterval: 15, - substeps: [ - { - key: 'chooseConnection', - name: 'Choose connection', - }, - { - arguments: [ - { - key: 'repo', - label: 'Repo', - required: true, - source: { - arguments: [ - { - name: 'key', - value: 'listRepos', - }, - ], - name: 'getDynamicData', - type: 'query', - }, - type: 'dropdown', - variables: false, - }, - ], - key: 'chooseTrigger', - name: 'Set up a trigger', - }, - { - key: 'testStep', - name: 'Test trigger', - }, - ], - }, - { - description: 'Triggers when a user watches a repository', - key: 'newWatchers', - name: 'New watchers', - pollInterval: 15, - substeps: [ - { - key: 'chooseConnection', - name: 'Choose connection', - }, - { - arguments: [ - { - key: 'repo', - label: 'Repo', - required: true, - source: { - arguments: [ - { - name: 'key', - value: 'listRepos', - }, - ], - name: 'getDynamicData', - type: 'query', - }, - type: 'dropdown', - variables: false, - }, - ], - key: 'chooseTrigger', - name: 'Set up a trigger', - }, - { - key: 'testStep', - name: 'Test trigger', - }, - ], - }, - ], + authDocUrl: app.authDocUrl, + iconUrl: app.iconUrl, + key: app.key, + name: app.name, + primaryColor: app.primaryColor, + supportsConnections: app.supportsConnections, }, meta: { count: 1,