diff --git a/.github/workflows/dev-image-update.yaml b/.github/workflows/dev-image-update.yaml index 9e25b3e09..36bf0d089 100644 --- a/.github/workflows/dev-image-update.yaml +++ b/.github/workflows/dev-image-update.yaml @@ -11,7 +11,6 @@ on: - 'cypress.json' - 'cypress/**' - 'Dockerfile' - - 'config-overrides.js' - '.github/workflows/dev-image-update.yaml' env: gcr_google_project: 'broad-jade-dev' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 56dc5156d..ca1653caa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: timeout-minutes: 180 strategy: matrix: - node-version: [14.x] + node-version: [16.x] if: "!contains( github.event.pull_request.labels.*.name, 'skip-ci')" runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index d70564275..fa68ef762 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -18,7 +18,7 @@ jobs: timeout-minutes: 180 strategy: matrix: - node-version: [14.x] + node-version: [16.x] if: "!contains( github.event.pull_request.labels.*.name, 'skip-ci')" runs-on: ubuntu-latest steps: @@ -61,7 +61,7 @@ jobs: env: DISABLE_ESLINT_PLUGIN: true run: | - npm start & npx wait-on http://localhost:3000 + npm start & timeout 600 bash -c "until nc -z localhost 3000; do sleep 5; done" # Build the Docker image - name: Build run: | diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index da840e83d..80f79214a 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -12,7 +12,7 @@ jobs: timeout-minutes: 60 strategy: matrix: - node-version: [14.x] + node-version: [16.x] if: "!contains( github.event.pull_request.labels.*.name, 'skip-ci')" runs-on: ubuntu-latest steps: diff --git a/.node-version b/.node-version index 82c219143..cb406c60c 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -12.22.12 +16.20.2 diff --git a/Dockerfile b/Dockerfile index 1ebd3e330..475ab6577 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN /usr/local/bin/docker-entrypoint.sh generate -g typescript-axios -i $TDR_OPE ## Step 2. Build the deployable UI artifacts -FROM node:14.0-buster as build +FROM node:16.20.2-buster as build # Check out the build RUN set -x \ && git clone https://github.com/DataBiosphere/jade-data-repo-ui \ @@ -23,7 +23,7 @@ COPY --from=codegen /local /jade-data-repo-ui RUN cd jade-data-repo-ui \ && export DISABLE_ESLINT_PLUGIN=true \ && npm ci \ - && npm run build-no-code-gen --production + && npm run build-no-code-gen ## Step 3. Copy the static UI artifacts into an nginx image to host FROM us.gcr.io/broad-dsp-gcr-public/base/nginx:stable-alpine diff --git a/Dockerfile.direct b/Dockerfile.direct index 64c1c3b69..cc31fc715 100644 --- a/Dockerfile.direct +++ b/Dockerfile.direct @@ -10,7 +10,7 @@ ARG TDR_OPEN_API_YAML_LOCATION RUN /usr/local/bin/docker-entrypoint.sh generate -g typescript-axios -i $TDR_OPEN_API_YAML_LOCATION -o /local/src/generated/tdr --skip-validate-spec ## Step 2. Build the deployable UI artifacts -FROM node:14.0-buster as build +FROM node:16.20.2-buster as build # Copy the local code COPY . / # Copy the generated code @@ -18,7 +18,7 @@ COPY --from=codegen /local / # Run the build RUN export DISABLE_ESLINT_PLUGIN=true && \ npm ci && \ - npm run build-no-code-gen --production + npm run build-no-code-gen ## Step 3. Copy the static UI artifacts into an nginx image to host FROM nginxinc/nginx-unprivileged:stable-alpine diff --git a/README.md b/README.md index 71bffbe94..221ff230e 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ nvm install 10.0.0 rm -R ~/.avn (if you want to reset an existing or failed avn setup) nvm exec 10.0.0 npm install -g avn avn-nvm avn-n nvm exec 10.0.0 avn setup -nvm install lts/erbium --default -nvm use lts/erbium +nvm install lts/gallium --default +nvm use lts/gallium ``` - Run `npm install` to download dependencies defined in the package.json file and generate the node_modules folder with the installed modules. @@ -61,15 +61,14 @@ gcloud auth login --no-activate ### Development -- webpack-dev-server 3.x -- react-hot-loader 4.x +- vite-dev-server 5.x - redux-devtools (with browser plugin) `npm start` ### Building -- webpack 4.x +- vite 4.x - babel 7.x `npm run build` @@ -83,7 +82,7 @@ gcloud auth login --no-activate ### Testing -- cypress 9.x +- cypress 12.x To run end-to-end tests: `npx cypress run` or `npx cypress open` (interactive mode) diff --git a/config-overrides.js b/config-overrides.js deleted file mode 100644 index 96b86fff5..000000000 --- a/config-overrides.js +++ /dev/null @@ -1,17 +0,0 @@ -const rewireReactHotLoader = require('react-app-rewire-hot-loader'); - -module.exports = function override(config, env) { - config = rewireReactHotLoader(config, env); - - // To work around https://github.com/webpack/webpack/issues/11467 - // See https://egghead.io/lessons/react-customize-create-react-app-cra-without-ejecting-using-react-app-rewired for a great tutorial on configuring webpack - let loaders = config.module.rules[1].oneOf; - loaders.splice(loaders.length - 1, 0, { - test: /\.m?js/, - resolve: { - fullySpecified: false - } - }); - - return config; -}; diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 000000000..9b0c0d003 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,30 @@ +import { devServer } from '@cypress/vite-dev-server'; +import { defineConfig } from 'cypress'; +import vitePreprocessor from 'cypress-vite'; +import customViteConfig from './vite.config'; + +export default defineConfig({ + component: { + devServer(devServerConfig) { + return devServer({ + ...devServerConfig, + framework: 'react', + viteConfig: customViteConfig, + }); + }, + specPattern: 'src/**/*test.{js,jsx,ts,tsx}', + supportFile: 'cypress/support/index.js', + }, + e2e: { + setupNodeEvents(on) { + on('file:preprocessor', vitePreprocessor()); + }, + specPattern: 'cypress/integration/**/*.{js,jsx,ts,tsx}', + supportFile: 'cypress/support/index.js', + }, + projectId: 'e6ttjx', + viewportWidth: 1920, + viewportHeight: 1080, + defaultCommandTimeout: 60000, + requestTimeout: 120000, +}); diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 4e8183b8a..000000000 --- a/cypress.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "projectId": "e6ttjx", - "viewportWidth": 1920, - "viewportHeight": 1080, - "defaultCommandTimeout": 60000, - "requestTimeout": 120000, - "component": { - "componentFolder": "src", - "testFiles": "**/*test.{js,jsx,ts,tsx}" - } -} diff --git a/cypress/integration/datasetSharing.spec.js b/cypress/integration/datasetSharing.spec.js index 8c401d7a1..67ed92ba2 100644 --- a/cypress/integration/datasetSharing.spec.js +++ b/cypress/integration/datasetSharing.spec.js @@ -1,11 +1,11 @@ describe('test dataset sharing', () => { beforeEach(() => { - cy.server(); - cy.route('GET', 'api/repository/v1/datasets/**').as('getDataset'); - cy.route('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); - cy.route('GET', 'api/repository/v1/datasets/**/roles').as('getDatasetRoles'); - - cy.route({ method: 'GET', url: 'api/resources/v1/profiles/**' }).as('getBillingProfileById'); + cy.intercept('GET', 'api/repository/v1/datasets/**').as('getDataset'); + cy.intercept('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); + cy.intercept('GET', 'api/repository/v1/datasets/**/roles').as('getDatasetRoles'); + cy.intercept({ method: 'GET', url: 'api/resources/v1/profiles/**' }).as( + 'getBillingProfileById', + ); cy.visit('/login/e2e'); cy.get('#tokenInput').type(Cypress.env('GOOGLE_TOKEN'), { diff --git a/cypress/integration/errors.spec.js b/cypress/integration/errors.spec.js index 849a5cef0..2a3977209 100644 --- a/cypress/integration/errors.spec.js +++ b/cypress/integration/errors.spec.js @@ -1,9 +1,7 @@ describe('test error handling', () => { beforeEach(() => { - cy.server(); - - cy.route('GET', 'api/repository/v1/datasets/**').as('getDataset'); - cy.route('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); + cy.intercept('GET', 'api/repository/v1/datasets/**').as('getDataset'); + cy.intercept('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); cy.visit('/login/e2e'); cy.get('#tokenInput').type(Cypress.env('GOOGLE_TOKEN'), { @@ -18,11 +16,9 @@ describe('test error handling', () => { }); it('displays error toasts with error detail', () => { - cy.route({ - method: 'GET', - url: '/api/repository/v1/datasets/**/data/**', - status: 401, - response: { + cy.intercept('GET', '/api/repository/v1/datasets/**/data/**', { + statusCode: 401, + body: { message: 'Was not able to query', errorDetail: ['This is the reason for the error'], }, @@ -35,11 +31,9 @@ describe('test error handling', () => { }); it('displays error toasts with empty error detail', () => { - cy.route({ - method: 'GET', - url: '/api/repository/v1/datasets/**/data/**', - status: 401, - response: { + cy.intercept('GET', '/api/repository/v1/datasets/**/data/**', { + statusCode: 401, + body: { message: 'Was not able to query', errorDetail: [], }, @@ -52,11 +46,9 @@ describe('test error handling', () => { }); it('displays error toasts with no error detail', () => { - cy.route({ - method: 'GET', - url: '/api/repository/v1/datasets/**/data/**', - status: 401, - response: { + cy.intercept('GET', '/api/repository/v1/datasets/**/data/**', { + statusCode: 401, + body: { message: 'Was not able to query', }, }).as('getQueryResults'); diff --git a/cypress/integration/exportSnapshot.spec.js b/cypress/integration/exportSnapshot.spec.js index 473e9bdef..3fc406811 100644 --- a/cypress/integration/exportSnapshot.spec.js +++ b/cypress/integration/exportSnapshot.spec.js @@ -1,8 +1,6 @@ describe('test export snapshot', () => { beforeEach(() => { - cy.server(); - - cy.route('GET', 'api/repository/v1/snapshots/**').as('getSnapshot'); + cy.intercept('GET', 'api/repository/v1/snapshots/**').as('getSnapshot'); cy.visit('/login/e2e'); cy.get('#tokenInput').type(Cypress.env('GOOGLE_TOKEN'), { @@ -21,22 +19,18 @@ describe('test export snapshot', () => { it('exports to google sheets', () => { cy.contains(/Export Snapshot/g).click(); - cy.route({ - method: 'POST', - url: 'drive/v3/files', - status: 200, - response: { + cy.intercept('POST', 'drive/v3/files', { + statusCode: 200, + body: { kind: 'drive#file', id: '1dn_K-ehwE3SoVl-HzcUO0GuN3sYXAlKTfv5JF7RuTTU', name: 'V2FGWASSummaryStatisticsSnapshot1', mimeType: 'application/vnd.google-apps.spreadsheet', }, }).as('createSpreadsheet'); - cy.route({ - method: 'POST', - url: 'googlesheets/v4/spreadsheets/**:batchUpdate', - status: 200, - response: { + cy.intercept('POST', 'googlesheets/v4/spreadsheets/**:batchUpdate', { + statusCode: 200, + body: { spreadsheetId: '1dn_K-ehwE3SoVl-HzcUO0GuN3sYXAlKTfv5JF7RuTTU', replies: [ { diff --git a/cypress/integration/queryBuilder.spec.js b/cypress/integration/queryBuilder.spec.js index 93d4639ed..146c6873f 100644 --- a/cypress/integration/queryBuilder.spec.js +++ b/cypress/integration/queryBuilder.spec.js @@ -12,11 +12,9 @@ const testPlatforms = [ testPlatforms.forEach((testPlatform) => { describe(`test query builder on ${testPlatform.platform} dataset`, () => { beforeEach(() => { - cy.server(); - - cy.route('GET', 'api/repository/v1/datasets/**').as('getDataset'); - cy.route('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); - cy.route('GET', 'api/resources/v1/profiles/**').as('getBillingProfileById'); + cy.intercept('GET', 'api/repository/v1/datasets/**').as('getDataset'); + cy.intercept('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); + cy.intercept('GET', 'api/resources/v1/profiles/**').as('getBillingProfileById'); cy.visit('/login/e2e'); cy.get('#tokenInput').type(Cypress.env('GOOGLE_TOKEN'), { diff --git a/cypress/integration/snapshotCreation.spec.js b/cypress/integration/snapshotCreation.spec.js index fb80db622..928e61700 100644 --- a/cypress/integration/snapshotCreation.spec.js +++ b/cypress/integration/snapshotCreation.spec.js @@ -1,32 +1,26 @@ describe('test snapshot creation', () => { beforeEach(() => { - cy.server(); - - cy.route('GET', 'api/repository/v1/datasets/**').as('getDataset'); - cy.route('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); - cy.route({ method: 'GET', url: 'api/resources/v1/profiles/**' }).as('getBillingProfileById'); - cy.route({ - method: 'POST', - url: '/api/repository/v1/snapshots', - status: 200, - response: { + cy.intercept('GET', 'api/repository/v1/datasets/**').as('getDataset'); + cy.intercept('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); + cy.intercept({ method: 'GET', url: 'api/resources/v1/profiles/**' }).as( + 'getBillingProfileById', + ); + cy.intercept('POST', '/api/repository/v1/snapshots', { + statusCode: 200, + body: { id: 'jobId', }, }); - cy.route({ - method: 'GET', - url: '/api/repository/v1/jobs/jobId', - status: 200, - response: { + cy.intercept('GET', '/api/repository/v1/jobs/jobId', { + statusCpde: 200, + body: { id: 'jobId', job_status: 'succeeded', }, }); - cy.route({ - method: 'GET', - url: '/api/repository/v1/jobs/jobId/result', - status: 200, - response: { + cy.intercept('GET', '/api/repository/v1/jobs/jobId/result', { + statusCode: 200, + body: { name: 'mock_snapshot', description: '', id: 'snapshotId', @@ -34,11 +28,9 @@ describe('test snapshot creation', () => { profileId: 'profileId', }, }); - cy.route({ - method: 'GET', - url: '/api/repository/v1/snapshots/snapshotId**', - status: 200, - response: { + cy.intercept('GET', '/api/repository/v1/snapshots/snapshotId**', { + statusCode: 200, + body: { name: 'mock_snapshot', description: '', id: 'snapshotId', @@ -48,11 +40,9 @@ describe('test snapshot creation', () => { tables: [{ rowCount: 2 }], }, }); - cy.route({ - method: 'GET', - url: '/api/repository/v1/snapshots/snapshotId/policies', - status: 200, - response: { + cy.intercept('GET', '/api/repository/v1/snapshots/snapshotId/policies', { + statusCode: 200, + body: { policies: [ { name: 'reader', @@ -92,13 +82,9 @@ describe('test snapshot creation', () => { describe('test snapshot creation is disabled', () => { beforeEach(() => { - cy.server(); - - cy.route('GET', 'api/repository/v1/datasets/**').as('getDataset'); - cy.route('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); - cy.route({ - method: 'GET', - url: '/api/resources/v1/profiles/**', + cy.intercept('GET', 'api/repository/v1/datasets/**').as('getDataset'); + cy.intercept('GET', 'api/repository/v1/datasets/**/policies').as('getDatasetPolicies'); + cy.intercept('GET', '/api/resources/v1/profiles/**', { status: 401, response: { message: 'unauthorized', diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 48b2a0e87..272203b37 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -8,16 +8,3 @@ // You can read more here: // https://on.cypress.io/plugins-guide // *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) -//eslint-disable-next-line @typescript-eslint/no-var-requires -const injectDevServer = require('@cypress/react/plugins/react-scripts'); - -/** - * @type {Cypress.PluginConfig} - */ -module.exports = (on, config) => { - injectDevServer(on, config); - return config; -}; diff --git a/cypress/support/component-index.html b/cypress/support/component-index.html new file mode 100644 index 000000000..159a1f749 --- /dev/null +++ b/cypress/support/component-index.html @@ -0,0 +1,16 @@ + + + +
+ + + + +