From 995d119a094982938380e08dfccd0ed5cbe2d572 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 09:35:18 +0100 Subject: [PATCH 01/10] Fixed webui API rewrites by moving them from middleware to next.config. middleware only supports limited subset of node.js API, which does not include process.env (https://nextjs.org/docs/app/building-your-application/deploying#middleware). --- webui/next.config.mjs | 17 ++++++++++++++++- webui/src/middleware.ts | 35 ----------------------------------- 2 files changed, 16 insertions(+), 36 deletions(-) delete mode 100644 webui/src/middleware.ts diff --git a/webui/next.config.mjs b/webui/next.config.mjs index c1423129..101039dd 100644 --- a/webui/next.config.mjs +++ b/webui/next.config.mjs @@ -1,4 +1,7 @@ /** @type {import('next').NextConfig} */ + +const API_BASE = process.env.PAVI_API_BASE_URL || 'http://localhost:8000' + const nextConfig = { output: 'standalone', skipTrailingSlashRedirect: true, @@ -8,7 +11,19 @@ const nextConfig = { 'https://raw.githubusercontent.com/alliance-genome/agr_ui/test/', 'https://raw.githubusercontent.com/alliance-genome/agr_ui/stage/' ] - } + }, + rewrites: async () => { return [ + { + source: '/api/docs', + destination: `${API_BASE}/docs`, + }, { + source: '/api/:path', + destination: `${API_BASE}/api/:path`, + }, { + source: '/openapi.json', + destination: `${API_BASE}/openapi.json`, + } + ]} }; export default nextConfig; diff --git a/webui/src/middleware.ts b/webui/src/middleware.ts deleted file mode 100644 index b810e44a..00000000 --- a/webui/src/middleware.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' -import { env } from 'process' - -const API_BASE = env.PAVI_API_BASE_URL || 'http://localhost:8000' -const local_api_path = '/api' - -// This function can be marked `async` if using `await` inside -export function middleware(request: NextRequest) { - - const request_path = request.nextUrl.pathname - - //Proxy root API requests to API server docs - if (request_path === local_api_path) { - const url = request.nextUrl.clone() - url.pathname = local_api_path+'/docs' - return NextResponse.redirect(url) - } - else if (request_path === local_api_path+'/docs') { - return NextResponse.rewrite(new URL('/docs', API_BASE)) - } - //Proxy all other API requests to respective API server endpoints - else if (request_path.startsWith(local_api_path+'/')) { - return NextResponse.rewrite(new URL(request_path, API_BASE)) - } - //Proxy openAPI specs for API server docs - else if (request_path === '/openapi.json') { - return NextResponse.rewrite(new URL('/openapi.json', API_BASE)) - } -} - -// Only apply middleware to API paths (and supporting /openapi.json call) -export const config = { - matcher: ['/api/:path*', '/openapi.json'], -} From fbeda7ca5b3cd72277617c00c23662ca36710072 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 12:02:50 +0100 Subject: [PATCH 02/10] Added E2E openAPI interface test through webUI --- .github/workflows/PR-validation.yml | 1 + webui/Makefile | 4 +-- webui/cypress/e2e/api-rewrite-tests.cy.ts | 44 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 webui/cypress/e2e/api-rewrite-tests.cy.ts diff --git a/.github/workflows/PR-validation.yml b/.github/workflows/PR-validation.yml index 635ba40c..b91dd378 100644 --- a/.github/workflows/PR-validation.yml +++ b/.github/workflows/PR-validation.yml @@ -1000,6 +1000,7 @@ jobs: with: working-directory: webui/ wait-on: 'http://localhost:3000, http://localhost:8080/api/health' + env: 'API_BASE_URL=8080' - name: Cleanup webUI server (running container) working-directory: webui/ run: | diff --git a/webui/Makefile b/webui/Makefile index f7777410..776ce0e6 100644 --- a/webui/Makefile +++ b/webui/Makefile @@ -52,11 +52,11 @@ run-unit-tests: install-deps run-e2e-tests: install-deps $(MAKE) --no-print-directory run-container-dev - npx cypress run --e2e || true + npx cypress run --e2e --env API_BASE_URL=${PAVI_API_BASE_URL} || true $(MAKE) --no-print-directory stop-container-dev run-e2e-tests-dev: install-deps - npx cypress open --e2e + npx cypress open --e2e --env API_BASE_URL=${PAVI_API_BASE_URL} stop-container-dev: @docker compose -f docker-compose-dev.yml --env-file dev.env down agr.pavi.dev-local.webui diff --git a/webui/cypress/e2e/api-rewrite-tests.cy.ts b/webui/cypress/e2e/api-rewrite-tests.cy.ts new file mode 100644 index 00000000..307d8ec9 --- /dev/null +++ b/webui/cypress/e2e/api-rewrite-tests.cy.ts @@ -0,0 +1,44 @@ +/// + +describe('Test API rewrite functionality', () => { + it('/openapi.json response matches API config', () => { + + cy.request(`${Cypress.env('API_BASE_URL')}/openapi.json`).then((apiResp) => { + + expect(apiResp.status).to.eq(200) + + cy.request('/openapi.json').then((webResp) => { + expect(webResp.status).to.eq(200) + expect(JSON.stringify(webResp.body)) + .to.eq(JSON.stringify(apiResp.body)) + }) + }) + }) + + it('Test openAPI UI endpoint execution', () => { + + cy.visit('/api/docs') + + cy.get('span[data-path="/api/health"').parents('button').as('endpointBtn') + .should('have.length', 1) + .should('be.enabled') + .click() + + cy.get('@endpointBtn').parents('div.opblock.is-open').as('operationBlock') + + cy.get('@operationBlock').find('button.try-out__btn') + .should('have.length', 1) + .should('be.enabled') + .click() + + cy.get('@operationBlock').find('button.execute') + .should('have.length', 1) + .should('be.enabled') + .click() + + cy.get('@operationBlock').find('table.live-responses-table') + .should('have.length', 1) + .find('tbody>tr.response>td.response-col_status') + .contains(200) + }) +}) From 1fd19e9cfb2f5bfaa221da4cff56b725f82830d2 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 12:03:26 +0100 Subject: [PATCH 03/10] Added rewrite from webUI /api path to API docs --- webui/next.config.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webui/next.config.mjs b/webui/next.config.mjs index 101039dd..49e9702f 100644 --- a/webui/next.config.mjs +++ b/webui/next.config.mjs @@ -13,6 +13,10 @@ const nextConfig = { ] }, rewrites: async () => { return [ + { + source: '/api', + destination: `${API_BASE}/docs`, + }, { source: '/api/docs', destination: `${API_BASE}/docs`, From 895286430a50f2d272925c104107d524099de533 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 12:20:41 +0100 Subject: [PATCH 04/10] Reindented test scripts to use 4-space indentation --- webui/cypress/e2e/submit-workflow.cy.ts | 134 +++++++++--------- .../client/__tests__/AlignmentEntry.test.tsx | 48 +++---- .../__tests__/AlignmentEntryList.test.tsx | 102 ++++++------- .../client/__tests__/DarkModeToggle.test.tsx | 20 +-- .../client/__tests__/JobSubmitForm.test.tsx | 46 +++--- 5 files changed, 175 insertions(+), 175 deletions(-) diff --git a/webui/cypress/e2e/submit-workflow.cy.ts b/webui/cypress/e2e/submit-workflow.cy.ts index f1df7010..f5c28a62 100644 --- a/webui/cypress/e2e/submit-workflow.cy.ts +++ b/webui/cypress/e2e/submit-workflow.cy.ts @@ -7,76 +7,76 @@ import formInput from '../fixtures/test-submit-success-input.json' describe('submit form behaviour', () => { - beforeEach(() => { - // Cypress starts out with a blank slate for each test - // so we must tell it to visit our website with the `cy.visit()` command - // before each test - cy.visit('/') - }) - - it('tests job submission success', () => { - // We use the `cy.get()` command to get all elements that match the selector. - // There should only be one cell with a inputgroup. - // cy.get('table tbody tr td .p-inputgroup').should('have.length', 1) - - // Should displays one alignmentEntry by default. - cy.get('.p-inputgroup').should('have.length', 1) - - // There should be excactly one submit button - cy.get('button').filter('[aria-label="Submit"]').as('submitBtn') - cy.get('@submitBtn').should('have.length', 1) - - // and it should be disabled by default (on incomplete input). - cy.get('@submitBtn').should('be.disabled') - - // There should be excactly one element to click to add records - cy.get('button#add-record').as('addRecordBtn') - cy.get('@addRecordBtn').should('have.length', 1) - - // add as many records as there are entries in formInput - for(let i = 1, len = formInput.length; i < len; ++i){ - cy.get('@addRecordBtn').click() - } - cy.get('.p-inputgroup').should('have.length', formInput.length) - - // Input all data into form - for(let i = 0, len = formInput.length; i < len; ++i){ - - // Form should be able to receive gene as user input. - cy.get('.p-inputgroup').eq(i).find('input#gene').focus().type(formInput[i].gene) - - // Once the transcript list loaded, from should enable selecting the relevant transcripts. - cy.get('.p-inputgroup').eq(i).find('#transcripts').find('input').focus() - cy.get('.p-multiselect-panel').as('openTranscriptsSelectBox').should('be.visible') - - // A list of transcript should be available - cy.get('@openTranscriptsSelectBox').find('li.p-multiselect-item').as('openTranscriptsList') - cy.get('@openTranscriptsList').should('have.length.at.least', 1) - - // And the relevant transcripts should be selectable - formInput[i].transcripts.forEach((transcript) => { - cy.get('@openTranscriptsList').contains(transcript).click() - }) - cy.get('@openTranscriptsSelectBox').find('button.p-multiselect-close').click() - - cy.focused().blur() - - // Submit button should stay disabled as long a last entry was not submitted - if ( i < len - 1 ) { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command + // before each test + cy.visit('/') + }) + + it('tests job submission success', () => { + // We use the `cy.get()` command to get all elements that match the selector. + // There should only be one cell with a inputgroup. + // cy.get('table tbody tr td .p-inputgroup').should('have.length', 1) + + // Should displays one alignmentEntry by default. + cy.get('.p-inputgroup').should('have.length', 1) + + // There should be excactly one submit button + cy.get('button').filter('[aria-label="Submit"]').as('submitBtn') + cy.get('@submitBtn').should('have.length', 1) + + // and it should be disabled by default (on incomplete input). cy.get('@submitBtn').should('be.disabled') - if (i === 0) { - cy.wait(5000) - cy.get('@submitBtn').should('be.disabled') + // There should be excactly one element to click to add records + cy.get('button#add-record').as('addRecordBtn') + cy.get('@addRecordBtn').should('have.length', 1) + + // add as many records as there are entries in formInput + for(let i = 1, len = formInput.length; i < len; ++i){ + cy.get('@addRecordBtn').click() + } + cy.get('.p-inputgroup').should('have.length', formInput.length) + + // Input all data into form + for(let i = 0, len = formInput.length; i < len; ++i){ + + // Form should be able to receive gene as user input. + cy.get('.p-inputgroup').eq(i).find('input#gene').focus().type(formInput[i].gene) + + // Once the transcript list loaded, from should enable selecting the relevant transcripts. + cy.get('.p-inputgroup').eq(i).find('#transcripts').find('input').focus() + cy.get('.p-multiselect-panel').as('openTranscriptsSelectBox').should('be.visible') + + // A list of transcript should be available + cy.get('@openTranscriptsSelectBox').find('li.p-multiselect-item').as('openTranscriptsList') + cy.get('@openTranscriptsList').should('have.length.at.least', 1) + + // And the relevant transcripts should be selectable + formInput[i].transcripts.forEach((transcript) => { + cy.get('@openTranscriptsList').contains(transcript).click() + }) + cy.get('@openTranscriptsSelectBox').find('button.p-multiselect-close').click() + + cy.focused().blur() + + // Submit button should stay disabled as long a last entry was not submitted + if ( i < len - 1 ) { + cy.get('@submitBtn').should('be.disabled') + + if (i === 0) { + cy.wait(5000) + cy.get('@submitBtn').should('be.disabled') + } + } } - } - } - // Submit button should become active after completing all input - cy.get('@submitBtn').should('be.enabled') + // Submit button should become active after completing all input + cy.get('@submitBtn').should('be.enabled') - // Submitting the analysis should report a UUID - cy.get('@submitBtn').click() - cy.contains('div#display-message', /^job .+ is now pending\.$/) - }) + // Submitting the analysis should report a UUID + cy.get('@submitBtn').click() + cy.contains('div#display-message', /^job .+ is now pending\.$/) + }) }) diff --git a/webui/src/app/components/client/__tests__/AlignmentEntry.test.tsx b/webui/src/app/components/client/__tests__/AlignmentEntry.test.tsx index 72237ae9..f192fda3 100644 --- a/webui/src/app/components/client/__tests__/AlignmentEntry.test.tsx +++ b/webui/src/app/components/client/__tests__/AlignmentEntry.test.tsx @@ -4,33 +4,33 @@ import { render } from '@testing-library/react' import { AlignmentEntry } from '../AlignmentEntry/AlignmentEntry' jest.mock('https://raw.githubusercontent.com/alliance-genome/agr_ui/main/src/lib/utils.js', - () => { - return { - getSpecies: jest.fn(() => {}), - getSingleGenomeLocation: jest.fn(() => {}) - } - }, - {virtual: true} + () => { + return { + getSpecies: jest.fn(() => {}), + getSingleGenomeLocation: jest.fn(() => {}) + } + }, + {virtual: true} ) describe('AlignmentEntry', () => { - it('renders a gene input element', () => { - const result = render( - - ) + it('renders a gene input element', () => { + const result = render( + + ) - const geneInputElement = result.container.querySelector('#gene') - expect(geneInputElement).not.toBe(null) // Expect gene input element to be found - expect(geneInputElement).toHaveClass('p-inputtext') // Expect element to be inputtext box - }) + const geneInputElement = result.container.querySelector('#gene') + expect(geneInputElement).not.toBe(null) // Expect gene input element to be found + expect(geneInputElement).toHaveClass('p-inputtext') // Expect element to be inputtext box + }) - it('renders transcript input element', () => { - const result = render( - - ) - - const transcriptInputElement = result.container.querySelector('#transcripts') - expect(transcriptInputElement).not.toBe(null) // Expect transcript input element to be found - expect(transcriptInputElement).toHaveClass('p-multiselect') // Expect element to be multiselect box - }) + it('renders transcript input element', () => { + const result = render( + + ) + + const transcriptInputElement = result.container.querySelector('#transcripts') + expect(transcriptInputElement).not.toBe(null) // Expect transcript input element to be found + expect(transcriptInputElement).toHaveClass('p-multiselect') // Expect element to be multiselect box + }) }) diff --git a/webui/src/app/components/client/__tests__/AlignmentEntryList.test.tsx b/webui/src/app/components/client/__tests__/AlignmentEntryList.test.tsx index 73ee0e58..d266fa63 100644 --- a/webui/src/app/components/client/__tests__/AlignmentEntryList.test.tsx +++ b/webui/src/app/components/client/__tests__/AlignmentEntryList.test.tsx @@ -4,58 +4,58 @@ import { render, fireEvent } from '@testing-library/react' import { AlignmentEntryList } from '../AlignmentEntryList/AlignmentEntryList' jest.mock('https://raw.githubusercontent.com/alliance-genome/agr_ui/main/src/lib/utils.js', - () => { - return { - getSpecies: jest.fn(() => {}), - getSingleGenomeLocation: jest.fn(() => {}) - } - }, - {virtual: true} + () => { + return { + getSpecies: jest.fn(() => {}), + getSingleGenomeLocation: jest.fn(() => {}) + } + }, + {virtual: true} ) describe('AlignmentEntryList', () => { - it('renders one input record by default', () => { - const result = render( - - ) - - const inputGroups = result.container.querySelectorAll('div.p-inputgroup') - expect(inputGroups).toHaveLength(1) // Expect exactly one input group to be found - }) - - it('renders a functional button to remove individual records', () => { - const result = render( - - ) - - const inputGroupsBefore = result.container.querySelectorAll('div.p-inputgroup') - expect(inputGroupsBefore).toHaveLength(1) // Expect exactly one input group to be found - - const removeRecordBtn = result.container.querySelector('button#remove-record') - expect(removeRecordBtn).not.toBeNull() // Expect remove-record button to be found - expect(removeRecordBtn).toBeEnabled() - - fireEvent.click(removeRecordBtn!) - - //Check one entry-record was removed (zero left) - const inputGroupsAfter = result.container.querySelectorAll('div.p-inputgroup') - expect(inputGroupsAfter).toHaveLength(0) // Expect exactly one input group to be found - }) - - it('renders a functional add-record button', () => { - const result = render( - - ) - - const addRecordBtn = result.container.querySelector('button#add-record') - expect(addRecordBtn).not.toBeNull() // Expect add-record button to be found - expect(addRecordBtn).toBeEnabled() - - //Click the button - fireEvent.click(addRecordBtn!) - - //Check one new entry-record was added (two total) - const inputGroups = result.container.querySelectorAll('div.p-inputgroup') - expect(inputGroups).toHaveLength(2) // Expect exactly two input groups to be found - }) + it('renders one input record by default', () => { + const result = render( + + ) + + const inputGroups = result.container.querySelectorAll('div.p-inputgroup') + expect(inputGroups).toHaveLength(1) // Expect exactly one input group to be found + }) + + it('renders a functional button to remove individual records', () => { + const result = render( + + ) + + const inputGroupsBefore = result.container.querySelectorAll('div.p-inputgroup') + expect(inputGroupsBefore).toHaveLength(1) // Expect exactly one input group to be found + + const removeRecordBtn = result.container.querySelector('button#remove-record') + expect(removeRecordBtn).not.toBeNull() // Expect remove-record button to be found + expect(removeRecordBtn).toBeEnabled() + + fireEvent.click(removeRecordBtn!) + + //Check one entry-record was removed (zero left) + const inputGroupsAfter = result.container.querySelectorAll('div.p-inputgroup') + expect(inputGroupsAfter).toHaveLength(0) // Expect exactly one input group to be found + }) + + it('renders a functional add-record button', () => { + const result = render( + + ) + + const addRecordBtn = result.container.querySelector('button#add-record') + expect(addRecordBtn).not.toBeNull() // Expect add-record button to be found + expect(addRecordBtn).toBeEnabled() + + //Click the button + fireEvent.click(addRecordBtn!) + + //Check one new entry-record was added (two total) + const inputGroups = result.container.querySelectorAll('div.p-inputgroup') + expect(inputGroups).toHaveLength(2) // Expect exactly two input groups to be found + }) }) diff --git a/webui/src/app/components/client/__tests__/DarkModeToggle.test.tsx b/webui/src/app/components/client/__tests__/DarkModeToggle.test.tsx index b185c7a4..11de067f 100644 --- a/webui/src/app/components/client/__tests__/DarkModeToggle.test.tsx +++ b/webui/src/app/components/client/__tests__/DarkModeToggle.test.tsx @@ -5,15 +5,15 @@ import { PrimeReactProvider } from 'primereact/api'; import { DarkModeToggle } from '../DarkModeToggle' describe('DarkModeToggle', () => { - it('renders a switch', () => { - render( - - - - ) + it('renders a switch', () => { + render( + + + + ) - const element = screen.getByRole('switch') - - expect(element).toBeInTheDocument() - }) + const element = screen.getByRole('switch') + + expect(element).toBeInTheDocument() + }) }) diff --git a/webui/src/app/components/client/__tests__/JobSubmitForm.test.tsx b/webui/src/app/components/client/__tests__/JobSubmitForm.test.tsx index 58d67d10..a6a73fdb 100644 --- a/webui/src/app/components/client/__tests__/JobSubmitForm.test.tsx +++ b/webui/src/app/components/client/__tests__/JobSubmitForm.test.tsx @@ -4,32 +4,32 @@ import { render } from '@testing-library/react' import { JobSubmitForm } from '../JobSubmitForm/JobSubmitForm' jest.mock('https://raw.githubusercontent.com/alliance-genome/agr_ui/main/src/lib/utils.js', - () => { - return { - getSpecies: jest.fn(() => {}), - getSingleGenomeLocation: jest.fn(() => {}) - } - }, - {virtual: true} + () => { + return { + getSpecies: jest.fn(() => {}), + getSingleGenomeLocation: jest.fn(() => {}) + } + }, + {virtual: true} ) describe('AlignmentEntry', () => { - it('renders a data-entry form', () => { - const result = render( - - ) + it('renders a data-entry form', () => { + const result = render( + + ) - const inputGroup = result.container.querySelector('div.p-inputgroup') - expect(inputGroup).not.toBeNull() // Expect (at least one) input group to be found - }) + const inputGroup = result.container.querySelector('div.p-inputgroup') + expect(inputGroup).not.toBeNull() // Expect (at least one) input group to be found + }) - it('renders a submit button that is disabled by default', () => { - const result = render( - - ) - - const submitBtn = result.container.querySelector('button[aria-label="Submit"]') - expect(submitBtn).not.toBeNull() // Expect submit button to be found - expect(submitBtn).toBeDisabled() - }) + it('renders a submit button that is disabled by default', () => { + const result = render( + + ) + + const submitBtn = result.container.querySelector('button[aria-label="Submit"]') + expect(submitBtn).not.toBeNull() // Expect submit button to be found + expect(submitBtn).toBeDisabled() + }) }) From d1c0f137b7762268ac4f5cfd2b79536a4a3844d5 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 12:22:32 +0100 Subject: [PATCH 05/10] More indentation updates --- webui/src/app/layout.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/webui/src/app/layout.tsx b/webui/src/app/layout.tsx index 2ce62e90..91faca8d 100644 --- a/webui/src/app/layout.tsx +++ b/webui/src/app/layout.tsx @@ -6,19 +6,19 @@ import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "PAVI webUI pilot", - description: "Generated by create next app", - icons: "https://www.alliancegenome.org/favicon-16x16.png", + title: "PAVI webUI pilot", + description: "Generated by create next app", + icons: "https://www.alliancegenome.org/favicon-16x16.png", }; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - return ( - - {children} - - ); + return ( + + {children} + + ); } From dab6bb7b8cfaa9212a81077321e973211c9f08ff Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 12:35:32 +0100 Subject: [PATCH 06/10] Corrected API_BASE_URL for cypress GHA --- .github/workflows/PR-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/PR-validation.yml b/.github/workflows/PR-validation.yml index b91dd378..734d37a7 100644 --- a/.github/workflows/PR-validation.yml +++ b/.github/workflows/PR-validation.yml @@ -1000,7 +1000,7 @@ jobs: with: working-directory: webui/ wait-on: 'http://localhost:3000, http://localhost:8080/api/health' - env: 'API_BASE_URL=8080' + env: 'API_BASE_URL=http://localhost:8080' - name: Cleanup webUI server (running container) working-directory: webui/ run: | From fe8c628577a02bf6f61426ff133fe0253176b4a4 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 15:05:54 +0100 Subject: [PATCH 07/10] Reverted 1fd19e9c and 995d119a (env vars in next.config defined at build time) --- webui/next.config.mjs | 21 +-------------------- webui/src/middleware.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 webui/src/middleware.ts diff --git a/webui/next.config.mjs b/webui/next.config.mjs index 49e9702f..c1423129 100644 --- a/webui/next.config.mjs +++ b/webui/next.config.mjs @@ -1,7 +1,4 @@ /** @type {import('next').NextConfig} */ - -const API_BASE = process.env.PAVI_API_BASE_URL || 'http://localhost:8000' - const nextConfig = { output: 'standalone', skipTrailingSlashRedirect: true, @@ -11,23 +8,7 @@ const nextConfig = { 'https://raw.githubusercontent.com/alliance-genome/agr_ui/test/', 'https://raw.githubusercontent.com/alliance-genome/agr_ui/stage/' ] - }, - rewrites: async () => { return [ - { - source: '/api', - destination: `${API_BASE}/docs`, - }, - { - source: '/api/docs', - destination: `${API_BASE}/docs`, - }, { - source: '/api/:path', - destination: `${API_BASE}/api/:path`, - }, { - source: '/openapi.json', - destination: `${API_BASE}/openapi.json`, - } - ]} + } }; export default nextConfig; diff --git a/webui/src/middleware.ts b/webui/src/middleware.ts new file mode 100644 index 00000000..b810e44a --- /dev/null +++ b/webui/src/middleware.ts @@ -0,0 +1,35 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' +import { env } from 'process' + +const API_BASE = env.PAVI_API_BASE_URL || 'http://localhost:8000' +const local_api_path = '/api' + +// This function can be marked `async` if using `await` inside +export function middleware(request: NextRequest) { + + const request_path = request.nextUrl.pathname + + //Proxy root API requests to API server docs + if (request_path === local_api_path) { + const url = request.nextUrl.clone() + url.pathname = local_api_path+'/docs' + return NextResponse.redirect(url) + } + else if (request_path === local_api_path+'/docs') { + return NextResponse.rewrite(new URL('/docs', API_BASE)) + } + //Proxy all other API requests to respective API server endpoints + else if (request_path.startsWith(local_api_path+'/')) { + return NextResponse.rewrite(new URL(request_path, API_BASE)) + } + //Proxy openAPI specs for API server docs + else if (request_path === '/openapi.json') { + return NextResponse.rewrite(new URL('/openapi.json', API_BASE)) + } +} + +// Only apply middleware to API paths (and supporting /openapi.json call) +export const config = { + matcher: ['/api/:path*', '/openapi.json'], +} From ebb5de3446b7a092770a08eccf0137e35c5dfb66 Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 15:22:43 +0100 Subject: [PATCH 08/10] Replaced unsupported import of process module. Some node.js modules are not supported in middleware file: https://nextjs.org/docs/messages/node-module-in-edge-runtime --- webui/src/middleware.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webui/src/middleware.ts b/webui/src/middleware.ts index b810e44a..b0a83592 100644 --- a/webui/src/middleware.ts +++ b/webui/src/middleware.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -import { env } from 'process' -const API_BASE = env.PAVI_API_BASE_URL || 'http://localhost:8000' +const API_BASE = process.env.PAVI_API_BASE_URL || 'http://localhost:8000' const local_api_path = '/api' // This function can be marked `async` if using `await` inside From ef0ef6c0c79f5e2396cf7fcfe6a2710eb9be02ac Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 15:29:25 +0100 Subject: [PATCH 09/10] Ignore build and install artifacts in local dev env --- webui/Dockerfile.dockerignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 webui/Dockerfile.dockerignore diff --git a/webui/Dockerfile.dockerignore b/webui/Dockerfile.dockerignore new file mode 100644 index 00000000..5b3ad333 --- /dev/null +++ b/webui/Dockerfile.dockerignore @@ -0,0 +1,2 @@ +.next/ +node_modules/ From 8863f5305a91b4ee378cb58cc0ffb98e37b7c29a Mon Sep 17 00:00:00 2001 From: Manuel Luypaert Date: Fri, 6 Sep 2024 15:32:03 +0100 Subject: [PATCH 10/10] Extended cleanup --- webui/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/webui/Makefile b/webui/Makefile index 776ce0e6..726cd3f5 100644 --- a/webui/Makefile +++ b/webui/Makefile @@ -9,6 +9,7 @@ REG=${AWS_ACCT_NR}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com clean: $(eval ADDITIONAL_BUILD_ARGS := --no-cache) + @rm -rf .next/ @rm -rf node_modules/ container-image: