diff --git a/.github/workflows/master-deployment.yml b/.github/workflows/master-deployment.yml
index 06c1d1087..c7c3afe16 100644
--- a/.github/workflows/master-deployment.yml
+++ b/.github/workflows/master-deployment.yml
@@ -31,7 +31,7 @@ jobs:
with:
build: yarn run build
start: yarn run dev
- wait-on: "http://localhost:3000"
+ wait-on: 'http://localhost:3000'
browser: chrome
build-s3:
needs: build-test
@@ -79,7 +79,7 @@ jobs:
context: .
file: ./Dockerfile
push: true
- tags: ${{ secrets.DOCKER_HUB_LABS_USERNAME }}/neodash:latest,${{ secrets.DOCKER_HUB_LABS_USERNAME }}/neodash:2.3.5
+ tags: ${{ secrets.DOCKER_HUB_LABS_USERNAME }}/neodash:latest,${{ secrets.DOCKER_HUB_LABS_USERNAME }}/neodash:2.4.6
build-docker-legacy:
needs: build-test
runs-on: neodash-runners
@@ -103,7 +103,7 @@ jobs:
context: .
file: ./Dockerfile
push: true
- tags: ${{ secrets.DOCKER_HUB_USERNAME }}/neodash:latest,${{ secrets.DOCKER_HUB_USERNAME }}/neodash:2.3.5
+ tags: ${{ secrets.DOCKER_HUB_USERNAME }}/neodash:latest,${{ secrets.DOCKER_HUB_USERNAME }}/neodash:2.4.6
deploy-gallery:
runs-on: neodash-runners
strategy:
diff --git a/.gitignore b/.gitignore
index 49f853b33..56c2e7ad8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,7 +15,7 @@ target
/coverage
/.nyc_output
cypress/videos
-
+cypress/screenshots
# production
/build
/dist
diff --git a/Dockerfile b/Dockerfile
index c31f51ae8..1e78a8511 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# build stage
-FROM node:lts-alpine AS build-stage
+FROM node:lts-alpine3.18 AS build-stage
RUN yarn global add typescript jest
WORKDIR /usr/local/src/neodash
@@ -10,13 +10,14 @@ WORKDIR /usr/local/src/neodash
# Copy sources and install/build
COPY ./package.json /usr/local/src/neodash/package.json
+COPY ./yarn.lock /usr/local/src/neodash/yarn.lock
RUN yarn install
COPY ./ /usr/local/src/neodash
RUN yarn run build-minimal
# production stage
-FROM nginx:alpine AS neodash
+FROM nginx:alpine3.18 AS neodash
RUN apk upgrade
ENV NGINX_PORT=5005
@@ -43,4 +44,4 @@ USER nginx
EXPOSE $NGINX_PORT
HEALTHCHECK cmd curl --fail "http://localhost:$NGINX_PORT" || exit 1
-LABEL version="2.3.5"
+LABEL version="2.4.6"
diff --git a/changelog.md b/changelog.md
index 1e203fa53..aceee2e41 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,128 @@
+## NeoDash 2.4.6
+This is a minor release containing a few critical fixes and some extra style customizations:
+
+- Fix bad text wrapping for arrays in tables ([868](https://github.com/neo4j-labs/neodash/pull/868)).
+- Make wrapping in table optional, disabled by default ([872](https://github.com/neo4j-labs/neodash/pull/872)).
+- Fixed issues where cross database dashboard sharing always reverted back to the default database ([873](https://github.com/neo4j-labs/neodash/pull/873)).
+- Added option to define style config using environment variables for the Docker image ([876](https://github.com/neo4j-labs/neodash/pull/876)).
+
+## NeoDash 2.4.5
+This is a small release containing a few fixes:
+- Fixed rendering of string arrays inside tables, report titles, and report action buttons [849](https://github.com/neo4j-labs/neodash/pull/849)
+- Allowed text to wrap in tables, preserving the number of rows [852](https://github.com/neo4j-labs/neodash/pull/852)
+- Disabled auto-sorting of Cypher query-based Parameter Select ; use Cypher ORDER BY to control result order [857](https://github.com/neo4j-labs/neodash/pull/857)
+- Updated role selector menu, and made user updates more robust [854](https://github.com/neo4j-labs/neodash/pull/854)
+
+Thanks to all the contributors for this release:
+- [MariusC](https://github.com/mariusconjeaud),
+- [LiamEdwardsLamarche](https://github.com/LiamEdwardsLamarche),
+- [AleSim94](https://github.com/AleSim94)
+
+## NeoDash 2.4.4
+This is a hotfix release fixing some breaking issues in the 2.4.3:
+- Fixed number parsing using newer versions of the Neo4j driver. [811](https://github.com/neo4j-labs/neodash/pull/811)
+- Reverted new connection handler for auto-renewed SSO sessions. [815](https://github.com/neo4j-labs/neodash/pull/815)
+- Improved handling of parameters in form extension, resolved local state issues. [813](https://github.com/neo4j-labs/neodash/pull/813)
+- Updated Role management extension to no longer execute queries in parallel, improved UX and error handling [813](https://github.com/neo4j-labs/neodash/pull/813)
+
+If you are currently using NeoDash version 2.4.3, we recommend updating as soon as possible.
+
+## NeoDash 2.4.3
+This release contains several improvements and additions to multi-dashboard management, as well as a bug fixes and a variety of quality-of-life improvements:
+
+Dashboard management and access control:
+- Added a UI for handling dashboard access using RBAC, as well as a new extension to simply access control.
+- Added button to sidebar to refresh the list of dashboards saved in the database.
+- Improved handling and detection of draft dashboards in the dashboard sidebar.
+
+Other improvements:
+- Changed CSV export functionality for tables to use UTF-8 format.
+- Various improvements / fixes to the documentation to include new images, and up-to-date functionality.
+- Added logic for handling refresh tokens when connected to NeoDash via SSO.
+- Incorporated tooltips for bar charts with and without custom labels.
+
+Bug fixes and testing:
+- Implemented bug fixes on type casting for numeric parameter selectors.
+- Fixed issue with report actions not functioning properly on node click events.
+- Extended test suite with Cypress tests for advanced settings in the bar chart.
+
+Thanks to all the contributors for this release:
+- [OskarDamkjaer](https://github.com/OskarDamkjaer)
+- [alfredorubin96](https://github.com/alfredorubin96),
+- [AleSim94](https://github.com/AleSim94),
+- [BennuFire](https://github.com/BennuFire),
+- [jacobbleakley-neo4j](https://github.com/jacobbleakley-neo4j),
+- [josepmonclus](https://github.com/josepmonclus)
+- [nielsdejong](https://github.com/nielsdejong)
+
+
+## NeoDash 2.4.2
+This is a release with a large amount of quality of life improvements, as well as some new features:
+
+- Visualize graphs in 3D with the new 3D graph report. [#737](https://github.com/neo4j-labs/neodash/pull/737)
+- Improved dashboard management sidebar and handling of drafts. [#734](https://github.com/neo4j-labs/neodash/pull/734)
+- Added parameter select setting for autopopulating first selector value. [#746](https://github.com/neo4j-labs/neodash/pull/746)
+- Improved UX for editing page names & dashboard titles. [#743](https://github.com/neo4j-labs/neodash/pull/743)
+- Unified common settings for each report type. [#724](https://github.com/neo4j-labs/neodash/pull/724)
+- Title of the browser tab NeoDash runs on is now automatically set to the dashboard name. [#708](https://github.com/neo4j-labs/neodash/pull/708)
+- Fixed issue where invisible table columns were not handled correctly. [#695](https://github.com/neo4j-labs/neodash/pull/695)
+- Miscellaneous bug fixes, style improvements & stability fixes. [#744](https://github.com/neo4j-labs/neodash/pull/744)
+
+
+## NeoDash 2.4.1
+This is a patch release following 2.4.0. It contains several new features for self-hosted (standalone) NeoDash deployments, as well as a variety of UX improvements for dashboard editors.
+
+
+Included:
+- Improvements to customizability of the bar chart (styling, legend customization, report actions). [#689](https://github.com/neo4j-labs/neodash/pull/689)
+- Improved dashboard settings interface, fixed alignment for table download button. [#729](https://github.com/neo4j-labs/neodash/pull/729)
+- Adjusted ordering of suggested labels/properties for parameter selectors. [#728](https://github.com/neo4j-labs/neodash/pull/728)
+- Better handling of date parameters when saving/loading dashboards. [#727](https://github.com/neo4j-labs/neodash/pull/727)
+- Fixed incorrect z-index issue for form creation modals. [#726](https://github.com/neo4j-labs/neodash/pull/726)
+- Adjusted filtering tooltip on tables to avoid hiding result data. [#712](https://github.com/neo4j-labs/neodash/pull/712)
+- Fixed uncontrolled component issue for dashboard import modal. [#711](https://github.com/neo4j-labs/neodash/pull/711)
+- Adjusted font color of graph context popups to use theme colors. [#699](https://github.com/neo4j-labs/neodash/pull/699)
+- Adjust sidebar database selector to only show active databases. [#698](https://github.com/neo4j-labs/neodash/pull/698)
+- Incorporated logging functionality for self-hosted NeoDash deployments. [#705](https://github.com/neo4j-labs/neodash/pull/705)
+- Improved dashboard management in standalone-mode deployments. [#705](https://github.com/neo4j-labs/neodash/pull/705)
+- Added Docker parameter for overriding the app's logo & custom header. [#705](https://github.com/neo4j-labs/neodash/pull/705)
+- Changed the dashboard 'save' action to a logical merge, rather than a delete + create, allowing to persist labels across saves. [#705](https://github.com/neo4j-labs/neodash/pull/705)
+- Docker: Updated Alpine base image to mitigate CVE-2023-38039 & CVE-2023-4863. [#705](https://github.com/neo4j-labs/neodash/pull/705)
+
+
+## NeoDash 2.4.0
+NeoDash 2.4 is out! 🎂 This release packs a ton of new features, as well as improvements to the existing visualizations.
+
+Key new features:
+- A new sidebar with support for managing, save and load multiple dashboards directly from the UI.
+ [#657](https://github.com/neo4j-labs/neodash/pull/657)
+- Added **Forms** as a new extension. Forms let you combine multiple parameter selectors in one card and have users edit/submit data to Neo4j. [#568](https://github.com/neo4j-labs/neodash/pull/568)
+- Added a new advanced visualization type: Gantt charts. [#684](https://github.com/neo4j-labs/neodash/pull/684)
+- Doubled the grid resolution for dashboards, giving you more freedom to arrange visualizations. [#682](https://github.com/neo4j-labs/neodash/pull/682)
+- Several improvements for the natural language queries extension - including customizable prompting, and faster schema retrieval. [#600](https://github.com/neo4j-labs/neodash/pull/600)
+
+Other improvements:
+- Support for multiselect checkboxes as a report action for tables. [#688](https://github.com/neo4j-labs/neodash/pull/688/commits)
+- Added keyboard shortcuts (CMD/CTRL+Enter) for running Cypher queries from the editor. [#694](https://github.com/neo4j-labs/neodash/pull/694/)
+- Added new experimental graph layouts (trees in various directions), with customizable level distance. [#690](https://github.com/neo4j-labs/neodash/pull/690)
+- Increased customizability for the Pie chart's styling. [#638](https://github.com/neo4j-labs/neodash/pull/638/)
+- Fixed issues with parameter selector: Better handling of integer / long parameters and processing external updates. [#641](https://github.com/neo4j-labs/neodash/pull/641/)
+- Improvements on text readability for the experimental dark mode. [#668](https://github.com/neo4j-labs/neodash/pull/668/)
+- UX improvements on database connection interface. [#675](https://github.com/neo4j-labs/neodash/pull/675/)
+- Added option to provide a custom message when no data is returned by a report. [#683](https://github.com/neo4j-labs/neodash/pull/683/)
+- Fixed issue where column names were not hidden correctly. [#685](https://github.com/neo4j-labs/neodash/pull/685/commits)
+
+Thanks to all the contributors for this release:
+[alfredorubin96](https://github.com/alfredorubin96),
+[AleSim94](https://github.com/AleSim94),
+[BennuFire](https://github.com/BennuFire),
+[jacobbleakley-neo4j](https://github.com/jacobbleakley-neo4j),
+[hugorplobo](https://github.com/hugorplobo),
+[brahmprakashMishra](https://github.com/brahmprakashMishra),
+[m-o-n-i-s-h](https://github.com/m-o-n-i-s-h),
+[JonanOribe](https://github.com/JonanOribe),
+[nielsdejong](https://github.com/nielsdejong)
+
## NeoDash 2.3.5
This is a bugfix / stability release directly following 2.3.4.
diff --git a/cypress/e2e/bar_chart.cy.js b/cypress/e2e/bar_chart.cy.js
new file mode 100644
index 000000000..3f9c20faa
--- /dev/null
+++ b/cypress/e2e/bar_chart.cy.js
@@ -0,0 +1,316 @@
+import { barChartCypherQuery } from '../fixtures/cypher_queries';
+
+const WAITING_TIME = 20000;
+// Ignore warnings that may appear when using the Cypress dev server
+Cypress.on('uncaught:exception', (err, runnable) => {
+ console.log(err, runnable);
+ return false;
+});
+
+describe('Testing bar chart', () => {
+ beforeEach('open neodash', () => {
+ cy.viewport(1920, 1080);
+ cy.visit('/', {
+ onBeforeLoad(win) {
+ win.localStorage.clear();
+ },
+ });
+
+ cy.get('#form-dialog-title', { timeout: 20000 }).should('contain', 'NeoDash - Neo4j Dashboard Builder').click();
+
+ cy.get('#form-dialog-title').then(($div) => {
+ const text = $div.text();
+ if (text == 'NeoDash - Neo4j Dashboard Builder') {
+ cy.wait(500);
+ // Create new dashboard
+ cy.contains('New Dashboard').click();
+ }
+ });
+
+ cy.get('#form-dialog-title', { timeout: 20000 }).should('contain', 'Connect to Neo4j');
+
+ cy.get('#url').clear().type('localhost');
+ cy.get('#dbusername').clear().type('neo4j');
+ cy.get('#dbpassword').type('test1234');
+ cy.get('button').contains('Connect').click();
+ cy.wait(100);
+
+ //Opens the div containing all report cards
+ cy.get('.react-grid-layout:eq(0)').within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ //Clicks the 2nd button (opens settings)
+ cy.get('button').eq(1).click();
+ });
+ });
+ cy.get('.react-grid-layout:eq(0)').within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ //Opens the drop down
+ cy.getDataTest('type-dropdown').click();
+ });
+ });
+ // Selects the Bar option
+ cy.get('[id^="react-select-5-option"]')
+ .contains(/Bar Chart/i)
+ .should('be.visible')
+ .click({ force: true });
+ cy.get('.react-grid-layout .MuiGrid-root:eq(1) #type input[name="Type"]').should('have.value', 'Bar Chart');
+
+ // Creates basic bar chart
+ cy.get('.react-grid-layout')
+ .first()
+ .within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ //Removes text in cypher editor and types new query
+ cy.get('.ndl-cypher-editor div[role="textbox"]')
+ .should('be.visible')
+ .click()
+ .clear()
+ .type(barChartCypherQuery);
+
+ cy.wait(400);
+ cy.get('button[aria-label="run"]').click();
+ });
+ });
+
+ cy.wait(500);
+ });
+
+ it.skip('Checking Colour Picker settings', () => {
+ //Opens advanced settings
+ cy.get('.react-grid-layout')
+ .first()
+ .within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ // Access advanced settings
+ cy.get('button').eq(1).click();
+ cy.get('[role="switch"]').click();
+ cy.wait(200);
+ // Changing setting for colour picker
+ cy.get('[data-testid="colorpicker-input"]').find('input').click().type('{selectall}').type('red');
+ cy.get('button[aria-label="run"]').click();
+ // Checking that colour picker was applied correctly
+ cy.get('.card-view').should('have.css', 'background-color', 'rgb(255, 0, 0)');
+ cy.wait(200);
+ // Changing colour back to white
+ cy.get('button').eq(1).click();
+ cy.get('[data-testid="colorpicker-input"]').find('input').click().type('{selectall}').type('white');
+ cy.get('button[aria-label="run"]').click();
+ // Checking colour has been set back to white
+ cy.wait(200);
+ cy.get('.card-view').should('have.css', 'background-color', 'rgb(255, 255, 255)');
+ });
+ });
+ });
+
+ it.skip('Checking Selector Description', () => {
+ //Opens first 2nd card
+ cy.get('.react-grid-layout:eq(0) .MuiGrid-root:eq(1)').within(() => {
+ // Access advanced settings
+ cy.get('button').eq(1).click();
+ cy.get('[role="switch"]').click();
+ cy.wait(200);
+ // Changing Selector Description to 'Test'
+ cy.get('.ndl-textarea').contains('span', 'Selector Description').click().type('Test');
+ cy.get('button[aria-label="run"]').click();
+ // Pressing Selector Description button
+ cy.get('button[aria-label="details"]').click();
+ });
+ // Checking that Selector Description is behaving as expected
+ cy.get('.MuiDialog-paper').should('be.visible').and('contain.text', 'Test');
+ cy.wait(1000);
+
+ // Click elsewhere on the page to close dialog box
+ cy.get('div[role="dialog"]').parent().click(-100, -100, { force: true });
+ });
+
+ it.skip('Checking full screen bar chart setting', () => {
+ //Opens first 2nd card
+ cy.get('.react-grid-layout:eq(0) .MuiGrid-root:eq(1)').within(() => {
+ // Opening settings
+ cy.get('button').eq(1).click();
+ // Activating advanced settings
+ cy.get('[role="switch"]').click();
+ cy.wait(200);
+ // Finding fullscreen setting and changing it to 'on'
+ cy.get('.ndl-dropdown')
+ .contains('label', 'Fullscreen enabled')
+ .scrollIntoView()
+ .should('be.visible')
+ .click()
+ .type('on{enter}');
+ // Pressing run to return to card view
+ cy.get('button[aria-label="run"]').click();
+ cy.get('button[aria-label="maximize"]').click();
+ });
+ // Modal outside of scope of card
+ // Checking existence of full-screen modal
+ cy.get('.dialog-xxl').should('be.visible');
+ // Action to close full-screen modal
+ cy.get('button[aria-label="un-maximize"]').click();
+ // Checking that fullscreen has un-maximized
+ // Check that the div is no longer in the DOM
+ cy.get('div[data-focus-lock-disabled="false"]').should('not.exist');
+ });
+
+ it.skip('Checking "Autorun Query" works as intended', () => {
+ // Custom command to open advanced settings
+ cy.advancedSettings(() => {
+ // Finding 'Auto-run query setting and changing it to 'off'
+ cy.get('.ndl-dropdown')
+ .contains('label', 'Auto-run query')
+ .scrollIntoView()
+ .should('be.visible')
+ .click()
+ .type('off{enter}');
+ cy.wait(200);
+ cy.get('button[aria-label="run"]').click();
+ cy.get('.ndl-cypher-editor').should('be.visible');
+ cy.get('g').should('not.exist');
+ cy.wait(100);
+ cy.get('.MuiCardContent-root').find('button[aria-label="run"]').filter(':visible').click();
+ cy.get('g').should('exist');
+ });
+ });
+
+ it.skip('Checking Legend integration works as intended', () => {
+ cy.advancedSettings(() => {
+ // Checking that legend appears
+ cy.setDropdownValue('Show Legend', 'on');
+ cy.wait(100);
+ cy.get('button[aria-label="run"]').click();
+ cy.wait(100);
+ //Checking that legend matches value specified: in the case - 'count'
+ cy.get('svg g g text').last().contains(/count/i);
+ });
+ cy.advancedSettings(() => {
+ // Activating advanced settings
+ cy.get('[role="switch"]').click();
+ // Checking that legend disappears
+ cy.setDropdownValue('Show Legend', 'off');
+ cy.wait(100);
+ cy.get('button[aria-label="run"]').click();
+ cy.wait(100);
+ cy.get('svg g g text').last().contains(/count/i).should('not.exist');
+ });
+ });
+
+ it.skip('Checking the stacked grouping function works as intended', () => {
+ cy.advancedSettings(() => {
+ cy.get('.ndl-cypher-editor div[role="textbox"]')
+ .should('be.visible')
+ .click()
+ .clear()
+ .type(
+ 'MATCH (p:Person)-[:DIRECTED]->(n:Movie) RETURN n.released AS released, p.name AS Director, count(n.title) AS count LIMIT 5'
+ );
+ cy.setDropdownValue('Grouping', 'on');
+ cy.wait(100);
+ cy.get('button[aria-label="run"]').click();
+ cy.get('.ndl-dropdown:contains("Group")').find('svg').parent().click().type('Director{enter}');
+ // Checking that the groups are stacked
+ cy.get('.MuiCardContent-root')
+ .find('g')
+ .children('g')
+ .eq(3) // Get the fourth g element (index starts from 0)
+ .invoke('attr', 'transform')
+ .then((transformValue) => {
+ // Captures the first number in the tranlsate attribute using the parenthisis to capture the first digit and put it in the second value of the resulting array
+ // if transformValue is translate(100,200), then transformValue.match(/translate\((\d+),\d+\)/) will produce an array like ["translate(100,200)", "100"],
+ const match = transformValue.match(/translate\((\d+),\d+\)/);
+ if (match?.[1]) {
+ const xValue = match[1];
+ console.log('xValue: ', xValue);
+
+ // Now find sibling g elements with the same x transform value
+ cy.get('.MuiCardContent-root')
+ .find('g')
+ .children('g')
+ .filter((index, element) => {
+ const siblingTransform = Cypress.$(element).attr('transform');
+ return siblingTransform?.includes(`translate(${xValue},`);
+ })
+ .should('have.length', 3); // Check that there's at least one element
+ } else {
+ throw new Error('Transform attribute not found or invalid format');
+ }
+ });
+ });
+ cy.get('.ndl-dropdown:contains("Group")').find('svg').parent().click().type('(none){enter}');
+ // Checking that the stacked grouped elements do not exist
+ cy.get('.MuiCardContent-root')
+ .find('g')
+ .children('g')
+ .eq(3) // Get the fourth g element (index starts from 0)
+ .invoke('attr', 'transform')
+ .then((transformValue) => {
+ // Captures the first number in the tranlsate attribute using the parenthisis to capture the first digit and put it in the second value of the resulting array
+ // if transformValue is translate(100,200), then transformValue.match(/translate\((\d+),\d+\)/) will produce an array like ["translate(100,200)", "100"],
+ const match = transformValue.match(/translate\((\d+),\d+\)/);
+ if (match?.[1]) {
+ const xValue = match[1];
+ console.log('xValue: ', xValue);
+
+ // Now find sibling g elements with the same x transform value
+ cy.get('.MuiCardContent-root')
+ .find('g')
+ .children('g')
+ .filter((index, element) => {
+ const siblingTransform = Cypress.$(element).attr('transform');
+ return siblingTransform?.includes(`translate(${xValue},`);
+ })
+ .should('have.length', 1); // Check that there are no matching elements
+ } else {
+ throw new Error('Transform attribute not found or invalid format');
+ }
+ });
+ });
+
+ // How to properly test this?
+ it.skip('Testing grouped grouping mode', () => {
+ cy.advancedSettings(() => {
+ cy.get('.ndl-cypher-editor div[role="textbox"]')
+ .should('be.visible')
+ .click()
+ .clear()
+ .type(
+ 'MATCH (p:Person)-[:DIRECTED]->(n:Movie) RETURN n.released AS released, p.name AS Director, count(n.title) AS count LIMIT 5'
+ );
+ cy.setDropdownValue('Grouping', 'on');
+ cy.setDropdownValue('Group Mode', 'grouped');
+ cy.wait(400);
+ cy.get('button[aria-label="run"]').click();
+ cy.get('.ndl-dropdown:contains("Group")').find('svg').parent().click().type('Director{enter}');
+ });
+ });
+
+ it.skip('Testing "Show Value on Bars"', () => {
+ cy.advancedSettings(() => {
+ cy.setDropdownValue('Show Values On Bars', 'on');
+ cy.get('button[aria-label="run"]').click();
+ cy.get('.MuiCardContent-root')
+ .find('div svg > g > g > text')
+ .should('have.length', 5)
+ .then((textElements) => {
+ cy.log('Number of text elements:', textElements.length);
+ });
+ });
+ cy.wait(100);
+ cy.openSettings(() => {
+ cy.setDropdownValue('Show Values On Bars', 'off');
+ cy.get('button[aria-label="run"]').click();
+ cy.get('.MuiCardContent-root').find('div svg > g > g > text').should('not.exist');
+ });
+ });
+});
diff --git a/cypress/e2e/start_page.cy.js b/cypress/e2e/start_page.cy.js
index e28dae082..2f6bfa873 100644
--- a/cypress/e2e/start_page.cy.js
+++ b/cypress/e2e/start_page.cy.js
@@ -8,6 +8,7 @@ import {
loadDashboardURL,
sankeyChartCypherQuery,
gaugeChartCypherQuery,
+ formCypherQuery,
} from '../fixtures/cypher_queries';
const WAITING_TIME = 20000;
@@ -57,17 +58,17 @@ describe('NeoDash E2E Tests', () => {
});
it('initializes the dashboard', () => {
- checkInitialState();
+ cy.checkInitialState();
});
it('creates a new card', () => {
- checkInitialState();
- createCard();
+ cy.checkInitialState();
+ cy.createCard();
});
// Test each type of card
it('creates a table report', () => {
- checkInitialState();
+ cy.checkInitialState();
cy.get('main .react-grid-item button[aria-label="add report"]').should('be.visible').click();
cy.get('main .react-grid-item')
.contains('No query specified.')
@@ -87,15 +88,15 @@ describe('NeoDash E2E Tests', () => {
.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-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', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Bar Chart', barChartCypherQuery);
cy.get('main .react-grid-item:eq(2) #index input[name="Category"]', { timeout: WAITING_TIME }).should(
'have.value',
@@ -106,7 +107,7 @@ describe('NeoDash E2E Tests', () => {
});
it('creates a pie chart report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Pie Chart', barChartCypherQuery);
cy.get('main .react-grid-item:eq(2) #index input[name="Category"]', { timeout: WAITING_TIME }).should(
'have.value',
@@ -118,7 +119,7 @@ describe('NeoDash E2E Tests', () => {
});
it('creates a line chart report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Line Chart', barChartCypherQuery);
cy.get('main .react-grid-item:eq(2) #x input[name="X-value"]', { timeout: WAITING_TIME }).should(
'have.value',
@@ -133,7 +134,7 @@ describe('NeoDash E2E Tests', () => {
});
it('creates a map chart report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Map', mapChartCypherQuery, true);
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > path', { timeout: WAITING_TIME }).should(
'have.length',
@@ -142,7 +143,7 @@ describe('NeoDash E2E Tests', () => {
});
it('creates a single value report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Single Value', barChartCypherQuery);
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root > div > div:nth-child(2) > span', {
timeout: WAITING_TIME,
@@ -153,16 +154,16 @@ describe('NeoDash E2E Tests', () => {
});
});
- it('creates a gauge chart report', () => {
+ it.skip('creates a gauge chart report', () => {
enableAdvancedVisualizations();
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Gauge Chart', gaugeChartCypherQuery);
cy.get('.text-group > text', { timeout: WAITING_TIME }).contains('69');
});
it('creates a sunburst chart report', () => {
enableAdvancedVisualizations();
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Sunburst Chart', sunburstChartCypherQuery);
cy.get('main .react-grid-item:eq(2) #index input[name="Path"]', { timeout: WAITING_TIME }).should(
'have.value',
@@ -174,7 +175,7 @@ describe('NeoDash E2E Tests', () => {
it('creates a circle packing report', () => {
enableAdvancedVisualizations();
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Circle Packing', sunburstChartCypherQuery);
cy.get('main .react-grid-item:eq(2) #index input[name="Path"]', { timeout: WAITING_TIME }).should(
'have.value',
@@ -186,7 +187,7 @@ describe('NeoDash E2E Tests', () => {
it('creates a tree map report', () => {
enableAdvancedVisualizations();
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Treemap', sunburstChartCypherQuery);
cy.get('main .react-grid-item:eq(2) #index input[name="Path"]', { timeout: WAITING_TIME }).should(
'have.value',
@@ -198,7 +199,7 @@ describe('NeoDash E2E Tests', () => {
it('creates a sankey chart report', () => {
enableAdvancedVisualizations();
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Sankey Chart', sankeyChartCypherQuery, true);
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root svg > g > path', { timeout: WAITING_TIME }).should(
'have.attr',
@@ -208,7 +209,7 @@ describe('NeoDash E2E Tests', () => {
});
it('creates a raw json report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Raw JSON', barChartCypherQuery);
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root textarea:nth-child(1)', { timeout: 45000 }).should(
($div) => {
@@ -219,7 +220,7 @@ describe('NeoDash E2E Tests', () => {
});
it('creates a parameter select report', () => {
- checkInitialState();
+ cy.checkInitialState();
selectReportOfType('Parameter Select');
cy.wait(500);
cy.get('#autocomplete-label-type').type('Movie');
@@ -233,24 +234,39 @@ describe('NeoDash E2E Tests', () => {
});
it('creates an iframe report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('iFrame', iFrameText);
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root iframe', { timeout: 45000 }).should('be.visible');
});
it('creates a markdown report', () => {
- checkInitialState();
+ cy.checkInitialState();
createReportOfType('Markdown', markdownText);
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root h1', { timeout: 45000 }).should('have.text', 'Hello');
});
- // it('creates a radar report', () => {
- // // TODO - create a test for radar.
- // })
+ it.skip('creates a form report', () => {
+ enableFormsExtension();
+ cy.checkInitialState();
+ createReportOfType('Form', formCypherQuery, true, false);
+ cy.get('main .react-grid-item:eq(2) .form-add-parameter').click();
+ cy.wait(200);
+ cy.get('#autocomplete-label-type').type('Movie');
+ cy.get('#autocomplete-label-type-option-0').click();
+ cy.wait(200);
+ cy.get('#autocomplete-property').type('title');
+ cy.get('#autocomplete-property-option-0').click();
+
+ cy.get('.ndl-dialog-close').click();
- // it('creates a sankey report', () => {
- // // TODO - create a test for sankey charts.
- // })
+ cy.get('main .react-grid-item:eq(2) button[aria-label="run"]').scrollIntoView().should('be.visible').click();
+ cy.wait(500);
+ cy.get('#autocomplete').type('The Matrix');
+ cy.get('#autocomplete-option-0').click();
+ cy.get('#form-submit').click();
+ cy.wait(500);
+ cy.get('.form-submitted-message').should('have.text', 'Form Submitted.Reset Form');
+ });
// Test load stress-test dashboard from file
// TODO - this test is flaky, especially in GitHub actions environment.
@@ -285,6 +301,14 @@ function enableAdvancedVisualizations() {
cy.wait(200);
}
+function enableFormsExtension() {
+ cy.get('main button[aria-label="Extensions').should('be.visible').click();
+ cy.get('#checkbox-forms').scrollIntoView();
+ cy.get('#checkbox-forms').should('be.visible').click();
+ cy.get('.ndl-dialog-close').scrollIntoView().should('be.visible').click();
+ cy.wait(200);
+}
+
function selectReportOfType(type) {
cy.get('main .react-grid-item button[aria-label="add report"]').should('be.visible').click();
cy.get('main .react-grid-item')
@@ -298,7 +322,7 @@ function selectReportOfType(type) {
cy.wait(100);
}
-function createReportOfType(type, query, fast = false) {
+function createReportOfType(type, query, fast = false, run = true) {
selectReportOfType(type);
if (fast) {
cy.get('main .react-grid-item:eq(2) .ReactCodeMirror').type(query, { delay: 1, parseSpecialCharSequences: false });
@@ -308,22 +332,7 @@ function createReportOfType(type, query, fast = false) {
cy.wait(400);
cy.get('main .react-grid-item:eq(2)').contains('Advanced settings').click();
-
- cy.get('main .react-grid-item:eq(2) button[aria-label="run"]').click();
-}
-
-function checkInitialState() {
- // 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 report');
-}
-
-function createCard() {
- // Check the starter cards
- cy.get('main .react-grid-item button[aria-label="add report"]', { timeout: WAITING_TIME })
- .should('be.visible')
- .click();
- cy.wait(1000);
- cy.get('main .react-grid-item:eq(2)').should('contain', 'No query specified.');
+ if (run) {
+ cy.get('main .react-grid-item:eq(2) button[aria-label="run"]').click();
+ }
}
diff --git a/cypress/e2e/table.cy.js b/cypress/e2e/table.cy.js
new file mode 100644
index 000000000..8ae2ac3c8
--- /dev/null
+++ b/cypress/e2e/table.cy.js
@@ -0,0 +1,82 @@
+import { tableCypherQuery } from '../fixtures/cypher_queries';
+
+const WAITING_TIME = 20000;
+// Ignore warnings that may appear when using the Cypress dev server
+Cypress.on('uncaught:exception', (err, runnable) => {
+ console.log(err, runnable);
+ return false;
+});
+
+describe('Testing table', () => {
+ beforeEach('open neodash', () => {
+ cy.viewport(1920, 1080);
+ cy.visit('/', {
+ onBeforeLoad(win) {
+ win.localStorage.clear();
+ },
+ });
+
+ cy.get('#form-dialog-title', { timeout: 20000 }).should('contain', 'NeoDash - Neo4j Dashboard Builder').click();
+
+ cy.get('#form-dialog-title').then(($div) => {
+ const text = $div.text();
+ if (text == 'NeoDash - Neo4j Dashboard Builder') {
+ cy.wait(500);
+ // Create new dashboard
+ cy.contains('New Dashboard').click();
+ }
+ });
+
+ cy.get('#form-dialog-title', { timeout: 20000 }).should('contain', 'Connect to Neo4j');
+
+ cy.get('#url').clear().type('localhost');
+ cy.get('#dbusername').clear().type('neo4j');
+ cy.get('#dbpassword').type('test1234');
+ cy.get('button').contains('Connect').click();
+ cy.wait(100);
+ });
+
+ it.skip('create a table', () => {
+ //Opens the div containing all report cards
+ cy.get('.react-grid-layout:eq(0)')
+ .first()
+ .within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ //Clicks the 2nd button (opens settings)
+ cy.get('button').eq(1).click();
+ // cy.get('div[role="textbox"')
+ });
+ });
+ cy.get('.react-grid-layout')
+ .first()
+ .within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ //Opens the drop down
+ cy.getDataTest('type-dropdown').click();
+ });
+ });
+ // Selects the Table option
+ cy.get('[id^="react-select-5-option"]').contains(/Table/).should('be.visible').click({ force: true });
+ cy.get('.react-grid-layout .MuiGrid-root:eq(1) #type input[name="Type"]').should('have.value', 'Table');
+
+ //Removes text in cypher editor and types new query
+ cy.get('.react-grid-layout')
+ .first()
+ .within(() => {
+ //Finds the 2nd card
+ cy.get('.MuiGrid-root')
+ .eq(1)
+ .within(() => {
+ //Replaces default query with new query
+ cy.get('.ndl-cypher-editor div[role="textbox"]').clear().type(tableCypherQuery);
+ cy.get('button[aria-label="run"]').click();
+ });
+ });
+ });
+});
diff --git a/cypress/fixtures/cypher_queries.js b/cypress/fixtures/cypher_queries.js
index 28b893723..4f009b7ae 100644
--- a/cypress/fixtures/cypher_queries.js
+++ b/cypress/fixtures/cypher_queries.js
@@ -12,6 +12,7 @@ export const sunburstChartCypherQuery =
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 formCypherQuery = 'MATCH (n:Movie) WHERE n.title = $neodash_movie_title SET n.rating = 92';
export const iFrameText = 'https://www.wikipedia.org/';
export const markdownText = '# Hello';
export const loadDashboardURL =
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 119ab03f7..e486473bc 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -23,3 +23,62 @@
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+Cypress.Commands.add('getDataTest', (dataTestSelector) => {
+ return cy.get(`[data-test="${dataTestSelector}"]`);
+ });
+
+ /**
+ * Function to interact with a specific element and execute additional custom commands.
+ * @param {Function} customAction - A callback function containing custom Cypress commands.
+ */
+
+ // Used to open the 2nd report card and activate 'advanced settings'
+ Cypress.Commands.add('advancedSettings', (customAction) => {
+ cy.get('.react-grid-layout:eq(0) .MuiGrid-root:eq(1)').within(() => {
+ // Opening settings
+ cy.get('button').eq(1).click();
+ // Activating advanced settings
+ cy.get('[role="switch"]').click();
+ cy.wait(200);
+ customAction();
+ });
+ });
+
+ // Used to open 2nd the report card
+ Cypress.Commands.add('openSettings', (customAction) => {
+ cy.get('.react-grid-layout:eq(0) .MuiGrid-root:eq(1)').within(() => {
+ // Opening settings
+ cy.get('button').eq(1).click();
+ cy.wait(200);
+ customAction();
+ });
+ });
+
+ // Needs to be used when already inside scole of a report card
+ Cypress.Commands.add('setDropdownValue', (labelName, setting) => {
+ cy.get('.ndl-dropdown')
+ .contains('label', labelName)
+ .scrollIntoView()
+ .should('be.visible')
+ .click()
+ .type(`${setting}{enter}`);
+ });
+
+ //Used in start_page.cy.js
+ Cypress.Commands.add('checkInitialState', () => {
+ // 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 report');
+ });
+
+ // Creates a card
+ const WAITING_TIME = 20000;
+ Cypress.Commands.add('createCard', () => {
+ // Check the starter cards
+ cy.get('main .react-grid-item button[aria-label="add report"]', { timeout: WAITING_TIME })
+ .should('be.visible')
+ .click();
+ cy.wait(1000);
+ cy.get('main .react-grid-item:eq(2)').should('contain', 'No query specified.');
+ });
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 1a1f09e4f..cd7110303 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,7 +6,7 @@ An external workflow picks up this directory, embeds it into the Neo4j docs, and
```
https://neo4j.com/labs/neodash/{version}
```
-For example: https://neo4j.com/labs/neodash/2.3
+For example: https://neo4j.com/labs/neodash/2.4
## Local Build
To compile and view the documentation locally, navigate to this (`./docs`) folder and run:
diff --git a/docs/antora.yml b/docs/antora.yml
index 34fdc5c69..ac99e6b7f 100644
--- a/docs/antora.yml
+++ b/docs/antora.yml
@@ -1,5 +1,5 @@
name: neodash
-version: 2.3
+version: 2.4
title: NeoDash
start_page: ROOT:index.adoc
nav:
@@ -7,7 +7,7 @@ nav:
asciidoc:
attributes:
- docs-version: 2.3
+ docs-version: 2.4
page-product: NeoDash
page-type: NeoDash Manual
page-canonical-root: /labs
\ No newline at end of file
diff --git a/docs/modules/ROOT/images/createform.png b/docs/modules/ROOT/images/createform.png
new file mode 100644
index 000000000..a139510aa
Binary files /dev/null and b/docs/modules/ROOT/images/createform.png differ
diff --git a/docs/modules/ROOT/images/dashboardaccesscontrol.png b/docs/modules/ROOT/images/dashboardaccesscontrol.png
new file mode 100644
index 000000000..3ca825baf
Binary files /dev/null and b/docs/modules/ROOT/images/dashboardaccesscontrol.png differ
diff --git a/docs/modules/ROOT/images/dashboardnew.png b/docs/modules/ROOT/images/dashboardnew.png
new file mode 100644
index 000000000..203e26d61
Binary files /dev/null and b/docs/modules/ROOT/images/dashboardnew.png differ
diff --git a/docs/modules/ROOT/images/dashboardnewsettings.png b/docs/modules/ROOT/images/dashboardnewsettings.png
new file mode 100644
index 000000000..ec1ccf887
Binary files /dev/null and b/docs/modules/ROOT/images/dashboardnewsettings.png differ
diff --git a/docs/modules/ROOT/images/formbutton.png b/docs/modules/ROOT/images/formbutton.png
new file mode 100644
index 000000000..a8b61b742
Binary files /dev/null and b/docs/modules/ROOT/images/formbutton.png differ
diff --git a/docs/modules/ROOT/images/forms.png b/docs/modules/ROOT/images/forms.png
new file mode 100644
index 000000000..003518b5e
Binary files /dev/null and b/docs/modules/ROOT/images/forms.png differ
diff --git a/docs/modules/ROOT/images/formselector.png b/docs/modules/ROOT/images/formselector.png
new file mode 100644
index 000000000..28423b586
Binary files /dev/null and b/docs/modules/ROOT/images/formselector.png differ
diff --git a/docs/modules/ROOT/images/formsimple.png b/docs/modules/ROOT/images/formsimple.png
new file mode 100644
index 000000000..c1b55a1b9
Binary files /dev/null and b/docs/modules/ROOT/images/formsimple.png differ
diff --git a/docs/modules/ROOT/images/gantt.png b/docs/modules/ROOT/images/gantt.png
new file mode 100644
index 000000000..eae7ae37f
Binary files /dev/null and b/docs/modules/ROOT/images/gantt.png differ
diff --git a/docs/modules/ROOT/images/graph3d.png b/docs/modules/ROOT/images/graph3d.png
new file mode 100644
index 000000000..3e8c8522f
Binary files /dev/null and b/docs/modules/ROOT/images/graph3d.png differ
diff --git a/docs/modules/ROOT/images/graph3dvirtual.png b/docs/modules/ROOT/images/graph3dvirtual.png
new file mode 100644
index 000000000..3347041c4
Binary files /dev/null and b/docs/modules/ROOT/images/graph3dvirtual.png differ
diff --git a/docs/modules/ROOT/images/llm-examples.png b/docs/modules/ROOT/images/llm-examples.png
new file mode 100644
index 000000000..f4649ad6c
Binary files /dev/null and b/docs/modules/ROOT/images/llm-examples.png differ
diff --git a/docs/modules/ROOT/images/rolelabelmodal.png b/docs/modules/ROOT/images/rolelabelmodal.png
new file mode 100644
index 000000000..804afb17a
Binary files /dev/null and b/docs/modules/ROOT/images/rolelabelmodal.png differ
diff --git a/docs/modules/ROOT/images/rolesmenu.png b/docs/modules/ROOT/images/rolesmenu.png
new file mode 100644
index 000000000..4f948ba22
Binary files /dev/null and b/docs/modules/ROOT/images/rolesmenu.png differ
diff --git a/docs/modules/ROOT/images/select-multiple-table.png b/docs/modules/ROOT/images/select-multiple-table.png
new file mode 100644
index 000000000..b154cd20d
Binary files /dev/null and b/docs/modules/ROOT/images/select-multiple-table.png differ
diff --git a/docs/modules/ROOT/images/select-single-table.png b/docs/modules/ROOT/images/select-single-table.png
new file mode 100644
index 000000000..f6ded431d
Binary files /dev/null and b/docs/modules/ROOT/images/select-single-table.png differ
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index f1633a425..f6984cda2 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -9,6 +9,7 @@
*** xref:user-guide/reports/bar-chart.adoc[Bar Chart]
*** xref:user-guide/reports/pie-chart.adoc[Pie Chart]
*** xref:user-guide/reports/line-chart.adoc[Line Chart]
+*** xref:user-guide/reports/graph3d.adoc[3D Graph]
*** xref:user-guide/reports/sunburst.adoc[Sunburst]
*** xref:user-guide/reports/circle-packing.adoc[Circle Packing]
*** xref:user-guide/reports/choropleth.adoc[Choropleth]
@@ -16,11 +17,13 @@
*** xref:user-guide/reports/treemap.adoc[Treemap]
*** xref:user-guide/reports/radar.adoc[Radar Chart]
*** xref:user-guide/reports/sankey.adoc[Sankey Chart]
+*** xref:user-guide/reports/gantt.adoc[Gantt Chart]
*** xref:user-guide/reports/map.adoc[Map]
*** xref:user-guide/reports/single-value.adoc[Single Value]
*** xref:user-guide/reports/gauge-chart.adoc[Gauge Chart]
*** xref:user-guide/reports/raw-json.adoc[Raw JSON]
*** xref:user-guide/reports/parameter-select.adoc[Parameter Select]
+*** xref:user-guide/reports/form.adoc[Form]
*** xref:user-guide/reports/iframe.adoc[iFrame]
*** xref:user-guide/reports/markdown.adoc[Markdown]
** xref:user-guide/publishing.adoc[Publishing]
@@ -29,7 +32,9 @@
*** xref:user-guide/extensions/advanced-visualizations.adoc[Advanced Visualizations]
*** xref:user-guide/extensions/rule-based-styling.adoc[Rule-Based Styling]
*** xref:user-guide/extensions/report-actions.adoc[Report Actions]
-*** xref:user-guide/extensions/natural-language-queries.adoc[Natural Language Queries]
+*** xref:user-guide/extensions/natural-language-queries.adoc[Text2Cypher - Natural Language Queries]
+*** xref:user-guide/extensions/forms.adoc[Forms]
+*** xref:user-guide/extensions/access-control-management.adoc[Access Control Management]
** xref:user-guide/faq.adoc[FAQ]
* xref:developer-guide/index.adoc[Developer Guide]
** xref:developer-guide/build-and-run.adoc[Build & Run]
diff --git a/docs/modules/ROOT/pages/developer-guide/configuration.adoc b/docs/modules/ROOT/pages/developer-guide/configuration.adoc
index a56946281..604ed1298 100644
--- a/docs/modules/ROOT/pages/developer-guide/configuration.adoc
+++ b/docs/modules/ROOT/pages/developer-guide/configuration.adoc
@@ -25,7 +25,14 @@ will look like this:
"standaloneDatabase": "neo4j",
"standaloneDashboardName": "My Dashboard",
"standaloneDashboardDatabase": "dashboards",
- "standaloneDashboardURL": ""
+ "standaloneDashboardURL": "",
+ "standaloneAllowLoad": false,
+ "standaloneLoadFromOtherDatabases": false,
+ "standaloneMultiDatabase": false,
+ "standaloneDatabaseList": "neo4j"
+ "loggingMode": "0",
+ "loggingDatabase": "logs",
+ "customHeader": "",
}
....
@@ -35,7 +42,7 @@ will look like this:
|===
|Name |Type |Default Value |Description
|ssoEnabled |boolean |false |If enabled, lets users connect to Neo4j
-using SSO. This requires the app to be running in standalone mode, and a
+using SSO. This requires a
valid ssoDiscoveryUrl to be set.
|ssoProviders |List |[] |When using multiple SSO providers on the database, you can configure the list of providers (by id) to be used on Neodash. If empty, all providers will be displayed.
@@ -87,6 +94,52 @@ use multiple databases.
inside Neo4j and would like to run a standalone mode deployment with a
dashboard from a URL, set this parameter to the complete URL pointing to
the dashboard JSON.
+
+|standaloneAllowLoad |boolean |false |If set to yes the "Load Dashboard"
+button will be enabled in standalone mode, allowing users to load
+additional dashboards from Neo4J. This parameter is false by default
+_unless you are using Neo4j Enterprise Edition_, which lets you use multiple
+databases.
+*NOTE*: when Load is enabled in standalone mode, only Database is available
+as a source, not file.
+
+|standaloneLoadFromOtherDatabases |boolean |false |If _standaloneAllowLoad_ is
+set to true, this parmeter enables or not users to load dashboards from
+other databases than the one deifned in _standaloneDashboardDatabase_. If
+_standaloneAllowLoad_ is set to false this parameters has no effect.
+
+|standaloneMultiDatabase |boolean |false |If this parameter set to true, the
+standalone configuration will ignore the _standaloneDatabase_ parameter and
+allow users to choose which database to connect to in the login screen, among
+the ones provided in _standaloneDatabaseList_, with a dropdown list. This
+parameter is false by default _unless you are using Neo4j Enterprise Edition_,
+which lets you use multiple databases.
+
+|standaloneDatabaseList |string |neo4j |If _standaloneMultiDatabase_ is
+set to true, this parmeter must contain a comma separated list of database
+names that will be displayed as options in the Database dropdown at user
+login (e.g. 'neo4j,database1,database2' will populate the database dropdown
+with the values 'neo4j','database1' and 'database2' in the connection screen).
+If _standaloneMultiDatabase_ is set to false this parameters has no effect.
+
+|loggingMode |string |none |Determines whether neodash should create any
+user activity logs. possible values include: `0` (no log is created),
+`1` (user login are tracked), `2` (tracks when a specific dashboard is
+accessed/loaded or saved by a user*).
+
+⚠️ Logs are created in Neo4J DB using the current user credentials
+(or standaloneUsername if configured); write access to the log database
+must be granted to enble any user to create logs.
+
+⚠️ * Load/Save from/to file are not logged (only from/to Database)
+
+|loggingDatabase |string |neo4j |When loggingMode is set to anything
+else than '0', the database to use for logging. Log records (nodes)
+will be created in this database.
+
+|customHeader |string |none |When set the dashboard header will display
+the prameter value as a fixed string, otherwise it will display the host
+and port of current connection.
|===
== Configuring SSO
@@ -129,7 +182,7 @@ be enabled by changing the `standalone` config parameter:
* If standalone mode is `false`, all other configuration parameters are
ignored. NeoDash will run in Editor mode, and require a manual sign-in.
* If standalone mode is `true`, NeoDash will read all configuration
-parameters. A *fixed dashboard* will be auto-loaded, and no changes to
+parameters. A *predefined dashboard* will be auto-loaded, and no changes to
the dashboard can be made. There are two types of valid standalone
deployments:
** A standalone deployment that *reads the fixed dashboard from Neo4j*.
@@ -137,3 +190,15 @@ The `standaloneDashboardName` and `standaloneDashboardDatabase` config
parameters are used to define these.
** A standalone deployment that *reads the fixed dashboard from a URL*.
The `standaloneDashboardURL` config parameter is used to define this.
+
+* Standalone mode can also be configured to allow users load a different
+dashboard after the predefined one is loaded (a `Load Dashboard` button
+will be displayed on the right side of dashboard title).
+The `standaloneAllowLoad` and `standaloneLoadFromOtherDatabases` are used
+to define this.
+* When allowing users to load dashboards dyamically in standalone mode,
+they may also need to connect to different databases, depending on the
+specific dashboard bing loaded. this can be enabled setting
+`standaloneMultiDatabase` to true and providing a comma separated list
+of the allowed database names in the`standaloneDatabaseList` parameter.
+
diff --git a/docs/modules/ROOT/pages/developer-guide/deploy-a-build.adoc b/docs/modules/ROOT/pages/developer-guide/deploy-a-build.adoc
index 72942f3a1..6fadeb4b7 100644
--- a/docs/modules/ROOT/pages/developer-guide/deploy-a-build.adoc
+++ b/docs/modules/ROOT/pages/developer-guide/deploy-a-build.adoc
@@ -37,7 +37,7 @@ Depending on the webserver type and version, this could be different directory.
As an example - to copy the files to an nginx webserver using `scp`:
```bash
-scp neodash-2.3.5 username@host:/usr/share/nginx/html
+scp neodash-2.4.6 username@host:/usr/share/nginx/html
```
NeoDash should now be visible by visiting your (sub)domain in the browser.
diff --git a/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc b/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc
index b40bda419..f752cbad1 100644
--- a/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc
+++ b/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc
@@ -48,6 +48,11 @@ docker run -it --rm -p 5005:5005 \
-e standaloneDatabase="neo4j" \
-e standaloneDashboardName="My Dashboard" \
-e standaloneDashboardDatabase="dashboards" \
+ -e standaloneDashboardURL="dashboards" \
+ -e standaloneAllowLoad=false \
+ -e standaloneLoadFromOtherDatabases=false \
+ -e standaloneMultiDatabase=false \
+ -e standaloneDatabaseList="neo4j" \
neo4jlabs/neodash
....
diff --git a/docs/modules/ROOT/pages/developer-guide/state-management.adoc b/docs/modules/ROOT/pages/developer-guide/state-management.adoc
index 231f98483..bdece9b9e 100644
--- a/docs/modules/ROOT/pages/developer-guide/state-management.adoc
+++ b/docs/modules/ROOT/pages/developer-guide/state-management.adoc
@@ -11,7 +11,7 @@ structure:
{
"dashboard": {
"title": "My Dashboard Name",
- "version": "2.3",
+ "version": "2.4",
"settings": {
"pagenumber": 0,
"editable": true,
@@ -56,7 +56,7 @@ dashboard. Take the following simple dashboard as an example.
{
"dashboard": {
"title": "A Simple Dashboard",
- "version": "2.3",
+ "version": "2.4",
"settings": {
"pagenumber": 0,
"editable": true,
@@ -135,6 +135,13 @@ standalone mode.
"standaloneDatabase": "neo4j",
"standaloneDashboardName": "My Dashboard",
"standaloneDashboardDatabase": "dashboards",
+ "standaloneDashboardURL": "dashboards",
+ "loggingMode": "0",
+ "loggingDatabase": "logging",
+ "standaloneAllowLoad": false,
+ "standaloneLoadFromOtherDatabases ": false,
+ "standaloneMultiDatabase": false,
+ "standaloneDatabaseList": "neo4j",
"notificationIsDismissable": null
}
....
diff --git a/docs/modules/ROOT/pages/developer-guide/style-configuration.adoc b/docs/modules/ROOT/pages/developer-guide/style-configuration.adoc
index e9d5af5c4..b82124d2f 100644
--- a/docs/modules/ROOT/pages/developer-guide/style-configuration.adoc
+++ b/docs/modules/ROOT/pages/developer-guide/style-configuration.adoc
@@ -6,9 +6,15 @@ link:https://cdn.jsdelivr.net/npm/@neo4j-ndl/base@1.4.0/lib/tokens/css/tokens.cs
For a simple (non-Dockerized) deployment, these configuration parameters
can be changed by modifying `dist/style.config.json` after you have built the
-application. When Docker image, these can not be passed as environment
-variables.
+application. When using the NeoDash Docker image, these can be passed as environment
+variables. For example:
+....
+docker run -p 5005:5005 \
+ -e DASHBOARD_HEADER_BRAND_LOGO=https://picsum.photos/500/100 \
+ neo4jlabs/neodash
+....
+
An example configuration for NeoDash
....
diff --git a/docs/modules/ROOT/pages/quickstart.adoc b/docs/modules/ROOT/pages/quickstart.adoc
index fea4b67ad..b128dac71 100644
--- a/docs/modules/ROOT/pages/quickstart.adoc
+++ b/docs/modules/ROOT/pages/quickstart.adoc
@@ -21,7 +21,7 @@ yarn run dev
NeoDash connects to any recent version of the Neo4j database. (Neo4j 4.0
or later). The quickest way to get started is to create a free cloud
-database on https://neo4j.io[Neo4j Aura].
+database on https://console.neo4j.io[Neo4j Aura].
To get started with building your own dashboard, see the Dashboards
page.
diff --git a/docs/modules/ROOT/pages/user-guide/access-control.adoc b/docs/modules/ROOT/pages/user-guide/access-control.adoc
new file mode 100644
index 000000000..03c62dfd1
--- /dev/null
+++ b/docs/modules/ROOT/pages/user-guide/access-control.adoc
@@ -0,0 +1,9 @@
+= Access Control
+
+The Access Control feature in NeoDash is a security measure that allows Users with write access or higher privileges to manage who has access to specific dashboards.
+
+
+== How it Works
+
+Navigate to a specific dashboard and inside the dashboard settings click on the 'Access Control' option in the dashboard sidebar. This opens a modal where users can add labels to the dashboard. These labels are then used to determine which users have access to the dashboard. Please keep in mind that prior to doing this, an administrator needs to provide certain privileges for different user roles for each label in order for this to work. You can read more about how RBAC works in Neo4j by reading the [Neo4j RBAC documentation](https://neo4j.com/docs/operations-manual/current/authentication-authorization/manage-privileges/).
+
diff --git a/docs/modules/ROOT/pages/user-guide/dashboards.adoc b/docs/modules/ROOT/pages/user-guide/dashboards.adoc
index 5392f11eb..dc6b70571 100644
--- a/docs/modules/ROOT/pages/user-guide/dashboards.adoc
+++ b/docs/modules/ROOT/pages/user-guide/dashboards.adoc
@@ -3,7 +3,7 @@
In NeoDash, a dashboard consists of several pages, each of which can
consist of multiple reports.
-image::dashboard2.png[Dashboard]
+image::dashboardnew.png[Dashboard]
As an example: The screenshot above shows a dashboard with three pages:
`Breweries`, `Beer Ratings` and `Styles`. The dashboard title `My
@@ -21,7 +21,7 @@ dashboard or open an existing one (if available). After being connected,
the buttons on the sidebar can be used to save, load or share a
dashboard.
-image::saveloadshare.png[Save/Load/Share Button]
+image::dashboardnewsettings.png[Save/Load/Share Button]
=== Save a Dashboard
@@ -115,6 +115,15 @@ When creating a NeoDash deployment on a production database, it is not
recommended to use the `Share' feature. Rather, set up a dedicated
standalone deployment of NeoDash. See Publishing for more infomation.
+=== Dashboard Access Control
+With this feature, you can manage dashboard access by leveraging the native Neo4j Role-based Access Control (RBAC) functionality. Attach additional labels to the currently selected dashboard node within this window, either by utilizing existing labels in your database or creating new ones, to regulate access permissions.
+
+You can find the Dashboard Access Control feature by clicking on the three dots next to the dashboard name in the sidebar and selecting the "Access Control" option.
+
+> This approach should be used together with restricted privileges on labels, assigned to certain roles. See link:../extensions/access-control-management[Access Control Management] for details.
+
+image::dashboardaccesscontrol.png[Dashboard Access Control]
+
== Dashboard Settings
Settings for the entire dashboard can be accessed by clicking the
diff --git a/docs/modules/ROOT/pages/user-guide/extensions/access-control-management.adoc b/docs/modules/ROOT/pages/user-guide/extensions/access-control-management.adoc
new file mode 100644
index 000000000..941b4dbda
--- /dev/null
+++ b/docs/modules/ROOT/pages/user-guide/extensions/access-control-management.adoc
@@ -0,0 +1,26 @@
+= Access Control Management
+
+This extension lets you manage access control for roles and users, letting you assign users to roles as well as controlling which node labels can be read by a user.
+
+This extension is only visible to users with the role of "Administrator" or "Super User". Enabling this extension will allow the admin user to manage the labels of the roles in the database and then attach them to the users.
+
+
+== Using the Extension ==
+If you have logged in to Neodash as an admin user, you will be able to enable the extension in the "Extensions" menu. Clicking on this extension will give the user a new button next to the settings button in the dashboard header. If the user click on this button, a menu will appear with all the roles in the database.
+
+image::rolesmenu.png[Role menu]
+
+The user can then click on any role and a window will appear with the role's context:
+
+* User list - This is a list of users from your database. You can select multiple users from the list and the role will be added to all the selected users.
+
+* Allow list - This is a list of labels that the role will be granted to read. You can select multiple labels from the list or if you want every label to be granted, you can select "*" from the list. (Requires a database to be selected)
+
+* Deny list - This is a list of labels that the role will be denied to read. You can select multiple labels from the list or if you want every label to be denied, you can select "*" from the list. (Requires a database to be selected)
+
+
+Finally when the admin user clicks on the "Save" button, the role will be updated in the database and the labels will be granted or denied to the users that were selected for the specific role and database.
+
+image::rolelabelmodal.png[Role modal]
+
+> Universal (Cross-database) `GRANT` and `DENY` privileges are not supported by this extension. Privileges must be added on a database-specific level. See the Neo4j https://neo4j.com/docs/operations-manual/current/authentication-authorization/privileges-reads/[documentation on read privileges] for more information.
diff --git a/docs/modules/ROOT/pages/user-guide/extensions/advanced-visualizations.adoc b/docs/modules/ROOT/pages/user-guide/extensions/advanced-visualizations.adoc
index c3c7be5a2..fb02c5a94 100644
--- a/docs/modules/ROOT/pages/user-guide/extensions/advanced-visualizations.adoc
+++ b/docs/modules/ROOT/pages/user-guide/extensions/advanced-visualizations.adoc
@@ -5,12 +5,13 @@ For specific use-cases, these visualizations may convey information that a simpl
To use advanced visualizations, enable them in the **Extensions Window**. This makes them selectable inside reports, as well as add examples to the Example window.
The following visualizations are part of this extension:
-
+- A link:../../reports/graph3d[3D Graph] to visualize a graph in three dimensions.
- A link:../../reports/sankey[Sankey Chart] to visualize flows.
- Three charts to plot hierarchical data (link:../../reports/sunburst[Sunburst], link:../../reports/circle-packing[Circle Packing], link:../../reports/treemap[Treemap])
-- A link:../../reports/gauge-chart[Gauge Chart] to show percentages
+- A link:../../reports/gauge-chart[Gauge Chart] to show percentages.
- An link:../../reports/choropleth[Choropleth] to visualize numeric, country-data.
- An link:../../reports/areamap[Area Map] to show an interactive world map, annotated with numeric country / region values.
+- A link:../../reports/gantt[Gantt] chart to visualize dependencies between tasks.
- A link:../../reports/radar[Radar Chart] to create a radial view of multiple categoric values.
image::advanced-visualizations.png[Advanced Visualizations]
diff --git a/docs/modules/ROOT/pages/user-guide/extensions/forms.adoc b/docs/modules/ROOT/pages/user-guide/extensions/forms.adoc
new file mode 100644
index 000000000..4bce11d4d
--- /dev/null
+++ b/docs/modules/ROOT/pages/user-guide/extensions/forms.adoc
@@ -0,0 +1,13 @@
+= Forms
+
+The 'forms' extension lets you combine different parameter selectors to update / modify your graph data.
+Update queries are predefined by the dashboard builder, and the user is limited to specifying the parameters for the query only.
+
+See link:../../reports/form[Form] on how to create, configure, and use forms.
+
+> Keep in mind that data-altering forms require your Neo4j user to have **write-access** to the graph. Make sure you give access to a select group of power-users only.
+
+
+
+
+image::forms.png[Forms]
diff --git a/docs/modules/ROOT/pages/user-guide/extensions/index.adoc b/docs/modules/ROOT/pages/user-guide/extensions/index.adoc
index 349acd77f..f267315ff 100644
--- a/docs/modules/ROOT/pages/user-guide/extensions/index.adoc
+++ b/docs/modules/ROOT/pages/user-guide/extensions/index.adoc
@@ -17,7 +17,9 @@ The currently available extensions in NeoDash are:
- link:advanced-visualizations[Advanced Visualizations]
- link:rule-based-styling[Rule-based Styling]
- link:report-actions[Report Actions]
-- link:natural-language-queries[Natural Language Queries]
+- link:natural-language-queries[Text2Cypher - Natural Language Queries]
+- link:forms[Forms]
+- link:access-control-management[Access Control Management]
== Types of Extensions
diff --git a/docs/modules/ROOT/pages/user-guide/extensions/natural-language-queries.adoc b/docs/modules/ROOT/pages/user-guide/extensions/natural-language-queries.adoc
index 2698497d5..e7f9ea059 100644
--- a/docs/modules/ROOT/pages/user-guide/extensions/natural-language-queries.adoc
+++ b/docs/modules/ROOT/pages/user-guide/extensions/natural-language-queries.adoc
@@ -1,25 +1,23 @@
-= Natural Language Query Generation in NeoDash!
+= Text2Cypher - Natural Language Queries
Use natural language to generate Cypher queries in NeoDash. Connect to an LLM through an API, and let NeoDash use your database schema + the report types to generate queries automatically.
-== Natural Language Queries
-Natural Language Queries feature allows users to interact with NeoDash using natural language to generate Cypher queries for querying Neo4j graph databases.
+== How it works
+This extension feature allows users to interact with NeoDash using natural language to generate Cypher queries for querying Neo4j graph databases.
This integration leverages Language Models (LLMs) to interpret user inputs and generate Cypher queries based on the provided schema definition.
== Configuration
To enable Natural Language Queries in NeoDash, follow these configuration steps:
1. Open NeoDash and navigate to the "Extensions" section in the left sidebar.
-2. Locate the "Natural Language Queries" extension and click on it to activate it.
-3. Once activated, a new button will appear in the sidebar(see image below). Click on the button to open the configuration window.
+2. Locate the "Text2Cypher" extension and click on it to activate it.
+3. Once activated, a new button will appear on top of the screen, with a red exclamation mark (⚠️). Click this button.
4. In the configuration window, you will be prompted to provide the necessary information to connect to the Language Model (LLM). Enter the model provider, API key, deployment url if needed by the model provider, and select the desired model to use.
5. After providing the required information, click on the "Start Querying" button to finalize the configuration.
-image::extensionbutton.png[Extension Button enables Natural Language Queries button in the sidebar]
-
image::llmconfiguration.png[Configuration settings for the Natural Language Queries extension]
== Usage
-Once the Natural Language Queries extension is configured, you can start using it in your NeoDash reports. Here's how:
+Once the extension is configured, you can start using it in your NeoDash reports. Here's how:
1. Open the Report settings for the desired report.
2. In the report settings, you will find a toggle switch located above the editor. This switch allows you to toggle between Cypher and English languages.
@@ -33,7 +31,21 @@ This allows you to review and modify the generated Cypher query before execution
image::englisheditor.png[Example of the English editor in NeoDash]
-=== Here is how it works behind the scenes:
+== Improving Accuracy with Custom Prompting
+To boost the accuracy of the language model, you can provide your own example queries to be fed into the prompt.
+Specifying queries specific to your data model & use-cases can significantly improve the quality of Text2Cypher translations.
+
+To access the model examples screen, open up the settings for the extensions.
+After specifying the provider and model, click the "Tweak Prompts" button on the bottom-left of the window.
+This leads you to the example interface:
+
+image::llm-examples.png[Custom Examples for your prompt]
+
+In this interface, you can specify one or more examples that are sent to the language model.
+An example consists of both a Cypher query, and a natural language equivalent of that query.
+You can create as many examples as you want, but keeping them close to your user queries will yield best results.
+
+== Underlying Functionality
* Retrieve the Schema: The system prompts at the beginning of the interaction to retrieve the database schema. This ensures that the generated queries adhere to the provided schema and available relationship types and properties.
* Prompting in English: Once the schema is retrieved, you can start prompting your queries in plain English. NeoDash, powered by the LLM, will interpret your English query and generate the corresponding Cypher query based on the provided schema.
diff --git a/docs/modules/ROOT/pages/user-guide/reports/bar-chart.adoc b/docs/modules/ROOT/pages/user-guide/reports/bar-chart.adoc
index 832e15950..56a8773c1 100644
--- a/docs/modules/ROOT/pages/user-guide/reports/bar-chart.adoc
+++ b/docs/modules/ROOT/pages/user-guide/reports/bar-chart.adoc
@@ -39,9 +39,7 @@ image::barstacked.png[Basic Table]
|Show Legend |on/off |off |If enabled, shows a legend at the top right
of the visualization.
-|Grouping |on/off |off |If enabled, lets users specify a third, grouping
-field. This is used to distinguish between different groups in the
-stacked bar chart.
+|Custom Dimensions |on/off |off |If enabled, the chart will no longer autofit to the size of the report card. If width extends beyond the report card, a scroll bar will be introduced to explore the chart horizontally.
|Value Scale |List |linear |When set to symlog, uses a Symmetric
logarithmic scale instead of the default linear scale.
@@ -98,7 +96,16 @@ label on top of the visualization (if enabled).
|Auto-run query |on/off |on |when activated automatically runs the query
when the report is displayed. When set to `off', the query is displayed
and will need to be executed manually.
+
|Report Description |markdown text | | When specified, adds another button the report header that opens a pop-up. This pop-up contains the rendered markdown from this setting.
+
+|Bar Width |number |10 |*Only active when 'Custom Dimensions' is on.* The width of each bar. Increasing the bar width will increase the width of the chart. This setting will have the largest influence on the width of the chart.
+
+|Expand Height For Legend |on/off |off |Useful for when the legend has many labels. When enabled the chart height will adjust to the number of rows returned by the query and therefore will prevent legend labels being cut off.
+
+|Inner Padding |number |0 |When specified, will add padding between any grouped elements.
+
+|Legend Position |Vertical/Horizontal |Vertical |Will dictate whether the lagend is displayed vertically on the right hand side of the chart or horizontally on the bottom of the chart.
|===
== Rule-Based Styling
diff --git a/docs/modules/ROOT/pages/user-guide/reports/form.adoc b/docs/modules/ROOT/pages/user-guide/reports/form.adoc
new file mode 100644
index 000000000..d7817f171
--- /dev/null
+++ b/docs/modules/ROOT/pages/user-guide/reports/form.adoc
@@ -0,0 +1,75 @@
+= Form
+
+A form is a special type of report that lets users run predefined, parameterized queries.
+A single form can consist of:
+
+- Zero or more link:../parameter-select[parameter selectors].
+- A button that triggers submitting the form.
+
+When creating a form, you write the Cypher query that is called when the submit button is clicked.
+This query can then use the parameters specified as input. The image below provides an example of a form. On the left, the settings used to define the form, on the right, the final form as visible to the user.
+
+image::createform.png[Complex Form]
+
+
+== Examples
+
+=== Simple Button
+
+A form without parameters is a button that runs a specified query.
+One or more buttons can be used to perform simple operations in the graph.
+Below is an example of a simple button form. On submitting, the following query is executed:
+
+[source,cypher]
+----
+MERGE (c:Counter)
+SET c.count = c.count+1
+----
+
+image::formbutton.png[Button Form]
+
+=== A Parameter and a Button
+To create a form with dynamic input, use both a parameter and a button.
+Below is an example of a form that deletes nodes from the graph. On submit, the following query is executed:
+
+[source,cypher]
+----
+MATCH (p:Person)
+WHERE p.name = $neodash_person_name
+DETACH DELETE p
+----
+
+image::formsimple.png[Simple Form]
+
+=== Parameters Only
+
+By hiding the submit button, a form can also be used as a space-efficient way to embed multiple parameter selectors.
+Disable `Has Submit Button` in the report's advanced settings, and add two or more parameter selectors to the form.
+
+image::formselector.png[Parameter-only Form]
+
+
+== Advanced Settings
+
+[width="100%",cols="19%,17%,26%,38%",options="header",]
+|===
+|Name |Type |Default Value |Description
+
+|Form Button Text |text |Submit |Text displayed on the button that submits the form.
+
+|Reset Button Text | text |Reset Form |Text displayed on the button that resets the form to data entry mode.
+
+|Confirmation Message | multiline text |Form submitted. |Text displayed to the user after the form is submitted successfully.
+
+|Clear parameters after submit |on/off |on | Clears all dashboard parameters in the form after submitting.
+
+|Has Submit Button |on/off |on | When enabled, lets the user submit the form with a button. Disabling turns the form into parameters-only mode.
+
+|Has Reset Button |on/off |on |When enabled, lets the user reset the form to enter more data.
+
+|Has Submit Message |on/off |on |When enabled, the user to a seperate screen after submitting the form. Else, always stay in data-entry mode.
+
+|Report Description |markdown text | | When specified, adds another button the report header that opens a pop-up. This pop-up contains the rendered markdown from this setting.
+
+
+|===
diff --git a/docs/modules/ROOT/pages/user-guide/reports/gantt.adoc b/docs/modules/ROOT/pages/user-guide/reports/gantt.adoc
new file mode 100644
index 000000000..c54567aa5
--- /dev/null
+++ b/docs/modules/ROOT/pages/user-guide/reports/gantt.adoc
@@ -0,0 +1,81 @@
+= Gantt Chart
+
+link:../../extensions/advanced-visualizations[label:Advanced Visualization[]]
+
+A Gantt chart can be used to visualize tasks on a timeline, as well as their dependencies.
+The NeoDash Gantt chart views your tasks are nodes in the graph, and your relationships are dependencies between them.
+
+To use the Sankey chart, nodes must have at least three properties on them:
+
+- A `startDate`
+- An `endDate`
+- A `title`
+
+In addition, different types of task dependencies can be visualized. The dependency must be stored as a property on a relationship, and can be one of four values:
+
+- `**SE**`: The dependency is from the **S**tart of the origin task, to the **E**nd of the next task.
+- `**SS**`: The dependency is from the **S**tart of the origin task, to the **S**start of the next task.
+- `**ES**`: The dependency is from the **E**nd of the origin task, to the **S**start of the next task.
+- `**EE**`: The dependency is from the **E**nd of the origin task, to the **E**nd of the next task.
+
+== Examples
+
+=== Gantt Chart
+Return nodes and relationships to be visualized in the chart.
+It is mandatory to specify the three node properties (start date, end date and title) in the report's advanced settings.
+
+[source,cypher]
+----
+MATCH (a:Activity)-[r:FOLLOWS]->(a2:Activity)
+RETURN a, r, a2
+----
+
+image::gantt.png[Gantt Chart]
+
+
+== Advanced Settings
+
+[width="100%",cols="15%,2%,6%,77%",options="header",]
+|===
+|Name |Type |Default Value |Description
+
+| Bar Color | string | '#a3a3ff' | Default color for the task bars (with no style rules applied.)
+
+| Task Label Property | string | 'activityName' | Node property to display on the task bar.
+
+| Task Start Date Property | string | 'startDate' | Node property to use as a start date for the task.
+
+| Task End Date Property | string | 'endDate' | Node property to use as an end date for the task.
+
+| Task Ordering Property | string | (auto) | Custom ordering of the tasks. Defaults to use the start date property.
+
+| Dependency Type Property | string | 'rel_type' | The relationship property that stores the dependency type. Property values must be one of `['SS', 'SE', 'ES', 'EE']`
+
+| View mode | string | 'auto' | Zoom level of the chart. one of `['auto', 'Half Day', 'Day', 'Week', 'Month', 'Year']`.
+
+|Margin Left (px) |number |24 |The margin in pixels on the left side of
+the visualization.
+
+|Margin Right (px) |number |24 |The margin in pixels on the right side
+of the visualization.
+
+|Margin Top (px) |number |24 |The margin in pixels on the top side of
+the visualization.
+
+|Margin Bottom (px) |number |40 |The margin in pixels on the bottom side
+of the visualization.
+
+|Auto-run query |on/off |on |when activated automatically runs the query
+when the report is displayed. When set to `off', the query is displayed
+and will need to be executed manually.
+
+|Report Description |markdown text | | When specified, adds another button the report header that opens a pop-up. This pop-up contains the rendered markdown from this setting.
+|===
+
+
+== Rule-Based Styling
+
+Using the link:../#_rule_based_styling[Rule-Based Styling] menu, the
+following style rules can be applied to the Gantt chart:
+
+- The color of a task bar.
diff --git a/docs/modules/ROOT/pages/user-guide/reports/graph.adoc b/docs/modules/ROOT/pages/user-guide/reports/graph.adoc
index 9d4f67c38..4d18523dd 100644
--- a/docs/modules/ROOT/pages/user-guide/reports/graph.adoc
+++ b/docs/modules/ROOT/pages/user-guide/reports/graph.adoc
@@ -26,7 +26,7 @@ RETURN p, a, m
image::graph.png[Basic Graph]
-== Virtual Graph (apoc is required)
+== Virtual Graph
....
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person)
@@ -97,10 +97,13 @@ If 0, no arrow will be drawn.
|Background Color |Text |#fafafa |The background color of the
visualization.
-|Layout (experimental) |List |force-directed |Use this to switch from
-the main (force-directed) layout to one of the two experimental layouts
-(tree/radial). For the experimental layouts, make sure your graph is a
-DAG (directed acyclic graph).
+|Layout (experimental) |List |force-directed |tree-top-down |tree-bottom-up |tree-left-right |tree-right-left |radial | Use this to switch from
+the main (force-directed) layout to one of the experimental layouts
+(tree, radial). For the experimental layouts, make sure
+your graph is a DAG (directed acyclic graph).
+
+| Graph Depth Separation | Number | 30 | Specify the level distance for the tree layout.
+This setting controls the separation between different levels in the tree hierarchy. Adjusting this value impacts the overall spacing of the tree layout in your graph visualization.
|Enable graph exploration |on/off |on |Enables basic exploration functionality for the graph. Exploration can be done by right clicking on a node, and choosing 'Expand' to choose a type to traverse. Data is retrieved real-time and not cached in the visualization.
@@ -127,6 +130,8 @@ that the graph can be explored further. Dynamic Dashboard Parameters
|Hide Selections |on/off |off |If enabled, hides the property selector
(footer of the visualization).
+|Override no data message |Text |Query returned no data. |Override the message displayed to the user when their query returns no data.
+
|Auto-run query |on/off |on |when activated automatically runs the query
when the report is displayed. When set to `off', the query is displayed
and will need to be executed manually.
diff --git a/docs/modules/ROOT/pages/user-guide/reports/graph3d.adoc b/docs/modules/ROOT/pages/user-guide/reports/graph3d.adoc
new file mode 100644
index 000000000..31d2ef6a2
--- /dev/null
+++ b/docs/modules/ROOT/pages/user-guide/reports/graph3d.adoc
@@ -0,0 +1,137 @@
+= 3D Graph
+
+link:../../extensions/advanced-visualizations[label:Advanced Visualization[]]
+
+The 3D graph report extends the default graph visualization with another dimension.
+It supports most of the features & customizations for the regular (2D) graph, including rule-based styling and report actions.
+Users can explore the 3D graph by zooming and panning through 3D space.
+
+
+== Examples
+
+=== Basic Graph
+
+....
+MATCH (p:Person)-[a:ACTED_IN]->(m:Movie)
+WHERE m.title = 'The Matrix'
+RETURN p, a, m
+....
+
+image::graph3d.png[Basic 3D Graph]
+
+== Virtual Graph
+
+....
+MATCH (p:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person)
+WHERE m.title = "The Matrix"
+RETURN p, p2, apoc.create.vRelationship(p, "KNOWS", {}, p2)
+....
+
+image::graph3dvirtual.png[Virtual 3D Graph]
+
+== Advanced Settings
+
+[width="100%",cols="12%,2%,3%,83%",options="header",]
+|===
+|Name |Type |Default Value |Description
+|Node Color Scheme |List |neodash |The color scheme to use for the node
+labels. Colors are assigned automatically (consequitevely) to the
+different labels returned by the Cypher query.
+
+|Node Label Color |Text |black |The color of the labels drawn on the
+nodes.
+
+|Node Label Font Size |Number |3.5 |Size of the labels drawn on the
+nodes.
+
+|Node Size |Number |2 |Default size of a node in the graph
+visualization. This size is applied if no custom size styling is defined
+and no Rule-Based styling is active.
+
+|Node Size Property |Text |size |Optionally, the name of the node
+property to map to the node size. This lets you define sizes on a
+node-specific level, if you have a property that directly maps to the
+numeric size value.
+
+|Node Color Property |Text |color |Optionally, the name of the node
+property to map to the node color. This lets you define colors on a
+node-specific level, if you have a property that directly maps to the
+HTML color value.
+
+|Relationship Color |Text |#a0a0a0 |The color used for drawing the
+relationship arrows in the visualization.
+
+|Relationship Width |Text |1 |The (default) width of the relationship
+arrows in the visualization.
+
+|Relationship Label Color |Text |#a0a0a0 |The color of the labels
+(relationship type) drawn next to the relationship arrows.
+
+|Relationship Label Font Size |Text |2.75 |The font size of the labels
+(relationship type) drawn next to the relationship arrows.
+
+|Relationship Color Property |Text |color |Optionally, the name of the
+relationship property to map to the arrow color. This lets you define
+colors on a relationship-specific level, if you have a property that
+directly maps to the HTML color value.
+
+|Relationship Width Property |Text |width |Optionally, the name of the
+relationship property to map to the arrow width. This lets you define
+widths on a relationship-specific level, if you have a property that
+directly maps to the width value.
+
+|Animated Particles on Relationships |on/off |off |If enabled, draw
+relationships with animated particles on them, moving in the direction
+of the relationship.
+
+|Arrow head size |Number |3 |Use this to set the length of the arrow head, size is adjusted automatically.
+If 0, no arrow will be drawn.
+
+|Background Color |Text |#fafafa |The background color of the
+visualization.
+
+|Layout (experimental) |List |force-directed |tree-top-down |tree-bottom-up |tree-left-right |tree-right-left |radial | Use this to switch from
+the main (force-directed) layout to one of the experimental layouts
+(tree, radial). For the experimental layouts, make sure
+your graph is a DAG (directed acyclic graph).
+
+| Graph Depth Separation | Number | 30 | Specify the level distance for the tree layout.
+This setting controls the separation between different levels in the tree hierarchy. Adjusting this value impacts the overall spacing of the tree layout in your graph visualization.
+
+|Enable graph exploration |on/off |on |Enables basic exploration functionality for the graph. Exploration can be done by right clicking on a node, and choosing 'Expand' to choose a type to traverse. Data is retrieved real-time and not cached in the visualization.
+
+|Enable graph editing |on/off |off |Enables editing of nodes and relationships in the graph from the right-click context menu. In addition, lets users create new relationships with existing types/property keys as present in the database.
+
+|Show pop-up on Hover |on/off |on |if enabled, shows a pop-up when a
+user hovers over one of the nodes/relationships in the visualization.
+The pop-up contains the label and properties of the node/relationship.
+
+|Show properties on Click |on/off |on |if enabled, opens up a window
+when a user clicks on one of the nodes/relationships in the
+visualization. The window contains the label and properties of the
+node/relationship.
+
+|Drilldown Link |Text (URL) |(no value) |Specifying a URL here will
+display a floating button on the top right of the visualization. This
+button can be used to drilldown into a different tool (e.g. Bloom) so
+that the graph can be explored further. Dynamic Dashboard Parameters
+(e.g. $neodash_person_name) can be used in these links as well.
+
+|Hide Selections |on/off |off |If enabled, hides the property selector
+(footer of the visualization).
+
+|Override no data message |Text |Query returned no data. |Override the message displayed to the user when their query returns no data.
+
+|Auto-run query |on/off |on |when activated automatically runs the query
+when the report is displayed. When set to `off', the query is displayed
+and will need to be executed manually.
+|Report Description |markdown text | | When specified, adds another button the report header that opens a pop-up. This pop-up contains the rendered markdown from this setting.
+|===
+
+== Rule-Based Styling
+
+Using the link:../#_rule_based_styling[Rule-Based Styling] menu, the
+following style rules can be applied to the graph:
+
+- The background color of a node.
+- The label color of a node.
diff --git a/docs/modules/ROOT/pages/user-guide/reports/index.adoc b/docs/modules/ROOT/pages/user-guide/reports/index.adoc
index c1fc84820..da44ed606 100644
--- a/docs/modules/ROOT/pages/user-guide/reports/index.adoc
+++ b/docs/modules/ROOT/pages/user-guide/reports/index.adoc
@@ -93,6 +93,7 @@ pages:
- link:bar-chart[Bar Chart]
- link:pie-chart[Pie Chart]
- link:line-chart[Line Chart]
+- link:graph3d[3D Graph]
- link:sunburst[Sunburst]
- link:circle-packing[Circle Packing]
- link:treemap[Treemap]
@@ -102,8 +103,10 @@ pages:
- link:areamap[Area Map]
- link:single-value[Single Value]
- link:sankey[Sankey Chart]
+- link:gantt[Gantt Chart]
- link:gauge[Gauge Chart]
- link:raw-json[Raw JSON]
- link:parameter-select[Parameter Select]
+- link:form[Form]
- link:iframe[iFrame]
- link:markdown[Markdown]
diff --git a/docs/modules/ROOT/pages/user-guide/reports/table.adoc b/docs/modules/ROOT/pages/user-guide/reports/table.adoc
index 2c8fe5559..8bb11cb14 100644
--- a/docs/modules/ROOT/pages/user-guide/reports/table.adoc
+++ b/docs/modules/ROOT/pages/user-guide/reports/table.adoc
@@ -54,6 +54,8 @@ set to ``[2, 1, 1]''.
the bottom right of the table footer. This button lets the user download
the complete set of table results (all pages) as a CSV file.
+|Override no data message |Text |Query returned no data. |Override the message displayed to the user when their query returns no data.
+
|Auto-run query |on/off |on |when activated automatically runs the query
when the report is displayed. When set to `off', the query is displayed
and will need to be executed manually.
@@ -71,3 +73,22 @@ following style rules can be applied to the table:
- The text color of a single cell in the table.
If a column is hidden (header prefixed with __ double underscore), it can still be used as an entry point for a styling rule.
+
+== Report Actions
+
+With the link:../../extensions/report-actions[Report Actions] extension, tables can be turned into interactive components that set parameters.
+Two flavours of report actions for tables exist:
+
+=== 1. Select a value from a row
+Adding a **Cell Click** action to a table column, turns the values in that row into clickable buttons.
+When the user clicks on the button, a predefined parameter is set to one of the columns in that row.
+
+image::select-single-table.png[Select a value from a table to be used as a parameter]
+
+=== 2. Select multiple from a row
+Adding a **Row Clicked** action to a table prepends each row with a checkbox.
+The user can then check one or more boxes to update a dashboard parameter.
+
+> Keep in mind that regardless if one or more values are selected, the type of the dashboard parameter is a list of values. The queries using the parameter must ensure that the list type is handled correctly.
+
+image::select-multiple-table.png[Select multiple values to be used as a parameter]
\ No newline at end of file
diff --git a/docs/preview.yml b/docs/preview.yml
index ed1a8f02e..038eb8bfb 100644
--- a/docs/preview.yml
+++ b/docs/preview.yml
@@ -12,7 +12,7 @@ content:
- '!**/README.adoc'
ui:
bundle:
- url: https://s3-eu-west-1.amazonaws.com/static-content.neo4j.com/build/ui-bundle.zip
+ url: https://static-content.neo4j.com/build/ui-bundle-latest.zip
snapshot: true
urls:
html_extension_style: indexify
diff --git a/docs/server.js b/docs/server.js
index 33333b02b..1f6da7130 100644
--- a/docs/server.js
+++ b/docs/server.js
@@ -1,7 +1,7 @@
const express = require('express')
const app = express()
-const version = "2.3"
+const version = "2.4"
app.use(express.static('./build/site'))
app.get('/', (req, res) => res.redirect('neodash/' + version))
app.listen(8000, () => console.log('📘 http://localhost:8000'))
\ No newline at end of file
diff --git a/gallery/package.json b/gallery/package.json
index c1a93960e..e4988c384 100644
--- a/gallery/package.json
+++ b/gallery/package.json
@@ -33,7 +33,7 @@
},
"devDependencies": {
"autoprefixer": "^10.4.12",
- "postcss": "^8.4.17",
+ "postcss": "^8.4.31",
"tailwindcss": "^3.1.8"
}
}
diff --git a/gallery/tsconfig.json b/gallery/tsconfig.json
index a273b0cfc..9d379a3c4 100644
--- a/gallery/tsconfig.json
+++ b/gallery/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@@ -20,7 +16,5 @@
"noEmit": true,
"jsx": "react-jsx"
},
- "include": [
- "src"
- ]
+ "include": ["src"]
}
diff --git a/gallery/yarn.lock b/gallery/yarn.lock
index 2d61aaaee..53f7e1fe2 100644
--- a/gallery/yarn.lock
+++ b/gallery/yarn.lock
@@ -26,6 +26,14 @@
dependencies:
"@babel/highlight" "^7.18.6"
+"@babel/code-frame@^7.22.13":
+ version "7.22.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+ integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
+ dependencies:
+ "@babel/highlight" "^7.22.13"
+ chalk "^2.4.2"
+
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f"
@@ -71,6 +79,16 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
+"@babel/generator@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
+ integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
+ dependencies:
+ "@babel/types" "^7.23.0"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@@ -136,6 +154,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+"@babel/helper-environment-visitor@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
+
"@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@@ -151,6 +174,14 @@
"@babel/template" "^7.20.7"
"@babel/types" "^7.21.0"
+"@babel/helper-function-name@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+ integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.23.0"
+
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@@ -158,6 +189,13 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5"
@@ -241,16 +279,33 @@
dependencies:
"@babel/types" "^7.18.6"
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-string-parser@^7.19.4":
version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
@@ -284,11 +339,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.22.13":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
+"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+ integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@@ -1049,19 +1118,28 @@
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
-"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2":
- version "7.21.4"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
- integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
+"@babel/template@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+ integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
- "@babel/code-frame" "^7.21.4"
- "@babel/generator" "^7.21.4"
- "@babel/helper-environment-visitor" "^7.18.9"
- "@babel/helper-function-name" "^7.21.0"
- "@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.21.4"
- "@babel/types" "^7.21.4"
+ "@babel/code-frame" "^7.22.13"
+ "@babel/parser" "^7.22.15"
+ "@babel/types" "^7.22.15"
+
+"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2":
+ version "7.23.2"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
+ integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.23.0"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.23.0"
+ "@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
@@ -1074,6 +1152,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
+"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
+ integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -3068,7 +3155,7 @@ case-sensitive-paths-webpack-plugin@^2.4.0:
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==
-chalk@^2.0.0, chalk@^2.4.1:
+chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -4590,9 +4677,9 @@ flatted@^3.1.0:
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.0.0:
- version "1.15.2"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
- integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+ version "1.15.4"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf"
+ integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==
for-each@^0.3.3:
version "0.3.3"
@@ -7487,10 +7574,10 @@ postcss@^7.0.35:
picocolors "^0.2.1"
source-map "^0.6.1"
-postcss@^8.0.9, postcss@^8.3.5, postcss@^8.4.17, postcss@^8.4.19, postcss@^8.4.4:
- version "8.4.23"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
- integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
+postcss@^8.0.9, postcss@^8.3.5, postcss@^8.4.19, postcss@^8.4.31, postcss@^8.4.4:
+ version "8.4.31"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
+ integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
diff --git a/package.json b/package.json
index b430ce577..f97d7fa5a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "neodash",
- "version": "2.3.5",
+ "version": "2.4.6",
"description": "NeoDash - Neo4j Dashboard Builder",
"neo4jDesktop": {
"apiVersion": "^1.2.0"
@@ -58,13 +58,14 @@
"@nivo/sunburst": "^0.83.0",
"@nivo/treemap": "^0.83.0",
"@sentry/react": "^7.57.0",
- "@sentry/webpack-plugin": "^2.5.0",
+ "@sentry/webpack-plugin": "^2.7.1",
"babel-runtime": "^6.26.0",
"chroma-js": "^2.4.2",
"classnames": "^2.3.1",
"d3-scale-chromatic": "^3.0.0",
"dayjs": "^1.11.7",
"dom-to-image": "^2.6.0",
+ "dompurify": "^3.1.0",
"leaflet": "^1.7.1",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
@@ -81,6 +82,7 @@
"react-cool-dimensions": "^2.0.7",
"react-dom": "^17.0.2",
"react-force-graph-2d": "^1.23.8",
+ "react-force-graph-3d": "^1.24.1",
"react-gauge-chart": "^0.4.1",
"react-grid-layout": "^1.3.4",
"react-leaflet": "^3.2.5",
@@ -97,6 +99,8 @@
"remark-gfm": "^3.0.1",
"reselect": "^4.1.8",
"tailwindcss": "^3.3.2",
+ "three": "^0.159.0",
+ "three-spritetext": "^1.8.1",
"use-neo4j": "^0.3.13",
"yaml": "^2.2.1"
},
@@ -117,6 +121,7 @@
"@typescript-eslint/parser": "^5.42.0",
"babel-loader": "^8.2.3",
"babel-plugin-istanbul": "^6.1.1",
+ "circular-dependency-plugin": "^5.2.2",
"css-loader": "^3.6.0",
"cypress": "^12.17.4",
"eslint": "^8.26.0",
@@ -136,7 +141,6 @@
"typescript": "^4.8.4",
"webpack": "^5.77.0",
"webpack-cli": "^4.9.1",
- "webpack-dev-server": "^4.7.3",
- "circular-dependency-plugin": "^5.2.2"
+ "webpack-dev-server": "^4.7.3"
}
}
diff --git a/public/accesscontrol2.jpg b/public/accesscontrol2.jpg
new file mode 100644
index 000000000..be948e1b2
Binary files /dev/null and b/public/accesscontrol2.jpg differ
diff --git a/public/config.json b/public/config.json
index 17754d56b..4e8d475a2 100644
--- a/public/config.json
+++ b/public/config.json
@@ -9,5 +9,12 @@
"standaloneDatabase": "neo4j",
"standaloneDashboardName": "My Dashboard",
"standaloneDashboardDatabase": "dashboards",
- "standaloneDashboardURL": ""
+ "standaloneDashboardURL": "",
+ "standaloneAllowLoad": false,
+ "standaloneLoadFromOtherDatabases": false,
+ "standaloneMultiDatabase": false,
+ "standaloneDatabaseList": "neo4j",
+ "loggingMode": "0",
+ "loggingDatabase": "logs",
+ "customHeader": ""
}
diff --git a/public/form.png b/public/form.png
new file mode 100644
index 000000000..a139510aa
Binary files /dev/null and b/public/form.png differ
diff --git a/public/style.css b/public/style.css
index 16ccc2fb5..63cf9fca1 100644
--- a/public/style.css
+++ b/public/style.css
@@ -86,12 +86,17 @@
border: none !important;
}
+.MuiDataGrid-footerContainer > div {
+ margin-top: -40px;
+}
+
.MuiChip-root:before {
border: none !important;
}
.react-resizable-handle {
- bottom: 1px !important;
+ bottom: 4px !important;
+ right: -2px !important;
opacity: 0.5;
color: rgb(222, 222, 222);
}
@@ -120,13 +125,13 @@
margin-top: -10px;
}
-.MuiDataGrid-virtualScroller {
- overflow-y: hidden !important;
+.MuiDataGrid-panel {
+ translate: 0px -152%;
}
.MuiCard-root {
- box-shadow: 0 0 #0000,0 0 #0000,var(--tw-shadow) !important;
- box-shadow: var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow) !important;
+ box-shadow: 0 0 #0000, 0 0 #0000, var(--tw-shadow) !important;
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) !important;
}
.white-text {
@@ -157,7 +162,7 @@
white-space: pre;
background-position: 0px -11px;
padding-right: 0px !important;
- margin-top: 0px !important;
+ margin-top: 0px;
padding-left: 30px;
padding-top: 0px !important;
padding-bottom: 0px !important;
@@ -165,7 +170,6 @@
overflow-x: scroll !important;
overflow-y: scroll;
min-height: 116px;
- /*height: 200px !important;*/
}
.textinput-linenumbers::-webkit-scrollbar {
@@ -180,7 +184,6 @@
text-align: center;
}
-
.card-view.expanded {
position: absolute;
top: 0;
@@ -209,25 +212,24 @@
}
.card-view .MuiTablePagination-root {
- margin-top: -40px;
-
+ margin-top: 0px;
}
@keyframes pulse {
- 0% {
- transform: scale(0.95);
- box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
- }
+ 0% {
+ transform: scale(0.95);
+ box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
+ }
- 70% {
- transform: scale(1);
- box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
- }
+ 70% {
+ transform: scale(1);
+ box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
+ }
- 100% {
- transform: scale(0.95);
- box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
- }
+ 100% {
+ transform: scale(0.95);
+ box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
+ }
}
/* Workaround for Needle not handling menu placement of dropdowns on modals */
@@ -235,4 +237,18 @@
z-index: 99 !important;
position: absolute;
}
-/* End workaround */
\ No newline at end of file
+
+/* End workaround */
+
+/* Workaround for cleaning the Gantt chart UI */
+.gantt-wrapper > div > div:first-child > div:first-child > div:first-child > div > div:not(:first-child) {
+ display: none;
+}
+.gantt-wrapper > div > div > div > div > div > div > div:not(:first-child) {
+ display: none;
+}
+/* End Gantt chart workaround */
+
+.markdown-widget a {
+ text-decoration: underline;
+}
diff --git a/release-notes.md b/release-notes.md
index 905e4f77f..d8b929222 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -1,13 +1,7 @@
-## NeoDash 2.3.5
-This is a bugfix / stability release directly following 2.3.4.
+## NeoDash 2.4.6
+This is a minor release containing a few critical fixes and some extra style customizations:
-Improvements:
-- Fixed issue where orphan relationships prevented graph charts from working ([@BennuFire](https://github.com/BennuFire), [#586](https://github.com/neo4j-labs/neodash/pull/586))
-- Fix issue where only one style rule was used a time on tables. ([@BennuFire](https://github.com/BennuFire), [#632](https://github.com/neo4j-labs/neodash/pull/632))
-- Added information about source and target on Graph Chart information modal . ([@BennuFire](https://github.com/BennuFire), [#627](https://github.com/neo4j-labs/neodash/pull/627)) Based on [@brahmprakashMishra](https://github.com/brahmprakashMishra) PR
-- Fixed issue where bar charts where displaying black bars instead of scheme colors. ([@BennuFire](https://github.com/BennuFire), [#626](https://github.com/neo4j-labs/neodash/pull/626))
-- Added right subpath replacement on shared links redirection while in self deployments. ([@m-o-n-i-s-h](https://github.com/m-o-n-i-s-h), [#618](https://github.com/neo4j-labs/neodash/pull/618))
-- Dark theme tweaks. ([@BennuFire](https://github.com/BennuFire), [#585](https://github.com/neo4j-labs/neodash/pull/585))
-- Fixed parameter selector search where numbers were not found and sporadically displayed with decimal points. ([@BennuFire](https://github.com/BennuFire), [#633](https://github.com/neo4j-labs/neodash/pull/633))
-- Added a configuration in order to list sso providers to be used whenever a database has more than one configured. ([@BennuFire](https://github.com/BennuFire), [#624](https://github.com/neo4j-labs/neodash/pull/624))
-- Added 'Ignore undefined parameters' advanced setting support for optional parameters on a query. Now queries will assume a null value instead of returning the error 'Parameter not defined'. ([@BennuFire](https://github.com/BennuFire), [#625](https://github.com/neo4j-labs/neodash/pull/625))
\ No newline at end of file
+- Fix bad text wrapping for arrays in tables ([868](https://github.com/neo4j-labs/neodash/pull/868)).
+- Make wrapping in table optional, disabled by default ([872](https://github.com/neo4j-labs/neodash/pull/872)).
+- Fixed issues where cross database dashboard sharing always reverted back to the default database ([873](https://github.com/neo4j-labs/neodash/pull/873)).
+- Added option to define style config using environment variables for the Docker image ([876](https://github.com/neo4j-labs/neodash/pull/876)).
\ No newline at end of file
diff --git a/scripts/config-entrypoint.sh b/scripts/config-entrypoint.sh
index 2eb04129c..3e2a063ec 100644
--- a/scripts/config-entrypoint.sh
+++ b/scripts/config-entrypoint.sh
@@ -7,7 +7,7 @@ echo " \
\"ssoEnabled\": ${ssoEnabled:=false}, \
\"ssoProviders\": ${ssoProviders:=[]}, \
\"ssoDiscoveryUrl\": \"${ssoDiscoveryUrl:='https://example.com'}\", \
- \"standalone\": "${standalone:=false}", \
+ \"standalone\": ${standalone:=false}, \
\"standaloneProtocol\": \"${standaloneProtocol:='neo4j+s'}\", \
\"standaloneHost\": \"${standaloneHost:='test.databases.neo4j.io'}\", \
\"standalonePort\": ${standalonePort:=7687}, \
@@ -16,5 +16,28 @@ echo " \
\"standalonePassword\": \"${standalonePassword:=}\", \
\"standaloneDashboardName\": \"${standaloneDashboardName:='My Dashboard'}\", \
\"standaloneDashboardDatabase\": \"${standaloneDashboardDatabase:='neo4j'}\", \
- \"standaloneDashboardURL\": \"${standaloneDashboardURL:=}\" \
- }" > /usr/share/nginx/html/config.json
+ \"standaloneDashboardURL\": \"${standaloneDashboardURL:=}\", \
+ \"standaloneAllowLoad\": ${standaloneAllowLoad:=false}, \
+ \"standaloneLoadFromOtherDatabases\": ${standaloneLoadFromOtherDatabases:=false}, \
+ \"standaloneMultiDatabase\": ${standaloneMultiDatabase:=false}, \
+ \"standaloneDatabaseList\": \"${standaloneDatabaseList:='neo4j'}\", \
+ \"standalonePasswordWarningHidden\": ${standalonePasswordWarningHidden:=false}, \
+ \"loggingMode\": \"${loggingMode:='0'}\", \
+ \"loggingDatabase\": \"${loggingDatabase:='logs'}\", \
+ \"customHeader\": \"${customHeader:=}\" \
+ }" > /usr/share/nginx/html/config.json
+
+echo " \
+ { \
+ \"DASHBOARD_HEADER_BRAND_LOGO\": \"${DASHBOARD_HEADER_BRAND_LOGO:=}\", \
+ \"DASHBOARD_HEADER_COLOR\" : \"${DASHBOARD_HEADER_COLOR:=}\", \
+ \"DASHBOARD_HEADER_BUTTON_COLOR\" : \"${DASHBOARD_HEADER_BUTTON_COLOR:=}\", \
+ \"DASHBOARD_HEADER_TITLE_COLOR\" : \"${DASHBOARD_HEADER_TITLE_COLOR:=}\", \
+ \"DASHBOARD_PAGE_LIST_COLOR\" : \"${DASHBOARD_PAGE_LIST_COLOR:=}\", \
+ \"DASHBOARD_PAGE_LIST_ACTIVE_COLOR\": \"${DASHBOARD_PAGE_LIST_ACTIVE_COLOR:=}\", \
+ \"style\": { \
+ \"--palette-light-neutral-bg-weak\": \"${STYLE_PALETTE_LIGHT_NEUTRAL_BG_WEAK:=}\" \
+ } \
+}" > /usr/share/nginx/html/style.config.json
+
+
diff --git a/src/application/Application.tsx b/src/application/Application.tsx
index cd8d9759a..66fd90c6e 100644
--- a/src/application/Application.tsx
+++ b/src/application/Application.tsx
@@ -15,6 +15,7 @@ import {
applicationGetStandaloneSettings,
applicationGetSsoSettings,
applicationHasReportHelpModalOpen,
+ applicationIsStandalone,
} from '../application/ApplicationSelectors';
import {
createConnectionThunk,
@@ -70,6 +71,7 @@ const Application = ({
connectionModalOpen,
reportHelpModalOpen,
ssoSettings,
+ standalone,
standaloneSettings,
aboutModalOpen,
loadDashboard,
@@ -140,7 +142,8 @@ const Application = ({
({
shareDetails: applicationGetShareDetails(state),
oldDashboard: applicationGetOldDashboard(state),
ssoSettings: applicationGetSsoSettings(state),
+ standalone: applicationIsStandalone(state),
standaloneSettings: applicationGetStandaloneSettings(state),
connectionModalOpen: applicationHasConnectionModalOpen(state),
aboutModalOpen: applicationHasAboutModalOpen(state),
@@ -214,9 +219,9 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setConnected(false));
dispatch(createConnectionFromDesktopIntegrationThunk());
},
- loadDashboard: (text) => {
+ loadDashboard: (uuid, text) => {
dispatch(clearNotification());
- dispatch(loadDashboardThunk(text));
+ dispatch(loadDashboardThunk(uuid, text));
},
resetDashboard: () => dispatch(resetDashboardState()),
clearOldDashboard: () => dispatch(setOldDashboard(null)),
diff --git a/src/application/ApplicationActions.ts b/src/application/ApplicationActions.ts
index 4f85a17c5..b8fe3d063 100644
--- a/src/application/ApplicationActions.ts
+++ b/src/application/ApplicationActions.ts
@@ -20,6 +20,12 @@ export const setConnected = (connected: boolean) => ({
payload: { connected },
});
+export const SET_DRAFT = 'APPLICATION/SET_DRAFT';
+export const setDraft = (draft: boolean) => ({
+ type: SET_DRAFT,
+ payload: { draft },
+});
+
export const SET_CONNECTION_MODAL_OPEN = 'APPLICATION/SET_CONNECTION_MODAL_OPEN';
export const setConnectionModalOpen = (open: boolean) => ({
type: SET_CONNECTION_MODAL_OPEN,
@@ -143,7 +149,12 @@ export const setStandaloneEnabled = (
standaloneDashboardDatabase: string,
standaloneDashboardURL: string,
standaloneUsername: string,
- standalonePassword: string
+ standalonePassword: string,
+ standalonePasswordWarningHidden: boolean,
+ standaloneAllowLoad: boolean,
+ standaloneLoadFromOtherDatabases: boolean,
+ standaloneMultiDatabase: boolean,
+ standaloneDatabaseList: string
) => ({
type: SET_STANDALONE_ENABLED,
payload: {
@@ -157,12 +168,17 @@ export const setStandaloneEnabled = (
standaloneDashboardURL,
standaloneUsername,
standalonePassword,
+ standalonePasswordWarningHidden,
+ standaloneAllowLoad,
+ standaloneLoadFromOtherDatabases,
+ standaloneMultiDatabase,
+ standaloneDatabaseList,
},
});
export const SET_STANDALONE_MODE = 'APPLICATION/SET_STANDALONE_MODE';
export const setStandaloneMode = (standalone: boolean) => ({
- type: SET_STANDALONE_ENABLED,
+ type: SET_STANDALONE_MODE,
payload: { standalone },
});
@@ -213,3 +229,9 @@ export const setParametersToLoadAfterConnecting = (parameters: any) => ({
type: SET_PARAMETERS_TO_LOAD_AFTER_CONNECTING,
payload: { parameters },
});
+
+export const SET_CUSTOM_HEADER = 'APPLICATION/SET_CUSTOM_HEADER';
+export const setCustomHeader = (customHeader: any) => ({
+ type: SET_CUSTOM_HEADER,
+ payload: { customHeader },
+});
diff --git a/src/application/ApplicationReducer.ts b/src/application/ApplicationReducer.ts
index 5e01776c7..e662ade63 100644
--- a/src/application/ApplicationReducer.ts
+++ b/src/application/ApplicationReducer.ts
@@ -2,7 +2,17 @@
* Reducers define changes to the application state when a given action is taken.
*/
+import {
+ HARD_RESET_CARD_SETTINGS,
+ TOGGLE_CARD_SETTINGS,
+ UPDATE_ALL_SELECTIONS,
+ UPDATE_FIELDS,
+ UPDATE_SCHEMA,
+ UPDATE_SELECTION,
+} from '../card/CardActions';
import { DEFAULT_NEO4J_URL } from '../config/ApplicationConfig';
+import { SET_DASHBOARD, SET_DASHBOARD_UUID } from '../dashboard/DashboardActions';
+import { UPDATE_DASHBOARD_SETTING } from '../settings/SettingsActions';
import {
CLEAR_DESKTOP_CONNECTION_PROPERTIES,
CLEAR_NOTIFICATION,
@@ -15,6 +25,7 @@ import {
SET_CONNECTION_PROPERTIES,
SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING,
SET_DESKTOP_CONNECTION_PROPERTIES,
+ SET_DRAFT,
SET_OLD_DASHBOARD,
SET_PARAMETERS_TO_LOAD_AFTER_CONNECTING,
SET_REPORT_HELP_MODAL_OPEN,
@@ -27,7 +38,15 @@ import {
SET_STANDALONE_MODE,
SET_WAIT_FOR_SSO,
SET_WELCOME_SCREEN_OPEN,
+ SET_CUSTOM_HEADER,
} from './ApplicationActions';
+import {
+ SET_LOGGING_MODE,
+ SET_LOGGING_DATABASE,
+ SET_LOG_ERROR_NOTIFICATION,
+ LOGGING_PREFIX,
+} from './logging/LoggingActions';
+import { loggingReducer, LOGGING_INITIAL_STATE } from './logging/LoggingReducer';
const update = (state, mutations) => Object.assign({}, state, mutations);
@@ -36,6 +55,7 @@ const initialState = {
notificationMessage: null,
connectionModalOpen: false,
welcomeScreenOpen: true,
+ draft: false,
aboutModalOpen: false,
connection: {
protocol: 'neo4j',
@@ -51,13 +71,41 @@ const initialState = {
dashboardToLoadAfterConnecting: null,
waitForSSO: false,
standalone: false,
+ logging: LOGGING_INITIAL_STATE,
};
export const applicationReducer = (state = initialState, action: { type: any; payload: any }) => {
const { type, payload } = action;
+ // This is a special application-level flag used to determine whether the dashboard needs to be saved to the database.
+ if (action.type.startsWith('DASHBOARD/') || action.type.startsWith('PAGE/') || action.type.startsWith('CARD/')) {
+ // if anything changes EXCEPT for the selected page, we flag that we are drafting a dashboard.
+ const NON_TRANSFORMATIVE_ACTIONS = [
+ UPDATE_DASHBOARD_SETTING,
+ UPDATE_SCHEMA,
+ HARD_RESET_CARD_SETTINGS,
+ SET_DASHBOARD,
+ UPDATE_ALL_SELECTIONS,
+ UPDATE_FIELDS,
+ SET_DASHBOARD_UUID,
+ TOGGLE_CARD_SETTINGS,
+ UPDATE_SELECTION,
+ ];
+
+ if (!state.draft && !NON_TRANSFORMATIVE_ACTIONS.includes(type)) {
+ state = update(state, { draft: true });
+ return state;
+ }
+ }
+
+ // Ignore any non-application actions.
if (!action.type.startsWith('APPLICATION/')) {
return state;
}
+ if (action.type.startsWith(LOGGING_PREFIX)) {
+ const enrichedPayload = update(payload, { logging: state.logging });
+ const enrichedAction = { type, payload: enrichedPayload };
+ return { ...state, logging: loggingReducer(state.logging, enrichedAction) };
+ }
// Application state updates are handled here.
switch (type) {
@@ -75,6 +123,11 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
state = update(state, { connected: connected });
return state;
}
+ case SET_DRAFT: {
+ const { draft } = payload;
+ state = update(state, { draft: draft });
+ return state;
+ }
case SET_CONNECTION_MODAL_OPEN: {
const { open } = payload;
state = update(state, { connectionModalOpen: open });
@@ -82,9 +135,6 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
}
case SET_ABOUT_MODAL_OPEN: {
const { open } = payload;
- if (!open) {
- console.log('');
- }
state = update(state, { aboutModalOpen: open });
return state;
}
@@ -108,6 +158,21 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
state = update(state, { standalone: standalone });
return state;
}
+ case SET_LOGGING_MODE: {
+ const { loggingMode } = payload;
+ state = update(state, { loggingMode: loggingMode });
+ return state;
+ }
+ case SET_LOGGING_DATABASE: {
+ const { loggingDatabase } = payload;
+ state = update(state, { loggingDatabase: loggingDatabase });
+ return state;
+ }
+ case SET_LOG_ERROR_NOTIFICATION: {
+ const { logErrorNotification } = payload;
+ state = update(state, { logErrorNotification: logErrorNotification });
+ return state;
+ }
case SET_SSO_ENABLED: {
const { enabled, discoveryUrl } = payload;
state = update(state, { ssoEnabled: enabled, ssoDiscoveryUrl: discoveryUrl });
@@ -140,6 +205,11 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
standaloneDashboardURL,
standaloneUsername,
standalonePassword,
+ standalonePasswordWarningHidden,
+ standaloneAllowLoad,
+ standaloneLoadFromOtherDatabases,
+ standaloneMultiDatabase,
+ standaloneDatabaseList,
} = payload;
state = update(state, {
standalone: standalone,
@@ -152,6 +222,11 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
standaloneDashboardURL: standaloneDashboardURL,
standaloneUsername: standaloneUsername,
standalonePassword: standalonePassword,
+ standalonePasswordWarningHidden: standalonePasswordWarningHidden,
+ standaloneAllowLoad: standaloneAllowLoad,
+ standaloneLoadFromOtherDatabases: standaloneLoadFromOtherDatabases,
+ standaloneMultiDatabase: standaloneMultiDatabase,
+ standaloneDatabaseList: standaloneDatabaseList,
});
return state;
}
@@ -242,6 +317,11 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
});
return state;
}
+ case SET_CUSTOM_HEADER: {
+ const { customHeader } = payload;
+ state = update(state, { customHeader: customHeader });
+ return state;
+ }
default: {
return state;
}
diff --git a/src/application/ApplicationSelectors.ts b/src/application/ApplicationSelectors.ts
index e99758185..4986ff564 100644
--- a/src/application/ApplicationSelectors.ts
+++ b/src/application/ApplicationSelectors.ts
@@ -21,6 +21,10 @@ export const getNotificationTitle = (state: any) => {
return state.application.notificationTitle;
};
+export const dashboardIsDraft = (state: any) => {
+ return state.application.draft;
+};
+
export const applicationIsConnected = (state: any) => {
return state.application.connected;
};
@@ -33,6 +37,10 @@ export const applicationGetConnectionDatabase = (state: any) => {
return state.application.connection.database;
};
+export const applicationGetConnectionUser = (state: any) => {
+ return state.application.connection.username;
+};
+
export const applicationGetShareDetails = (state: any) => {
return state.application.shareDetails;
};
@@ -41,6 +49,10 @@ export const applicationIsStandalone = (state: any) => {
return state.application.standalone;
};
+export const applicationGetLoggingMode = (state: any) => {
+ return state.application.loggingMode;
+};
+
export const applicationHasNeo4jDesktopConnection = (state: any) => {
return state.application.desktopConnection != null;
};
@@ -82,6 +94,11 @@ export const applicationGetStandaloneSettings = (state: any) => {
standaloneDashboardURL: state.application.standaloneDashboardURL,
standaloneUsername: state.application.standaloneUsername,
standalonePassword: state.application.standalonePassword,
+ standalonePasswordWarningHidden: state.application.standalonePasswordWarningHidden,
+ standaloneAllowLoad: state.application.standaloneAllowLoad,
+ standaloneLoadFromOtherDatabases: state.application.standaloneLoadFromOtherDatabases,
+ standaloneMultiDatabase: state.application.standaloneMultiDatabase,
+ standaloneDatabaseList: state.application.standaloneDatabaseList,
};
};
@@ -108,3 +125,7 @@ export const applicationGetDebugState = (state: any) => {
}
return copy;
};
+
+export const applicationGetCustomHeader = (state: any) => {
+ return state.application.customHeader;
+};
diff --git a/src/application/ApplicationThunks.ts b/src/application/ApplicationThunks.ts
index 38590733b..e3ddc8ce0 100644
--- a/src/application/ApplicationThunks.ts
+++ b/src/application/ApplicationThunks.ts
@@ -2,10 +2,11 @@ import { createDriver } from 'use-neo4j';
import { initializeSSO } from '../component/sso/SSOUtils';
import { DEFAULT_SCREEN, Screens } from '../config/ApplicationConfig';
import { setDashboard } from '../dashboard/DashboardActions';
-import { NEODASH_VERSION } from '../dashboard/DashboardReducer';
+import { NEODASH_VERSION, VERSION_TO_MIGRATE } from '../dashboard/DashboardReducer';
import {
+ assignDashboardUuidIfNotPresentThunk,
loadDashboardFromNeo4jByNameThunk,
- loadDashboardFromNeo4jByUUIDThunk,
+ loadDashboardFromNeo4jThunk,
loadDashboardThunk,
upgradeDashboardVersion,
} from '../dashboard/DashboardThunks';
@@ -38,8 +39,15 @@ import {
setWaitForSSO,
setParametersToLoadAfterConnecting,
setReportHelpModalOpen,
+ setDraft,
+ setCustomHeader,
} from './ApplicationActions';
+import { setLoggingMode, setLoggingDatabase, setLogErrorNotification } from './logging/LoggingActions';
import { version } from '../modal/AboutModal';
+import { applicationIsStandalone } from './ApplicationSelectors';
+import { applicationGetLoggingSettings } from './logging/LoggingSelectors';
+import { createLogThunk } from './logging/LoggingThunk';
+import { createUUID } from '../utils/uuid';
/**
* Application Thunks (https://redux.js.org/usage/writing-logic-thunks) handle complex state manipulations.
@@ -57,6 +65,9 @@ import { version } from '../modal/AboutModal';
*/
export const createConnectionThunk =
(protocol, url, port, database, username, password) => (dispatch: any, getState: any) => {
+ const loggingState = getState();
+ const loggingSettings = applicationGetLoggingSettings(loggingState);
+ const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor';
try {
const driver = createDriver(protocol, url, port, username, password, { userAgent: `neodash/v${version}` });
// eslint-disable-next-line no-console
@@ -66,13 +77,48 @@ export const createConnectionThunk =
console.log('Confirming connection was established...');
if (records && records[0] && records[0].error) {
dispatch(createNotificationThunk('Unable to establish connection', records[0].error));
+ if (loggingSettings.loggingMode > '0') {
+ dispatch(
+ createLogThunk(
+ driver,
+ loggingSettings.loggingDatabase,
+ neodashMode,
+ username,
+ 'ERR - connect to DB',
+ database,
+ '',
+ `Error while trying to establish connection to Neo4j DB in ${neodashMode} mode at ${Date(
+ Date.now()
+ ).substring(0, 33)}`
+ )
+ );
+ }
} else if (records && records[0] && records[0].keys[0] == 'connected') {
dispatch(setConnectionProperties(protocol, url, port, database, username, password));
dispatch(setConnectionModalOpen(false));
dispatch(setConnected(true));
+ // An old dashboard (pre-2.3.5) may not always have a UUID. We catch this case here.
+ dispatch(assignDashboardUuidIfNotPresentThunk());
dispatch(updateSessionParameterThunk('session_uri', `${protocol}://${url}:${port}`));
dispatch(updateSessionParameterThunk('session_database', database));
dispatch(updateSessionParameterThunk('session_username', username));
+ if (loggingSettings.loggingMode > '0') {
+ dispatch(
+ createLogThunk(
+ driver,
+ loggingSettings.loggingDatabase,
+ neodashMode,
+ username,
+ 'INF - connect to DB',
+ database,
+ '',
+ `${username} established connection to Neo4j DB in ${neodashMode} mode at ${Date(Date.now()).substring(
+ 0,
+ 33
+ )}`
+ )
+ );
+ }
// If we have remembered to load a specific dashboard after connecting to the database, take care of it here.
const { application } = getState();
if (
@@ -83,11 +129,11 @@ export const createConnectionThunk =
) {
fetch(application.dashboardToLoadAfterConnecting)
.then((response) => response.text())
- .then((data) => dispatch(loadDashboardThunk(data)));
+ .then((data) => dispatch(loadDashboardThunk(createUUID(), data)));
dispatch(setDashboardToLoadAfterConnecting(null));
} else if (application.dashboardToLoadAfterConnecting) {
const setDashboardAfterLoadingFromDatabase = (value) => {
- dispatch(loadDashboardThunk(value));
+ dispatch(loadDashboardThunk(createUUID(), value));
};
// If we specify a dashboard by name, load the latest version of it.
@@ -103,7 +149,7 @@ export const createConnectionThunk =
);
} else {
dispatch(
- loadDashboardFromNeo4jByUUIDThunk(
+ loadDashboardFromNeo4jThunk(
driver,
application.standaloneDashboardDatabase,
application.dashboardToLoadAfterConnecting,
@@ -125,7 +171,7 @@ export const createConnectionThunk =
query,
parameters,
1,
- () => { },
+ () => {},
(records) => validateConnection(records)
);
} catch (e) {
@@ -213,26 +259,45 @@ export const handleSharedDashboardsThunk = () => (dispatch: any) => {
const skipConfirmation = urlParams.get('skipConfirmation') == 'Yes';
const dashboardDatabase = urlParams.get('dashboardDatabase');
+ if (dashboardDatabase) {
+ dispatch(setStandaloneDashboardDatabase(dashboardDatabase));
+ }
+
if (urlParams.get('credentials')) {
+ setWelcomeScreenOpen(false);
const connection = decodeURIComponent(urlParams.get('credentials'));
const protocol = connection.split('://')[0];
const username = connection.split('://')[1].split(':')[0];
const password = connection.split('://')[1].split(':')[1].split('@')[0];
-
const database = connection.split('@')[1].split(':')[0];
const url = connection.split('@')[1].split(':')[1];
const port = connection.split('@')[1].split(':')[2];
- if (url == password) {
- // Special case where a connect link is generated without a password.
- // Here, the format is parsed incorrectly and we open the connection window instead.
-
- dispatch(resetShareDetails());
- dispatch(setConnectionProperties(protocol, url, port, database, username.split('@')[0], ''));
- dispatch(setWelcomeScreenOpen(false));
- dispatch(setConnectionModalOpen(true));
- // window.history.pushState({}, document.title, "/");
- return;
- }
+ // if (url == password) {
+ // // Special case where a connect link is generated without a password.
+ // // Here, the format is parsed incorrectly and we open the connection window instead.
+ // dispatch(setConnectionProperties(protocol, url, port, database, username.split('@')[0], ''));
+ // dispatch(
+ // setShareDetailsFromUrl(
+ // type,
+ // id,
+ // standalone,
+ // protocol,
+ // url,
+ // port,
+ // database,
+ // username.split('@')[0],
+ // '',
+ // dashboardDatabase,
+ // true
+ // )
+ // );
+ // setDashboardToLoadAfterConnecting(id);
+ // window.history.pushState({}, document.title, window.location.pathname);
+ // dispatch(setConnectionModalOpen(true));
+ // dispatch(setWelcomeScreenOpen(false));
+ // // window.history.pushState({}, document.title, "/");
+ // return;
+ // }
dispatch(setConnectionModalOpen(false));
dispatch(
@@ -301,6 +366,8 @@ export const onConfirmLoadSharedDashboardThunk = () => (dispatch: any, getState:
if (shareDetails.dashboardDatabase) {
dispatch(setStandaloneDashboardDatabase(shareDetails.dashboardDatabase));
+ } else if (!state.application.standaloneDashboardDatabase) {
+ // No standalone dashboard database configured, fall back to default
dispatch(setStandaloneDashboardDatabase(shareDetails.database));
}
if (shareDetails.url) {
@@ -351,6 +418,14 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
standaloneDashboardName: 'My Dashboard',
standaloneDashboardDatabase: 'dashboards',
standaloneDashboardURL: '',
+ loggingMode: '0',
+ loggingDatabase: 'logs',
+ logErrorNotification: '3',
+ standaloneAllowLoad: false,
+ standaloneLoadFromOtherDatabases: false,
+ standaloneMultiDatabase: false,
+ standaloneDatabaseList: 'neo4j',
+ customHeader: '',
};
try {
config = await (await fetch('config.json')).json();
@@ -382,6 +457,9 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
dispatch(setSSOProviders(config.ssoProviders));
const { standalone } = config;
+ // if a dashboard database was previously set, remember to use it.
+ const dashboardDatabase = state.application.standaloneDashboardDatabase;
+
dispatch(
setStandaloneEnabled(
standalone,
@@ -390,43 +468,41 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
config.standalonePort,
config.standaloneDatabase,
config.standaloneDashboardName,
- config.standaloneDashboardDatabase,
+ dashboardDatabase || config.standaloneDashboardDatabase,
config.standaloneDashboardURL,
config.standaloneUsername,
- config.standalonePassword
+ config.standalonePassword,
+ config.standalonePasswordWarningHidden,
+ config.standaloneAllowLoad,
+ config.standaloneLoadFromOtherDatabases,
+ config.standaloneMultiDatabase,
+ config.standaloneDatabaseList
)
);
+
+ dispatch(setLoggingMode(config.loggingMode));
+ dispatch(setLoggingDatabase(config.loggingDatabase));
+ dispatch(setLogErrorNotification('3'));
+
dispatch(setConnectionModalOpen(false));
+ dispatch(setCustomHeader(config.customHeader));
+
// Auto-upgrade the dashboard version if an old version is cached.
if (state.dashboard && state.dashboard.version !== NEODASH_VERSION) {
- if (state.dashboard.version == '2.0') {
- const upgradedDashboard = upgradeDashboardVersion(state.dashboard, '2.0', '2.1');
- dispatch(setDashboard(upgradedDashboard));
- dispatch(
- createNotificationThunk(
- 'Successfully upgraded dashboard',
- 'Your old dashboard was migrated to version 2.1. You might need to refresh this page.'
- )
- );
- }
- if (state.dashboard.version == '2.1') {
- const upgradedDashboard = upgradeDashboardVersion(state.dashboard, '2.1', '2.2');
- dispatch(setDashboard(upgradedDashboard));
- dispatch(
- createNotificationThunk(
- 'Successfully upgraded dashboard',
- 'Your old dashboard was migrated to version 2.2. You might need to refresh this page.'
- )
+ // Attempt upgrade if dashboard version is outdated.
+ while (VERSION_TO_MIGRATE[state.dashboard.version]) {
+ const upgradedDashboard = upgradeDashboardVersion(
+ state.dashboard,
+ state.dashboard.version,
+ VERSION_TO_MIGRATE[state.dashboard.version]
);
- }
- if (state.dashboard.version == '2.2') {
- const upgradedDashboard = upgradeDashboardVersion(state.dashboard, '2.2', '2.3');
dispatch(setDashboard(upgradedDashboard));
+ dispatch(setDraft(true));
dispatch(
createNotificationThunk(
'Successfully upgraded dashboard',
- 'Your old dashboard was migrated to version 2.3. You might need to refresh this page.'
+ `Your old dashboard was migrated to version ${upgradedDashboard.version}. You might need to refresh this page and reactivate extensions.`
)
);
}
@@ -554,7 +630,6 @@ export const initializeApplicationAsStandaloneThunk =
(config, paramsToSetAfterConnecting) => (dispatch: any, getState: any) => {
const clearNotificationAfterLoad = true;
const state = getState();
-
// If we are running in standalone mode, auto-set the connection details that are configured.
dispatch(
setConnectionProperties(
@@ -597,4 +672,5 @@ export const initializeApplicationAsStandaloneThunk =
} else {
dispatch(setConnectionModalOpen(true));
}
+ dispatch(handleSharedDashboardsThunk());
};
diff --git a/src/application/logging/LoggingActions.ts b/src/application/logging/LoggingActions.ts
new file mode 100644
index 000000000..bfbf0fc8a
--- /dev/null
+++ b/src/application/logging/LoggingActions.ts
@@ -0,0 +1,19 @@
+export const LOGGING_PREFIX = 'APPLICATION/LOGGING/';
+
+export const SET_LOGGING_MODE = `${LOGGING_PREFIX}/SET_LOGGING_MODE`;
+export const setLoggingMode = (loggingMode: string) => ({
+ type: SET_LOGGING_MODE,
+ payload: { loggingMode },
+});
+
+export const SET_LOGGING_DATABASE = `${LOGGING_PREFIX}/SET_LOGGING_DATABASE`;
+export const setLoggingDatabase = (loggingDatabase: string) => ({
+ type: SET_LOGGING_DATABASE,
+ payload: { loggingDatabase },
+});
+
+export const SET_LOG_ERROR_NOTIFICATION = `${LOGGING_PREFIX}/SET_LOG_ERROR_NOTIFICATION`;
+export const setLogErrorNotification = (logErrorNotification: any) => ({
+ type: SET_LOG_ERROR_NOTIFICATION,
+ payload: { logErrorNotification },
+});
diff --git a/src/application/logging/LoggingReducer.ts b/src/application/logging/LoggingReducer.ts
new file mode 100644
index 000000000..16e257e7b
--- /dev/null
+++ b/src/application/logging/LoggingReducer.ts
@@ -0,0 +1,39 @@
+import { LOGGING_PREFIX, SET_LOGGING_DATABASE, SET_LOGGING_MODE, SET_LOG_ERROR_NOTIFICATION } from './LoggingActions';
+
+const update = (state, mutations) => Object.assign({}, state, mutations);
+
+export const LOGGING_INITIAL_STATE = {
+ loggingMode: '0',
+ logErrorNotification: '3',
+ loggingDatabase: undefined,
+};
+
+export const loggingReducer = (state = LOGGING_INITIAL_STATE, action: { type: any; payload: any }) => {
+ const { type, payload } = action;
+
+ if (!action.type.startsWith(LOGGING_PREFIX)) {
+ return state;
+ }
+
+ // Logging state updates are handled here.
+ switch (type) {
+ case SET_LOGGING_MODE: {
+ const { loggingMode } = payload;
+ state = update(state, { loggingMode: loggingMode });
+ return state;
+ }
+ case SET_LOGGING_DATABASE: {
+ const { loggingDatabase } = payload;
+ state = update(state, { loggingDatabase: loggingDatabase });
+ return state;
+ }
+ case SET_LOG_ERROR_NOTIFICATION: {
+ const { logErrorNotification } = payload;
+ state = update(state, { logErrorNotification: logErrorNotification });
+ return state;
+ }
+ default: {
+ return state;
+ }
+ }
+};
diff --git a/src/application/logging/LoggingSelectors.ts b/src/application/logging/LoggingSelectors.ts
new file mode 100644
index 000000000..c8cba54c4
--- /dev/null
+++ b/src/application/logging/LoggingSelectors.ts
@@ -0,0 +1,6 @@
+/**
+ * Selector function for retrieving logging settings from the application state.
+ * @param state - The application state.
+ * @returns An object with logging settings.
+ */
+export const applicationGetLoggingSettings = (state: any) => state.application.logging;
diff --git a/src/application/logging/LoggingThunk.ts b/src/application/logging/LoggingThunk.ts
new file mode 100644
index 000000000..6bdd662ca
--- /dev/null
+++ b/src/application/logging/LoggingThunk.ts
@@ -0,0 +1,77 @@
+import { createNotificationThunk } from '../../page/PageThunks';
+import { runCypherQuery } from '../../report/ReportQueryRunner';
+import { setLogErrorNotification } from './LoggingActions';
+import { applicationGetLoggingSettings } from './LoggingSelectors';
+import { createUUID } from '../../utils/uuid';
+
+// Thunk to handle log events.
+
+export const createLogThunk =
+ (loggingDriver, loggingDatabase, neodashMode, logUser, logAction, logDatabase, logDashboard = '', logMessage) =>
+ (dispatch: any, getState: any) => {
+ try {
+ const uuid = createUUID();
+ // Generate a cypher query to save the log.
+ const query =
+ 'CREATE (n:_Neodash_Log) SET n.uuid = $uuid, n.user = $user, n.date = datetime(), n.neodash_mode = $neodashMode, n.action = $logAction, n.database = $logDatabase, n.dashboard = $logDashboard, n.message = $logMessage RETURN $uuid as uuid';
+
+ const parameters = {
+ uuid: uuid,
+ user: logUser,
+ logAction: logAction,
+ logDatabase: logDatabase,
+ neodashMode: neodashMode,
+ logDashboard: logDashboard,
+ logMessage: logMessage,
+ };
+ runCypherQuery(
+ loggingDriver,
+ loggingDatabase,
+ query,
+ parameters,
+ 1,
+ () => {},
+ (records) => {
+ if (records && records[0] && records[0]._fields && records[0]._fields[0] && records[0]._fields[0] == uuid) {
+ console.log(`log created: ${uuid}`);
+ } else {
+ // we only show error notification one time
+ const state = getState();
+ const loggingSettings = applicationGetLoggingSettings(state);
+ let LogErrorNotificationNum = Number(loggingSettings.logErrorNotification);
+ console.log(`Error creating log for ${(LogErrorNotificationNum - 4) * -1} times`);
+ if (LogErrorNotificationNum > 0) {
+ dispatch(
+ createNotificationThunk(
+ 'Error creating log',
+ LogErrorNotificationNum > 1
+ ? `Please check logging configuration with your Neodash administrator`
+ : `Please check logging configuration with your Neodash administrator - This message will not be displayed anymore in the current session`
+ )
+ );
+ }
+ LogErrorNotificationNum -= 1;
+ dispatch(setLogErrorNotification(LogErrorNotificationNum.toString()));
+ }
+ }
+ );
+ } catch (e) {
+ // we only show error notification 3 times
+ const state = getState();
+ const loggingSettings = applicationGetLoggingSettings(state);
+ let LogErrorNotificationNum = Number(loggingSettings.logErrorNotification);
+ console.log(`Error creating log for ${(LogErrorNotificationNum - 4) * -1} times`);
+ if (LogErrorNotificationNum > 0) {
+ dispatch(
+ createNotificationThunk(
+ 'Error creating log',
+ LogErrorNotificationNum > 1
+ ? `Please check logging configuration with your Neodash administrator`
+ : `Please check logging configuration with your Neodash administrator - This message will not be displayed anymore in the current session`
+ )
+ );
+ }
+ LogErrorNotificationNum -= 1;
+ dispatch(setLogErrorNotification(LogErrorNotificationNum.toString()));
+ }
+ };
diff --git a/src/card/Card.tsx b/src/card/Card.tsx
index 21f504022..ef10d4782 100644
--- a/src/card/Card.tsx
+++ b/src/card/Card.tsx
@@ -211,7 +211,7 @@ const NeoCard = ({
if (expanded) {
return (
);
}
diff --git a/src/card/settings/CardSettings.tsx b/src/card/settings/CardSettings.tsx
index 583bcf167..85962fe26 100644
--- a/src/card/settings/CardSettings.tsx
+++ b/src/card/settings/CardSettings.tsx
@@ -36,7 +36,7 @@ const NeoCardSettings = ({
expanded,
onToggleCardExpand,
}) => {
- const reportHeight = heightPx - CARD_HEADER_HEIGHT + 24;
+ const reportHeight = heightPx - CARD_HEADER_HEIGHT + 19;
const cardSettingsHeader = (
) : (
diff --git a/src/card/settings/CardSettingsContent.tsx b/src/card/settings/CardSettingsContent.tsx
index 9ea7c06ff..c02f1d339 100644
--- a/src/card/settings/CardSettingsContent.tsx
+++ b/src/card/settings/CardSettingsContent.tsx
@@ -8,7 +8,7 @@ import NeoCodeEditorComponent, {
import { getReportTypes } from '../../extensions/ExtensionUtils';
import { Dropdown } from '@neo4j-ndl/react';
import { EXTENSIONS_CARD_SETTINGS_COMPONENT } from '../../extensions/ExtensionConfig';
-import { update } from '../../utils/ObjectManipulation';
+import { objMerge } from '../../utils/ObjectManipulation';
const NeoCardSettingsContent = ({
pagenumber,
@@ -22,15 +22,15 @@ const NeoCardSettingsContent = ({
onQueryUpdate,
onReportSettingUpdate,
onTypeUpdate,
+ forceRunQuery, // Callback to force close the card settings.
onDatabaseChanged, // When the database related to a report is changed it must be stored in the report state
}) => {
// Ensure that we only trigger a text update event after the user has stopped typing.
const [queryText, setQueryText] = React.useState(query);
- const debouncedQueryUpdate = useCallback(debounce(onQueryUpdate, 250), []);
-
+ const debouncedQueryUpdate = useCallback(debounce(onQueryUpdate, 200), []);
// State to manage the current database entry inside the form
const [databaseText, setDatabaseText] = React.useState(database);
- const debouncedDatabaseUpdate = useCallback(debounce(onDatabaseChanged, 250), []);
+ const debouncedDatabaseUpdate = useCallback(debounce(onDatabaseChanged, 200), []);
useEffect(() => {
// Reset text to the dashboard state when the page gets reorganized.
@@ -67,6 +67,10 @@ const NeoCardSettingsContent = ({
reportId={reportId}
reportType={type}
extensions={extensions}
+ onExecute={() => {
+ onQueryUpdate(queryText);
+ forceRunQuery();
+ }}
cypherQuery={queryText}
updateCypherQuery={updateCypherQuery}
/>
@@ -85,6 +89,10 @@ const NeoCardSettingsContent = ({
value={queryText}
editable={true}
language={report?.inputMode || 'cypher'}
+ onExecute={() => {
+ onQueryUpdate(queryText);
+ forceRunQuery();
+ }}
onChange={(value) => {
updateCypherQuery(value);
}}
@@ -97,6 +105,7 @@ const NeoCardSettingsContent = ({
return (
{
+ onQueryUpdate(queryText);
+ forceRunQuery();
+ }}
/>
) : (