From 3a150e14d5ad9b2689119823e4686c655a55a83f Mon Sep 17 00:00:00 2001 From: Aloisio Bertolo Bastian Date: Thu, 19 Dec 2024 13:49:02 -0300 Subject: [PATCH] feat: migrate edge functions create and edit pages to api v4 --- .../routes/edge-functions-routes/index.js | 6 +- .../v4/create-edge-functions-service.js | 48 ++++++++ .../v4/edit-edge-functions-service.js | 45 ++++++++ .../edge-functions-services/v4/index.js | 10 +- .../v4/load-edge-function-service.js | 41 ++++++- .../v4/create-edge-functions-service.test.js | 103 ++++++++++++++++++ .../v4/edit-edge-functions-service.test.js | 84 ++++++++++++++ .../v4/load-edge-function-service.test.js | 55 +++++++++- src/views/EdgeFunctions/CreateView.vue | 4 +- src/views/EdgeFunctions/Drawer/index.vue | 6 +- src/views/EdgeFunctions/EditView.vue | 2 +- .../FormFieldsCreateEdgeFunctions.vue | 10 +- .../FormFieldsEditEdgeFunctions.vue | 12 +- 13 files changed, 398 insertions(+), 28 deletions(-) create mode 100644 src/services/edge-functions-services/v4/create-edge-functions-service.js create mode 100644 src/services/edge-functions-services/v4/edit-edge-functions-service.js create mode 100644 src/tests/services/edge-functions-services/v4/create-edge-functions-service.test.js create mode 100644 src/tests/services/edge-functions-services/v4/edit-edge-functions-service.test.js diff --git a/src/router/routes/edge-functions-routes/index.js b/src/router/routes/edge-functions-routes/index.js index a2a4d5185..c4bf860f6 100644 --- a/src/router/routes/edge-functions-routes/index.js +++ b/src/router/routes/edge-functions-routes/index.js @@ -31,7 +31,7 @@ export const edgeFunctionsRoutes = { name: 'create-edge-functions', component: () => import('@views/EdgeFunctions/CreateView.vue'), props: { - createEdgeFunctionsService: EdgeFunctionsService.createEdgeFunctionsService + createEdgeFunctionsService: EdgeFunctionsServiceV4.createEdgeFunctionsService }, meta: { breadCrumbs: [ @@ -51,8 +51,8 @@ export const edgeFunctionsRoutes = { name: 'edit-edge-functions', component: () => import('@views/EdgeFunctions/EditView.vue'), props: { - loadEdgeFunctionsService: EdgeFunctionsService.loadEdgeFunctionsService, - editEdgeFunctionsService: EdgeFunctionsService.editEdgeFunctionsService, + loadEdgeFunctionsService: EdgeFunctionsServiceV4.loadEdgeFunctionService, + editEdgeFunctionsService: EdgeFunctionsServiceV4.editEdgeFunctionsService, updatedRedirect: 'list-edge-functions' }, meta: { diff --git a/src/services/edge-functions-services/v4/create-edge-functions-service.js b/src/services/edge-functions-services/v4/create-edge-functions-service.js new file mode 100644 index 000000000..141977e35 --- /dev/null +++ b/src/services/edge-functions-services/v4/create-edge-functions-service.js @@ -0,0 +1,48 @@ +import * as Errors from '@/services/axios/errors' +import { AxiosHttpClientAdapter } from '../../axios/AxiosHttpClientAdapter' +import { makeEdgeFunctionsBaseUrl } from './make-edge-functions-base-url' +import { extractApiError } from '@/helpers/extract-api-error' + +export const createEdgeFunctionsService = async (payload) => { + let httpResponse = await AxiosHttpClientAdapter.request({ + url: `${makeEdgeFunctionsBaseUrl()}`, + method: 'POST', + body: adapt(payload) + }) + + return parseHttpResponse(httpResponse) +} + +const adapt = (payload) => { + const parsedArgs = JSON.parse(payload.args) + return { + name: payload.name, + code: payload.code, + language: payload.language, + initiator_type: payload.initiatorType, + json_args: parsedArgs, + active: payload.active + } +} + +/** + * @param {Object} httpResponse - The HTTP response object. + * @param {Object} httpResponse.body - The response body. + * @param {String} httpResponse.statusCode - The HTTP status code. + * @returns {string} The result message based on the status code. + * @throws {Error} If there is an error with the response. + */ +const parseHttpResponse = (httpResponse) => { + switch (httpResponse.statusCode) { + case 202: + return { + feedback: 'Your edge function has been created', + urlToEditView: `/edge-functions/edit/${httpResponse.body.data.id}`, + functionId: httpResponse.body.data.id + } + case 500: + throw new Errors.InternalServerError().message + default: + throw new Error(extractApiError(httpResponse)).message + } +} diff --git a/src/services/edge-functions-services/v4/edit-edge-functions-service.js b/src/services/edge-functions-services/v4/edit-edge-functions-service.js new file mode 100644 index 000000000..df3dc8e49 --- /dev/null +++ b/src/services/edge-functions-services/v4/edit-edge-functions-service.js @@ -0,0 +1,45 @@ +import * as Errors from '@/services/axios/errors' +import { AxiosHttpClientAdapter } from '../../axios/AxiosHttpClientAdapter' +import { makeEdgeFunctionsBaseUrl } from './make-edge-functions-base-url' +import { extractApiError } from '@/helpers/extract-api-error' + +export const editEdgeFunctionsService = async (payload) => { + const parsedPayload = adapt(payload) + let httpResponse = await AxiosHttpClientAdapter.request({ + url: `${makeEdgeFunctionsBaseUrl()}/${payload.id}`, + method: 'PATCH', + body: parsedPayload + }) + + return parseHttpResponse(httpResponse) +} + +/** + * @param {Object} httpResponse - The HTTP response object. + * @param {Object} httpResponse.body - The response body. + * @param {String} httpResponse.statusCode - The HTTP status code. + * @returns {string} The result message based on the status code. + * @throws {Error} If there is an error with the response. + */ +const parseHttpResponse = (httpResponse) => { + switch (httpResponse.statusCode) { + case 202: + return 'Your edge function has been updated' + case 500: + throw new Errors.InternalServerError().message + default: + throw new Error(extractApiError(httpResponse)).message + } +} + +const adapt = (payload) => { + const parsedArgs = JSON.parse(payload.args) + return { + name: payload.name, + code: payload.code, + language: payload.language, + initiator_type: payload.initiatorType, + json_args: parsedArgs, + active: payload.active + } +} diff --git a/src/services/edge-functions-services/v4/index.js b/src/services/edge-functions-services/v4/index.js index c7cc34397..a99b3094e 100644 --- a/src/services/edge-functions-services/v4/index.js +++ b/src/services/edge-functions-services/v4/index.js @@ -1,6 +1,8 @@ import { listEdgeFunctionsService } from './list-edge-functions-service' import { listEdgeFunctionsDropdownService } from './list-edge-functions-dropdown-service' import { loadEdgeFunctionService } from './load-edge-function-service' +import { createEdgeFunctionsService } from './create-edge-functions-service' +import { editEdgeFunctionsService } from './edit-edge-functions-service' /** * @typedef {Object} ExportedServicesType - The type of the exported services @@ -10,4 +12,10 @@ import { loadEdgeFunctionService } from './load-edge-function-service' /** * @type {ExportedServicesType} */ -export { listEdgeFunctionsService, listEdgeFunctionsDropdownService, loadEdgeFunctionService } +export { + listEdgeFunctionsService, + listEdgeFunctionsDropdownService, + loadEdgeFunctionService, + createEdgeFunctionsService, + editEdgeFunctionsService +} diff --git a/src/services/edge-functions-services/v4/load-edge-function-service.js b/src/services/edge-functions-services/v4/load-edge-function-service.js index e0796421d..287e8a4ec 100644 --- a/src/services/edge-functions-services/v4/load-edge-function-service.js +++ b/src/services/edge-functions-services/v4/load-edge-function-service.js @@ -12,16 +12,51 @@ export const loadEdgeFunctionService = async ({ id }) => { return parseHttpResponse(httpResponse) } +const STATUS_AS_TAG = { + true: { + content: 'Active', + severity: 'success' + }, + false: { + content: 'Inactive', + severity: 'danger' + } +} + +const LANGUAGE_WITH_ICON = { + javascript: { + content: 'JavaScript', + icon: 'javascript' + }, + lua: { + content: 'Lua', + icon: 'lua' + } +} + const adapt = (httpResponse) => { const { data } = httpResponse.body - const parsedEdgeFunctions = { + const parsedFunction = { id: data.id, + active: data.active, + language: data.language, + initiatorType: data.initiator_type, + lastEditor: data.last_editor, + referenceCount: data.reference_count, + args: JSON.stringify(data.json_args, null, 2), name: data.name, - args: JSON.stringify(data.json_args, null, '\t') + code: data.code, + version: data.version || '-', + modified: new Intl.DateTimeFormat('us', { dateStyle: 'full' }).format( + new Date(data.last_modified) + ), + statusTag: STATUS_AS_TAG[data.active], + languageIcon: LANGUAGE_WITH_ICON[data.language], + isProprietaryCode: data.is_proprietary_code || false } return { - body: parsedEdgeFunctions, + body: parsedFunction, statusCode: httpResponse.statusCode } } diff --git a/src/tests/services/edge-functions-services/v4/create-edge-functions-service.test.js b/src/tests/services/edge-functions-services/v4/create-edge-functions-service.test.js new file mode 100644 index 000000000..31f998dc0 --- /dev/null +++ b/src/tests/services/edge-functions-services/v4/create-edge-functions-service.test.js @@ -0,0 +1,103 @@ +import { AxiosHttpClientAdapter } from '@/services/axios/AxiosHttpClientAdapter' +import * as Errors from '@/services/axios/errors' +import { createEdgeFunctionsService } from '@/services/edge-functions-services/v4' +import { describe, expect, it, vi } from 'vitest' + +const fixtures = { + edgeFunctionMock: { + name: 'mockFunction', + code: 'function test() { return true }', + language: 'javascript', + initiatorType: 'request', + args: JSON.stringify({ key: 'value' }), + active: true + } +} + +const makeSut = () => { + const sut = createEdgeFunctionsService + return { sut } +} + +describe('EdgeFunctionsService', () => { + it('should call API with correct params', async () => { + const requestSpy = vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 202, + body: { + data: { + id: '123456' + } + } + }) + const { sut } = makeSut() + + await sut(fixtures.edgeFunctionMock) + + expect(requestSpy).toHaveBeenCalledWith({ + url: expect.any(String), + method: 'POST', + body: { + name: fixtures.edgeFunctionMock.name, + code: fixtures.edgeFunctionMock.code, + language: fixtures.edgeFunctionMock.language, + initiator_type: fixtures.edgeFunctionMock.initiatorType, + json_args: { key: 'value' }, + active: fixtures.edgeFunctionMock.active + } + }) + }) + + it('should return feedback and URLs when successfully creating an edge function', async () => { + const functionId = '123456' + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 202, + body: { + data: { + id: functionId + } + } + }) + const { sut } = makeSut() + + const result = await sut(fixtures.edgeFunctionMock) + + expect(result).toEqual({ + feedback: 'Your edge function has been created', + urlToEditView: `/edge-functions/edit/${functionId}`, + functionId: functionId + }) + }) + + it('should throw internal server error when request fails with 500 status code', async () => { + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 500, + body: { + detail: 'Internal server error' + } + }) + + const { sut } = makeSut() + + const apiErrorResponse = sut(fixtures.edgeFunctionMock) + const expectedError = new Errors.InternalServerError().message + + expect(apiErrorResponse).rejects.toBe(expectedError) + }) + + it('should throw parsing api error when request fails', async () => { + const apiErrorMock = { + detail: 'api error message' + } + + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 400, + body: apiErrorMock + }) + + const { sut } = makeSut() + + const apiErrorResponse = sut(fixtures.edgeFunctionMock) + + expect(apiErrorResponse).rejects.toBe('api error message') + }) +}) diff --git a/src/tests/services/edge-functions-services/v4/edit-edge-functions-service.test.js b/src/tests/services/edge-functions-services/v4/edit-edge-functions-service.test.js new file mode 100644 index 000000000..66d0146bb --- /dev/null +++ b/src/tests/services/edge-functions-services/v4/edit-edge-functions-service.test.js @@ -0,0 +1,84 @@ +import { AxiosHttpClientAdapter } from '@/services/axios/AxiosHttpClientAdapter' +import * as Errors from '@/services/axios/errors' +import { editEdgeFunctionsService } from '@/services/edge-functions-services/v4' +import { describe, expect, it, vi } from 'vitest' + +const fixtures = { + edgeFunctionMock: { + id: '123456', + name: 'Test Function', + code: 'function test() { return true; }', + language: 'javascript', + initiatorType: 'event', + args: JSON.stringify({ key: 'value' }), + active: true + } +} + +const makeSut = () => { + const sut = editEdgeFunctionsService + return { sut } +} + +describe('EdgeFunctionsServices', () => { + it('should call API with correct params', async () => { + const requestSpy = vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 202 + }) + const { sut } = makeSut() + + await sut(fixtures.edgeFunctionMock) + + expect(requestSpy).toHaveBeenCalledWith({ + url: `v4/edge_functions/functions/${fixtures.edgeFunctionMock.id}`, + method: 'PATCH', + body: { + name: fixtures.edgeFunctionMock.name, + code: fixtures.edgeFunctionMock.code, + language: fixtures.edgeFunctionMock.language, + initiator_type: fixtures.edgeFunctionMock.initiatorType, + json_args: JSON.parse(fixtures.edgeFunctionMock.args), + active: fixtures.edgeFunctionMock.active + } + }) + }) + + it('should return success message when edge function is updated', async () => { + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 202 + }) + const { sut } = makeSut() + + const result = await sut(fixtures.edgeFunctionMock) + + expect(result).toBe('Your edge function has been updated') + }) + + it('should throw internal server error when request fails with 500 status code', async () => { + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 500, + body: { detail: 'Internal server error' } + }) + + const { sut } = makeSut() + const promise = sut(fixtures.edgeFunctionMock) + + await expect(promise).rejects.toBe(new Errors.InternalServerError().message) + }) + + it('should throw API error when request fails with non-500 status code', async () => { + const apiErrorMock = { + detail: 'api error message' + } + + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 400, + body: apiErrorMock + }) + + const { sut } = makeSut() + const promise = sut(fixtures.edgeFunctionMock) + + await expect(promise).rejects.toBe('api error message') + }) +}) diff --git a/src/tests/services/edge-functions-services/v4/load-edge-function-service.test.js b/src/tests/services/edge-functions-services/v4/load-edge-function-service.test.js index 329bbbd15..df900850c 100644 --- a/src/tests/services/edge-functions-services/v4/load-edge-function-service.test.js +++ b/src/tests/services/edge-functions-services/v4/load-edge-function-service.test.js @@ -3,12 +3,22 @@ import { loadEdgeFunctionService } from '@/services/edge-functions-services/v4' import { describe, expect, it, vi } from 'vitest' const FUNCTION_ID = '456' +const MOCK_DATE = '2024-03-20T10:00:00Z' const fixture = { edgeFunction: { id: FUNCTION_ID, name: 'function name', - json_args: { key: 'value' } + json_args: { key: 'value' }, + active: true, + language: 'javascript', + initiator_type: 'http', + last_editor: 'john.doe@example.com', + reference_count: 2, + code: 'console.log("Hello")', + version: '1.0.0', + last_modified: MOCK_DATE, + is_proprietary_code: false } } @@ -46,9 +56,46 @@ describe('EdgeFunctionServices', () => { const result = await sut({ id: FUNCTION_ID }) expect(result).toEqual({ - id: fixture.edgeFunction.id, - name: fixture.edgeFunction.name, - args: JSON.stringify(fixture.edgeFunction.json_args, null, '\t') + id: FUNCTION_ID, + name: 'function name', + args: JSON.stringify({ key: 'value' }, null, 2), + active: true, + language: 'javascript', + initiatorType: 'http', + lastEditor: 'john.doe@example.com', + referenceCount: 2, + code: 'console.log("Hello")', + version: '1.0.0', + modified: new Intl.DateTimeFormat('us', { dateStyle: 'full' }).format(new Date(MOCK_DATE)), + statusTag: { + content: 'Active', + severity: 'success' + }, + languageIcon: { + content: 'JavaScript', + icon: 'javascript' + }, + isProprietaryCode: false + }) + }) + + it('should handle inactive status correctly', async () => { + const inactiveFunction = { + ...fixture.edgeFunction, + active: false + } + + vi.spyOn(AxiosHttpClientAdapter, 'request').mockResolvedValueOnce({ + statusCode: 200, + body: { data: inactiveFunction } + }) + + const { sut } = makeSut() + const result = await sut({ id: FUNCTION_ID }) + + expect(result.statusTag).toEqual({ + content: 'Inactive', + severity: 'danger' }) }) diff --git a/src/views/EdgeFunctions/CreateView.vue b/src/views/EdgeFunctions/CreateView.vue index e32066be2..8bcfbb39f 100644 --- a/src/views/EdgeFunctions/CreateView.vue +++ b/src/views/EdgeFunctions/CreateView.vue @@ -55,7 +55,7 @@ const validationSchema = yup.object({ name: yup.string().required('Name is a required field'), code: yup.string().required('Code is a required field'), - jsonArgs: yup.string().test('validJson', 'Invalid JSON', (value) => { + args: yup.string().test('validJson', 'Invalid JSON', (value) => { let isValidJson = true try { JSON.parse(value) @@ -75,7 +75,7 @@ active: true, language: 'javascript', code: HelloWorldSample, - jsonArgs: ARGS_INITIAL_STATE, + args: ARGS_INITIAL_STATE, initiatorType: 'edge_application' } diff --git a/src/views/EdgeFunctions/Drawer/index.vue b/src/views/EdgeFunctions/Drawer/index.vue index 06c9ce043..aee6eac1a 100644 --- a/src/views/EdgeFunctions/Drawer/index.vue +++ b/src/views/EdgeFunctions/Drawer/index.vue @@ -8,7 +8,7 @@ import { handleTrackerError } from '@/utils/errorHandlingTracker' import HelloWorldSample from '@/helpers/edge-function-hello-world' import { useRouter } from 'vue-router' - import { createEdgeFunctionsService } from '@/services/edge-functions-services' + import { createEdgeFunctionsService } from '@/services/edge-functions-services/v4' defineOptions({ name: 'edge-functions-drawer' }) @@ -32,7 +32,7 @@ const validationSchema = yup.object({ name: yup.string().required('Name is a required field'), code: yup.string().required('Code is a required field'), - jsonArgs: yup.string().test('validJson', 'Invalid JSON', (value) => { + args: yup.string().test('validJson', 'Invalid JSON', (value) => { let isValidJson = true try { JSON.parse(value) @@ -56,7 +56,7 @@ active: true, language: 'javascript', code: HelloWorldSample, - jsonArgs: ARGS_INITIAL_STATE, + args: ARGS_INITIAL_STATE, initiatorType: initiatorType } const handleTrackCreation = () => { diff --git a/src/views/EdgeFunctions/EditView.vue b/src/views/EdgeFunctions/EditView.vue index 15c6f184c..0780c7be0 100644 --- a/src/views/EdgeFunctions/EditView.vue +++ b/src/views/EdgeFunctions/EditView.vue @@ -50,7 +50,7 @@ const validationSchema = yup.object({ name: yup.string().required('Name is a required field'), code: yup.string().required('Code is a required field'), - jsonArgs: yup.string().test('validJson', 'Invalid JSON', (value) => { + args: yup.string().test('validJson', 'Invalid JSON', (value) => { let isValidJson = true try { JSON.parse(value) diff --git a/src/views/EdgeFunctions/FormFields/FormFieldsCreateEdgeFunctions.vue b/src/views/EdgeFunctions/FormFields/FormFieldsCreateEdgeFunctions.vue index 388e0dfba..109264940 100644 --- a/src/views/EdgeFunctions/FormFields/FormFieldsCreateEdgeFunctions.vue +++ b/src/views/EdgeFunctions/FormFields/FormFieldsCreateEdgeFunctions.vue @@ -39,7 +39,7 @@ const { value: name } = useField('name') - const { value: jsonArgs, errorMessage: jsonArgsError } = useField('jsonArgs', null, { + const { value: args, errorMessage: argsError } = useField('args', null, { initialValue: ARGS_INITIAL_STATE }) const { value: code, errorMessage: codeError } = useField('code', null, { @@ -51,13 +51,13 @@ }) const hasArgsError = computed(() => { - return !!jsonArgsError.value + return !!argsError.value }) const updateObject = computed(() => { const previewValues = { code: code.value, - args: jsonArgs.value + args: args.value } emit('update:previewData', previewValues) return previewValues @@ -216,7 +216,7 @@ >
{ initialCodeValue = code.value - initialJsonArgsValue = jsonArgs.value + initialJsonArgsValue = args.value if (initialCodeValue) { unwatch() @@ -61,13 +61,13 @@ }) const hasArgsError = computed(() => { - return !!jsonArgsError.value + return !!argsError.value }) const updateObject = computed(() => { const previewValues = { code: code.value, - args: jsonArgs.value + args: args.value } emit('update:previewData', previewValues) return previewValues @@ -234,7 +234,7 @@ >