diff --git a/.github/workflows/develop-deployment.yml b/.github/workflows/develop-deployment.yml index 62d3df6e7..100d29938 100644 --- a/.github/workflows/develop-deployment.yml +++ b/.github/workflows/develop-deployment.yml @@ -46,7 +46,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: yarn install - - run: PRODUCTION=true && yarn run build + - run: yarn run build-minimal - name: Set AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: diff --git a/.github/workflows/master-deployment.yml b/.github/workflows/master-deployment.yml index 9f49ad6a9..24b2b1d00 100644 --- a/.github/workflows/master-deployment.yml +++ b/.github/workflows/master-deployment.yml @@ -47,7 +47,7 @@ jobs: # node-version: ${{ matrix.node-version }} # - run: rm -rf docs # - run: yarn install -# - run: yarn run build +# - run: yarn run build-minimal # - name: Set AWS credentials # uses: aws-actions/configure-aws-credentials@v1 # with: @@ -93,8 +93,8 @@ jobs: # with: # node-version: ${{ matrix.node-version }} # - run: rm -rf docs -# - run: yarn install -# - run: yarn run build +# - run: yarn install-minimal +# - run: yarn run build-minimal # - run: curl ${{ secrets.INDEX_HTML_DEPLOYMENT_URL }} > dist/index.html # - run: npm pack # - run: rm -rf target @@ -129,7 +129,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: cd gallery && yarn install - - run: cd gallery && CI=false yarn run build + - run: cd gallery && yarn run build - name: Set AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: diff --git a/Dockerfile b/Dockerfile index 47816cb62..e5672110a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ COPY ./package.json /usr/local/src/neodash/package.json RUN yarn install COPY ./ /usr/local/src/neodash -RUN PRODUCTION=true && yarn run build +RUN yarn run build-minimal # production stage FROM nginx:alpine AS neodash diff --git a/cypress/fixtures/cypher_queries.js b/cypress/fixtures/cypher_queries.js index 835b9a76e..3daab317e 100644 --- a/cypress/fixtures/cypher_queries.js +++ b/cypress/fixtures/cypher_queries.js @@ -1,13 +1,18 @@ -export const defaultCypherQuery = "MATCH (n) RETURN n LIMIT 25"; -export const tableCypherQuery = "MATCH (n:Movie) RETURN n.title AS title, n.released AS released, id(n) AS __id LIMIT 8"; -export const barChartCypherQuery = "MATCH (n:Movie) RETURN n.released AS released, count(n.title) AS count LIMIT 5"; -export const mapChartCypherQuery = "UNWIND [{id: 'Tilburg', label: 'Cinema', point: point({latitude:51.59444886664065 , longitude:5.088862976119185})}, {id: 'Antwerp', label: 'Cinema', point: point({latitude:51.22065200961528 , longitude:4.414094044161085})}, \n" + -"{id: 'Brussels', label: 'Cinema', point: point({latitude:50.854284724408664, longitude:4.344177490986771})},{id: 'Cologne', label: 'Cinema', point: point({latitude:50.94247712506476 , longitude:6.9699327434361855 })}, \n" + -"{id: 'Nijmegen', label: 'Cinema', point: point({latitude:51.81283449474347 , longitude:5.866804797140869})},{start: 'Tilburg', end: 'Antwerp', type: 'ROUTE_TO', distance: '125km', id: 100}, {start: 'Antwerp', end: 'Brussels', type: 'ROUTE_TO', distance: '70km', id: 101}, \n" + -"{start: 'Brussels', end: 'Cologne', type: 'ROUTE_TO', distance: '259km', id: 102},{start: 'Cologne', end: 'Nijmegen', type: 'ROUTE_TO', distance: '180km', id: 103},{start: 'Nijmegen', end: 'Tilburg', type: 'ROUTE_TO', distance: '92km', id: 104}] as value RETURN value"; -export const sunburstChartCypherQuery = "UNWIND [{path: ['a', 'b'], value: 3}, {path: ['a', 'c'], value: 5},{path: ['a', 'd', 'e'], value: 2},{path: ['a', 'd', 'f'], value: 3}] as x RETURN x.path, x.value"; -export const sankeyChartCypherQuery = "WITH [ { path: { start: {labels: ['Person'], identity: 1, properties: {name: 'Jim'}}, end: {identity: 11}, length: 1, segments: [ { start: {labels: ['Person'], identity: 1, properties: {name: 'Jim'}}, relationship: {type: 'RATES', start: 1, end: 11, identity: 10001, properties: {value: 4.5}}, end: {labels: ['Movie'], identity: 11,properties: {title: 'The Matrix', released: 1999}} } ] }, person: 'Jim', movie: 'The Matrix', value: 4.5 }, { path: { start: {labels: ['Person'], identity: 2, properties: {name: 'Mike'}}, end: {identity: 11}, length: 1, segments: [ { start: {labels: ['Person'], identity: 2, properties: {name: 'Mike'}}, relationship: {type: 'RATES', start: 2, end: 11, identity: 10002, properties: {value: 3.8}}, end: {labels: ['Movie'], identity: 11,properties: {title: 'The Matrix', released: 1999}} } ] }, person: 'Mike', movie: 'The Matrix', value: 3.8 } ] as data UNWIND data as row RETURN row.path as Path" -export const gaugeChartCypherQuery = "RETURN 69"; -export const iFrameText = "https://www.wikipedia.org/"; +export const defaultCypherQuery = 'MATCH (n) RETURN n LIMIT 25'; +export const tableCypherQuery = + 'MATCH (n:Movie) RETURN n.title AS title, n.released AS released, id(n) AS __id LIMIT 8'; +export const barChartCypherQuery = 'MATCH (n:Movie) RETURN n.released AS released, count(n.title) AS count LIMIT 5'; +export const mapChartCypherQuery = + "UNWIND [{id: 'Tilburg', label: 'Cinema', point: point({latitude:51.59444886664065 , longitude:5.088862976119185})}, {id: 'Antwerp', label: 'Cinema', point: point({latitude:51.22065200961528 , longitude:4.414094044161085})}, \n" + + "{id: 'Brussels', label: 'Cinema', point: point({latitude:50.854284724408664, longitude:4.344177490986771})},{id: 'Cologne', label: 'Cinema', point: point({latitude:50.94247712506476 , longitude:6.9699327434361855 })}, \n" + + "{id: 'Nijmegen', label: 'Cinema', point: point({latitude:51.81283449474347 , longitude:5.866804797140869})},{start: 'Tilburg', end: 'Antwerp', type: 'ROUTE_TO', distance: '125km', id: 100}, {start: 'Antwerp', end: 'Brussels', type: 'ROUTE_TO', distance: '70km', id: 101}, \n" + + "{start: 'Brussels', end: 'Cologne', type: 'ROUTE_TO', distance: '259km', id: 102},{start: 'Cologne', end: 'Nijmegen', type: 'ROUTE_TO', distance: '180km', id: 103},{start: 'Nijmegen', end: 'Tilburg', type: 'ROUTE_TO', distance: '92km', id: 104}] as value RETURN value"; +export const sunburstChartCypherQuery = + "UNWIND [{path: ['a', 'b'], value: 3}, {path: ['a', 'c'], value: 5},{path: ['a', 'd', 'e'], value: 2},{path: ['a', 'd', 'f'], value: 3}] as x RETURN x.path, x.value"; +export const sankeyChartCypherQuery = + "WITH [ { path: { start: {labels: ['Person'], identity: 1, properties: {name: 'Jim'}}, end: {identity: 11}, length: 1, segments: [ { start: {labels: ['Person'], identity: 1, properties: {name: 'Jim'}}, relationship: {type: 'RATES', start: 1, end: 11, identity: 10001, properties: {value: 4.5}}, end: {labels: ['Movie'], identity: 11,properties: {title: 'The Matrix', released: 1999}} } ] }, person: 'Jim', movie: 'The Matrix', value: 4.5 }, { path: { start: {labels: ['Person'], identity: 2, properties: {name: 'Mike'}}, end: {identity: 11}, length: 1, segments: [ { start: {labels: ['Person'], identity: 2, properties: {name: 'Mike'}}, relationship: {type: 'RATES', start: 2, end: 11, identity: 10002, properties: {value: 3.8}}, end: {labels: ['Movie'], identity: 11,properties: {title: 'The Matrix', released: 1999}} } ] }, person: 'Mike', movie: 'The Matrix', value: 3.8 } ] as data UNWIND data as row RETURN row.path as Path"; +export const gaugeChartCypherQuery = 'RETURN 69'; +export const iFrameText = 'https://www.wikipedia.org/'; export const markdownText = '# Hello'; -export const loadDashboardURL = 'https://gist.githubusercontent.com/nielsdejong/ee33245256b471f92901ca4073b16ec1/raw/cfaae47e0fcdf430a5de6d0d8e3ac13cfd97742e/dashboard-cypress.json'; +export const loadDashboardURL = + 'https://gist.githubusercontent.com/nielsdejong/ee33245256b471f92901ca4073b16ec1/raw/cfaae47e0fcdf430a5de6d0d8e3ac13cfd97742e/dashboard-cypress.json'; diff --git a/cypress/integration/start_page.spec.js b/cypress/integration/start_page.spec.js index e0d92a79c..e7a7dbbe2 100644 --- a/cypress/integration/start_page.spec.js +++ b/cypress/integration/start_page.spec.js @@ -1,241 +1,279 @@ -import { tableCypherQuery, barChartCypherQuery, mapChartCypherQuery, sunburstChartCypherQuery, iFrameText, markdownText, loadDashboardURL, sankeyChartCypherQuery, gaugeChartCypherQuery } from "../fixtures/cypher_queries" +import { + tableCypherQuery, + barChartCypherQuery, + mapChartCypherQuery, + sunburstChartCypherQuery, + iFrameText, + markdownText, + loadDashboardURL, + sankeyChartCypherQuery, + gaugeChartCypherQuery, +} from '../fixtures/cypher_queries'; // Ignore warnings that may appear when using the Cypress dev server Cypress.on('uncaught:exception', (err, runnable) => { - console.log(err, runnable); - return false; + console.log(err, runnable); + return false; }); describe('NeoDash E2E Tests', () => { - beforeEach(() => { - cy.clearLocalStorage() - cy.viewport(1920, 1080) - // Navigate to index - cy.visit('/') - cy.wait(1000) - cy.get('#form-dialog-title').should('contain', 'NeoDash - Neo4j Dashboard Builder') - cy.wait(300) - - // Create new dashboard - cy.contains('New Dashboard').click() - cy.wait(300) - - // If an old dashboard exists in cache, do a check to make sure we clear it. - // if (cy.contains("Create new dashboard")) { - // cy.contains('Yes').click() - // } - - cy.get('#form-dialog-title').should('contain', 'Connect to Neo4j') - - // Connect to Neo4j database - // cy.get('#protocol').click() - // cy.contains('neo4j').click() - cy.get('#url').clear().type('localhost') - cy.wait(100) - // cy.get('#database').type('neo4j') - cy.get('#dbusername').clear().type('neo4j') - cy.get('#dbpassword').type('test') - cy.wait(100) - cy.get('button').contains('Connect').click() - }) - - it('initializes the dashboard', () => { - // Check the starter cards - cy.get('main .react-grid-item:eq(0)').should('contain', "This is your first dashboard!") - cy.get('main .react-grid-item:eq(1) .force-graph-container canvas').should('be.visible') - cy.get('main .react-grid-item:eq(2) button').should('have.attr', 'aria-label', 'add') - }) - - it('creates a new card', () => { - cy.get('main .react-grid-item:eq(2) button').click() - cy.get('main .react-grid-item:eq(2)').should('contain', 'No query specified.') - }) - - // Test each type of card - it('creates a table report', () => { - cy.get('main .react-grid-item:eq(2) button').click() - cy.get('main .react-grid-item:eq(2) button[aria-label="settings"]').click() - cy.get('main .react-grid-item:eq(2) .MuiInputLabel-root').contains("Type").next().should('contain', 'Table') - cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(tableCypherQuery) - cy.get('main .react-grid-item:eq(2) button[aria-label="save"]').click() - cy.get('main .react-grid-item:eq(2) .MuiDataGrid-columnHeaders').should('contain', 'title').and('contain', 'released').and('not.contain', '__id') - cy.get('main .react-grid-item:eq(2) .MuiDataGrid-virtualScroller .MuiDataGrid-row').should('have.length', 5) - cy.get('main .react-grid-item:eq(2) .MuiDataGrid-footerContainer').should('contain', '1–5 of 8') - cy.get('main .react-grid-item:eq(2) .MuiDataGrid-footerContainer button[aria-label="Go to next page"]').click() - cy.get('main .react-grid-item:eq(2) .MuiDataGrid-virtualScroller .MuiDataGrid-row').should('have.length', 3) - cy.get('main .react-grid-item:eq(2) .MuiDataGrid-footerContainer').should('contain', '6–8 of 8') - }) - - it('creates a bar chart report', () => { - createReportOfType('Bar Chart', barChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Category').next() - .should('contain', 'released') - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Value').next() - .should('contain', 'count') - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 8) - }) - - it('creates a pie chart report', () => { - createReportOfType('Pie Chart', barChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Category').next() - .should('contain', 'released') - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Value').next() - .should('contain', 'count') - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 3) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g:nth-child(2) > path').should('have.length', 5) - }) - - it('creates a line chart report', () => { - createReportOfType('Line Chart', barChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('X-value').next() - .should('contain', 'released') - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Y-value').next() - .should('contain', 'count') - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 6) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g:nth-child(2) > line').should('have.length', 11) - }) - - it('creates a map chart report', () => { - createReportOfType('Map', mapChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > path').should('have.length', 5) - }) - - it('creates a single value report', () => { - createReportOfType('Single Value', barChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root > div > div:nth-child(2) > span').contains('1,999') - }) - - it('creates a gauge chart report', () => { - enableAdvancedVisualizations() - createReportOfType('Gauge Chart', gaugeChartCypherQuery) - cy.get('.text-group > text').contains('69') - }) - - it('creates a sunburst chart report', () => { - enableAdvancedVisualizations() - createReportOfType('Sunburst Chart', sunburstChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Path').next() - .should('contain', 'x.path') - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Value').next() - .should('contain', 'x.value') - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g:nth-child(1) > path').should('have.length', 5) - }) - - it('creates a circle packing report', () => { - enableAdvancedVisualizations() - createReportOfType('Circle Packing', sunburstChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Path').next() - .should('contain', 'x.path') - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Value').next() - .should('contain', 'x.value') - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > circle').should('have.length', 6) - }) - - it('creates a tree map report', () => { - enableAdvancedVisualizations() - createReportOfType('Treemap', sunburstChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Path').next() - .should('contain', 'x.path') - cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root').contains('Value').next() - .should('contain', 'x.value') - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 6) - }) - - it('creates a sankey chart report', () => { - enableAdvancedVisualizations() - createReportOfType('Sankey Chart', sankeyChartCypherQuery, true) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > path').should('have.attr', 'fill-opacity', 0.5) - }) - - it('creates a raw json report', () => { - createReportOfType('Raw JSON', barChartCypherQuery) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root textarea:nth-child(1)').should(($div) => { - const text = $div.text() - expect(text.length).to.eq(1387) - }) - }) - - it('creates a parameter select report', () => { - cy.get('main .react-grid-item:eq(2) button').click() - cy.get('main .react-grid-item:eq(2) button[aria-label="settings"]').click() - cy.get('main .react-grid-item:eq(2) .MuiInputLabel-root').contains("Type").next().click() - cy.contains('Parameter Select').click() - cy.wait(300) - cy.get('#autocomplete-label-type').type('Movie') - cy.get('#autocomplete-label-type-option-0').click() - cy.wait(300) - cy.get('#autocomplete-property').type('title') - cy.get('#autocomplete-property-option-0').click() - cy.get('main .react-grid-item:eq(2) button[aria-label="save"]').click() - cy.get('#autocomplete').type('The Matrix') - cy.get('#autocomplete-option-0').click() - }) - - it('creates an iframe report', () => { - createReportOfType('iFrame', iFrameText) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root iframe') - }) - - it('creates a markdown report', () => { - createReportOfType('Markdown', markdownText) - cy.get('main .react-grid-item:eq(2) .MuiCardContent-root h1').should('have.text', 'Hello') - }) - - // it('creates a radar report', () => { - // // TODO - create a test for radar. - // }) - - - // it('creates a sankey report', () => { - // // TODO - create a test for sankey charts. - // }) - - // Test load stress-test dashboard from file - // TODO - this test is flaky, especially in GitHub actions environment. - it.skip('test load dashboard from file and stress test report customizations', () => { - try { - var NUMBER_OF_PAGES_IN_STRESS_TEST_DASHBOARD = 5; - const file = cy.request(loadDashboardURL).should((response) => { - - cy.get('#root .MuiDrawer-root .MuiIconButton-root:eq(2)').click() - cy.get('.MuiDialog-root .MuiPaper-root .MuiDialogContent-root textarea:eq(0)').invoke('val', response.body).trigger('change') - cy.get('.MuiDialog-root .MuiPaper-root .MuiDialogContent-root textarea:eq(0)').type(' ') - cy.get('.MuiDialog-root .MuiDialogContent-root .MuiButtonBase-root:eq(2)').click() - cy.wait(2500) - - // Click on each page and wait ~3 seconds for it to load completely - for (let i = 1; i < NUMBER_OF_PAGES_IN_STRESS_TEST_DASHBOARD; i++) { - cy.get('.MuiAppBar-root .react-grid-item:eq(' + i + ')').click() - cy.wait(3000) - } - }) - - } catch (e) { - console.log("Unable to fetch test dashboard. Skipping test."); + beforeEach(() => { + cy.clearLocalStorage(); + cy.viewport(1920, 1080); + // Navigate to index + cy.visit('/'); + cy.wait(1000); + cy.get('#form-dialog-title').should('contain', 'NeoDash - Neo4j Dashboard Builder'); + cy.wait(300); + + // Create new dashboard + cy.contains('New Dashboard').click(); + cy.wait(300); + + // If an old dashboard exists in cache, do a check to make sure we clear it. + // if (cy.contains("Create new dashboard")) { + // cy.contains('Yes').click() + // } + + cy.get('#form-dialog-title').should('contain', 'Connect to Neo4j'); + + // Connect to Neo4j database + // cy.get('#protocol').click() + // cy.contains('neo4j').click() + cy.get('#url').clear().type('localhost'); + cy.wait(100); + // cy.get('#database').type('neo4j') + cy.get('#dbusername').clear().type('neo4j'); + cy.get('#dbpassword').type('test'); + cy.wait(100); + cy.get('button').contains('Connect').click(); + }); + + it('initializes the dashboard', () => { + // Check the starter cards + cy.get('main .react-grid-item:eq(0)').should('contain', 'This is your first dashboard!'); + cy.get('main .react-grid-item:eq(1) .force-graph-container canvas').should('be.visible'); + cy.get('main .react-grid-item:eq(2) button').should('have.attr', 'aria-label', 'add'); + }); + + it('creates a new card', () => { + cy.get('main .react-grid-item:eq(2) button').click(); + cy.get('main .react-grid-item:eq(2)').should('contain', 'No query specified.'); + }); + + // Test each type of card + it('creates a table report', () => { + cy.get('main .react-grid-item:eq(2) button').click(); + cy.get('main .react-grid-item:eq(2) button[aria-label="settings"]').click(); + cy.get('main .react-grid-item:eq(2) .MuiInputLabel-root').contains('Type').next().should('contain', 'Table'); + cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(tableCypherQuery); + cy.get('main .react-grid-item:eq(2) button[aria-label="save"]').click(); + cy.get('main .react-grid-item:eq(2) .MuiDataGrid-columnHeaders') + .should('contain', 'title') + .and('contain', 'released') + .and('not.contain', '__id'); + cy.get('main .react-grid-item:eq(2) .MuiDataGrid-virtualScroller .MuiDataGrid-row').should('have.length', 5); + cy.get('main .react-grid-item:eq(2) .MuiDataGrid-footerContainer').should('contain', '1–5 of 8'); + cy.get('main .react-grid-item:eq(2) .MuiDataGrid-footerContainer button[aria-label="Go to next page"]').click(); + cy.get('main .react-grid-item:eq(2) .MuiDataGrid-virtualScroller .MuiDataGrid-row').should('have.length', 3); + cy.get('main .react-grid-item:eq(2) .MuiDataGrid-footerContainer').should('contain', '6–8 of 8'); + }); + + it('creates a bar chart report', () => { + createReportOfType('Bar Chart', barChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Category') + .next() + .should('contain', 'released'); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Value') + .next() + .should('contain', 'count'); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 8); + }); + + it('creates a pie chart report', () => { + createReportOfType('Pie Chart', barChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Category') + .next() + .should('contain', 'released'); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Value') + .next() + .should('contain', 'count'); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 3); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g:nth-child(2) > path').should('have.length', 5); + }); + + it('creates a line chart report', () => { + createReportOfType('Line Chart', barChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('X-value') + .next() + .should('contain', 'released'); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Y-value') + .next() + .should('contain', 'count'); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 6); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g:nth-child(2) > line').should( + 'have.length', + 11 + ); + }); + + it('creates a map chart report', () => { + createReportOfType('Map', mapChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > path').should('have.length', 5); + }); + + it('creates a single value report', () => { + createReportOfType('Single Value', barChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root > div > div:nth-child(2) > span').contains('1,999'); + }); + + it('creates a gauge chart report', () => { + enableAdvancedVisualizations(); + createReportOfType('Gauge Chart', gaugeChartCypherQuery); + cy.get('.text-group > text').contains('69'); + }); + + it('creates a sunburst chart report', () => { + enableAdvancedVisualizations(); + createReportOfType('Sunburst Chart', sunburstChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Path') + .next() + .should('contain', 'x.path'); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Value') + .next() + .should('contain', 'x.value'); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g:nth-child(1) > path').should('have.length', 5); + }); + + it('creates a circle packing report', () => { + enableAdvancedVisualizations(); + createReportOfType('Circle Packing', sunburstChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Path') + .next() + .should('contain', 'x.path'); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Value') + .next() + .should('contain', 'x.value'); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > circle').should('have.length', 6); + }); + + it('creates a tree map report', () => { + enableAdvancedVisualizations(); + createReportOfType('Treemap', sunburstChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Path') + .next() + .should('contain', 'x.path'); + cy.get('main .react-grid-item:eq(2) .MuiCardActions-root .MuiInputLabel-root') + .contains('Value') + .next() + .should('contain', 'x.value'); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > g').should('have.length', 6); + }); + + it('creates a sankey chart report', () => { + enableAdvancedVisualizations(); + createReportOfType('Sankey Chart', sankeyChartCypherQuery, true); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > path').should('have.attr', 'fill-opacity', 0.5); + }); + + it('creates a raw json report', () => { + createReportOfType('Raw JSON', barChartCypherQuery); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root textarea:nth-child(1)').should(($div) => { + const text = $div.text(); + expect(text.length).to.eq(1387); + }); + }); + + it('creates a parameter select report', () => { + cy.get('main .react-grid-item:eq(2) button').click(); + cy.get('main .react-grid-item:eq(2) button[aria-label="settings"]').click(); + cy.get('main .react-grid-item:eq(2) .MuiInputLabel-root').contains('Type').next().click(); + cy.contains('Parameter Select').click(); + cy.wait(300); + cy.get('#autocomplete-label-type').type('Movie'); + cy.get('#autocomplete-label-type-option-0').click(); + cy.wait(300); + cy.get('#autocomplete-property').type('title'); + cy.get('#autocomplete-property-option-0').click(); + cy.get('main .react-grid-item:eq(2) button[aria-label="save"]').click(); + cy.get('#autocomplete').type('The Matrix'); + cy.get('#autocomplete-option-0').click(); + }); + + it('creates an iframe report', () => { + createReportOfType('iFrame', iFrameText); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root iframe'); + }); + + it('creates a markdown report', () => { + createReportOfType('Markdown', markdownText); + cy.get('main .react-grid-item:eq(2) .MuiCardContent-root h1').should('have.text', 'Hello'); + }); + + // it('creates a radar report', () => { + // // TODO - create a test for radar. + // }) + + // it('creates a sankey report', () => { + // // TODO - create a test for sankey charts. + // }) + + // Test load stress-test dashboard from file + // TODO - this test is flaky, especially in GitHub actions environment. + it.skip('test load dashboard from file and stress test report customizations', () => { + try { + var NUMBER_OF_PAGES_IN_STRESS_TEST_DASHBOARD = 5; + const file = cy.request(loadDashboardURL).should((response) => { + cy.get('#root .MuiDrawer-root .MuiIconButton-root:eq(2)').click(); + cy.get('.MuiDialog-root .MuiPaper-root .MuiDialogContent-root textarea:eq(0)') + .invoke('val', response.body) + .trigger('change'); + cy.get('.MuiDialog-root .MuiPaper-root .MuiDialogContent-root textarea:eq(0)').type(' '); + cy.get('.MuiDialog-root .MuiDialogContent-root .MuiButtonBase-root:eq(2)').click(); + cy.wait(2500); + + // Click on each page and wait ~3 seconds for it to load completely + for (let i = 1; i < NUMBER_OF_PAGES_IN_STRESS_TEST_DASHBOARD; i++) { + cy.get('.MuiAppBar-root .react-grid-item:eq(' + i + ')').click(); + cy.wait(3000); } - }) - -}) + }); + } catch (e) { + console.log('Unable to fetch test dashboard. Skipping test.'); + } + }); +}); -function enableAdvancedVisualizations(){ - cy.get('#extensions-sidebar-button').click() - cy.wait(100) - cy.get('#checkbox-advanced-charts').click() - cy.wait(100) - cy.get('#extensions-modal-close-button').click() - cy.wait(200) +function enableAdvancedVisualizations() { + cy.get('#extensions-sidebar-button').click(); + cy.wait(100); + cy.get('#checkbox-advanced-charts').click(); + cy.wait(100); + cy.get('#extensions-modal-close-button').click(); + cy.wait(200); } -function createReportOfType(type, query, fast=false) { - cy.get('main .react-grid-item:eq(2) button').click() - cy.get('main .react-grid-item:eq(2) button[aria-label="settings"]').click() - cy.get('main .react-grid-item:eq(2) .MuiInputLabel-root').contains("Type").next().click() - cy.contains(type).click() - if(fast){ - cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(query, { delay:1, parseSpecialCharSequences: false }) - }else{ - cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(query, { parseSpecialCharSequences: false }) - } - - cy.get('main .react-grid-item:eq(2) button[aria-label="save"]').click() +function createReportOfType(type, query, fast = false) { + cy.get('main .react-grid-item:eq(2) button').click(); + cy.get('main .react-grid-item:eq(2) button[aria-label="settings"]').click(); + cy.get('main .react-grid-item:eq(2) .MuiInputLabel-root').contains('Type').next().click(); + cy.contains(type).click(); + if (fast) { + cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(query, { delay: 1, parseSpecialCharSequences: false }); + } else { + cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(query, { parseSpecialCharSequences: false }); + } + + cy.get('main .react-grid-item:eq(2) button[aria-label="save"]').click(); } diff --git a/package.json b/package.json index 0f690ada0..c33281439 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "dev": "yarn webpack-dev-server --mode development", "debug": "yarn --node-options='--inspect' webpack-dev-server --mode development", "build": "yarn webpack --mode production && cp -r public/* dist/", + "build-minimal": "yarn webpack --mode production --env production && cp -r public/* dist/", "format": "prettier --write \"**/*.{ts,tsx}\"", "lint": "eslint --ext .ts --ext .tsx .", "lint-staged": "lint-staged --config .lintstagedrc.json", diff --git a/src/chart/table/TableChart.tsx b/src/chart/table/TableChart.tsx index 1bcfc3c9d..8715f22eb 100644 --- a/src/chart/table/TableChart.tsx +++ b/src/chart/table/TableChart.tsx @@ -55,10 +55,10 @@ const NeoTableChart = (props: ChartProps) => { const { records } = props; const generateSafeColumnKey = (key) => { - return key != 'id' ? key : `${key } `; + return key != 'id' ? key : `${key} `; }; const columns = transposed - ? ['Field'].concat(records.map((r, j) => `Value${ j == 0 ? '' : ` ${ (j + 1).toString()}`}`)).map((key, i) => { + ? ['Field'].concat(records.map((r, j) => `Value${j == 0 ? '' : ` ${(j + 1).toString()}`}`)).map((key, i) => { const value = key; return ApplyColumnType( { @@ -96,7 +96,7 @@ const NeoTableChart = (props: ChartProps) => { return Object.assign( { id: i, Field: key }, ...records.map((r, j) => ({ - [`Value${ j == 0 ? '' : ` ${ (j + 1).toString()}`}`]: RenderSubValue(r._fields[i]), + [`Value${j == 0 ? '' : ` ${(j + 1).toString()}`}`]: RenderSubValue(r._fields[i]), })) ); }) @@ -160,13 +160,13 @@ const NeoTableChart = (props: ChartProps) => { ColumnSortedAscendingIcon: () => <>, }} getRowClassName={(params) => { - return `rule${ evaluateRulesOnDict(params.row, styleRules, ['row color', 'row text color'])}`; + return `rule${evaluateRulesOnDict(params.row, styleRules, ['row color', 'row text color'])}`; }} getCellClassName={(params) => { - return ( - `rule${ - evaluateRulesOnDict({ [params.field]: params.value }, styleRules, ['cell color', 'cell text color'])}` - ); + return `rule${evaluateRulesOnDict({ [params.field]: params.value }, styleRules, [ + 'cell color', + 'cell text color', + ])}`; }} /> diff --git a/webpack.config.js b/webpack.config.js index e0e7f5ff7..ce7d21bdc 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,44 +1,48 @@ const path = require('path'); const webpack = require('webpack'); -const production = process.env.PRODUCTION === 'true'; - -const rules = [{ +const rules = [ + { test: /\.(js|jsx|ts|tsx)$/, exclude: /(node_modules)/, loader: 'babel-loader', - options: { presets: ["@babel/env"] } -}, -{ + options: { presets: ['@babel/env'] }, + }, + { test: /\.css$/, - use: ["style-loader", "css-loader"] -}, -{ + use: ['style-loader', 'css-loader'], + }, + { test: /\.js$/, exclude: /(node_modules\/react-leaflet-heatmap-layer-v3)/, enforce: 'pre', use: ['source-map-loader'], -}, -{ + }, + { test: /.(png|svg|jpe?g|gif|woff2?|ttf|eot)$/, - use: ['file-loader'] -}] - + use: ['file-loader'], + }, +]; -module.exports = { +// TODO - move this config to a dedicated environment file. +// TODO - use process.env.NODE_ENV which will directly return the environment string "development", "production". +module.exports = (env) => { + const production = env.production; + return { entry: './src/index.tsx', mode: production ? 'production' : 'development', devtool: production ? undefined : 'source-map', module: { - rules: rules + rules: rules, }, resolve: { extensions: ['*', '.js', '.jsx', '.ts', '.tsx'] }, output: { - filename: 'bundle.js' + filename: 'bundle.js', }, devServer: { - port: 3000, - hot: true + port: 3000, + hot: true, }, - plugins: [new webpack.HotModuleReplacementPlugin()] -}; \ No newline at end of file + plugins: [new webpack.HotModuleReplacementPlugin()], + }; +};