Skip to content

Commit 0e8a982

Browse files
authored
2.0.7 (#73)
## NeoDash 2.0.7 Application functionality: - Added standalone 'dashboard viewer' mode. - Added option to save/load dashboards from other Neo4j databases. Reports/Visualizations: - Fixed bug in creating line charts. - Added support for datetime axis in line charts. - Added auto-locale formatting to number values in single value / table reports. - Added unified renderer for value types. - Updated default font size for single value reports. - Added optional deep-link button for graph visualizations. - Added option to disable auto-running a report, to let users explore the query first. - Minor styling tweaks to the graph views. For Developers: - Added more documentation on extending the app. - New security-vetted docker image available on Docker hub.
1 parent d2c452b commit 0e8a982

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1025
-385
lines changed

README.md

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,75 @@ To build the app for production:
4242
- deploy the contents of the build folder to a web server. You should then be able to run the web app.
4343

4444

45-
### Build Docker image
45+
## Build Docker image
4646
Make sure you have a recent version of `docker` installed.
47-
On a Unix-like system you can run `./tools/docker-build-run_unix.bash` to build the multi-stage NeoDash image & access it with nginx:
47+
On a Unix-like system you can run `./tools/docker-build.bash` to build the multi-stage NeoDash image.
48+
49+
After this, you can run Neo4j in a container. On Unix (Mac/Linux) systems:
4850
```
4951
$ cd tools/
50-
$ ./docker-build-run_unix.bash --port=$YOUR_PORT
52+
$ ./docker-run-unix.bash
5153
```
54+
5255
If you use Windows, you should have installed WSL. In WSL, you can run the script as follows:
5356
```
5457
$ cd tools/
55-
$ ./docker-build-run_windows.bash --port=$YOUR_PORT
58+
$ ./docker-run-windows.bash
5659
```
57-
Then visit localhost with the chosen port in your browser.
60+
Then visit `http://localhost:8080` with the chosen port in your browser.
5861

5962
A pre-built Docker image is available [on DockerHub](https://hub.docker.com/r/nielsdejong/neodash).
6063

64+
## Run in standalone mode
65+
NeoDash can be deployed in a 'standalone mode' for dashboard viewers. This mode will:
66+
- Disable all editing options
67+
- Have a hardcoded Neo4j URL and database name
68+
- Load a dashboard from Neo4j with a fixed name.
69+
70+
The diagram below illustrates how NeoDash standalone mode can be deployed next to a standard 'Editor Mode' instance:
71+
72+
![](doc/standalone-architecture.png)
73+
74+
You can configure an instance to run as standalone by changing the variables in `tools/docker-run-unix.bash`, or, if you're not using docker, directly modifying `public/config.json`. Note that the editor mode is determined at runtime by the React app, and *not* at build time. You therefore do not need to (re-)build a docker image.
75+
6176
## Extending NeoDash
77+
There are two categories of extensions to NeoDash you can build:
78+
- Core Dashboard Functionality
79+
- Custom Reports
80+
81+
The first will require some knowledge about React, Redux, and app internals. Some advanced level knowledge is therefore highly recommended. The second is much simpler, and you should be able to plug in your own visualizations with minimal JS knowledge.
82+
83+
### Core Dashboard Functionality
84+
To extend the core functionality of the app, it helps to be familiar with the following concepts:
85+
- ReactJS
86+
- Redux (State management for React)
87+
- Redux Selectors
88+
- Redux Thunks
89+
90+
The image below contains a high-level overview of the component hierarchy within the application. The following conceptual building blocks are used to create the interface:
91+
- The Application
92+
- The Dashboard
93+
- Modals
94+
- Drawer
95+
- Dashboard Header
96+
- Pages
97+
- Cards
98+
- Card Views
99+
- Card Settings
100+
- Card View Header
101+
- Report
102+
- Card View Footer
103+
- Card Settings Header
104+
- Card Settings Content
105+
- Card Settings Footer
106+
- Charts
107+
108+
![](doc/component-hierarchy.png)
109+
110+
### Custom Reports
62111
As of v2.0, NeoDash is easy to extend with your own visualizations. There are two steps to take to plug in your own charts:
63112

64-
### 1. Create the React component
113+
#### 1. Create the React component
65114
All NeoDash charts implement the interface defined in `src/charts/Chart.tsx`. A custom chart must do the same. the following parameter as passed to your chart from the application:
66115
- `records`: a list of Neo4j Records. This is the raw data returned from the Neo4j driver.
67116
- `settings`: a dictionary of settings as defined under "advanced report settings" for each report. You can use these values to customize your visualization based on user input.
@@ -72,7 +121,7 @@ All NeoDash charts implement the interface defined in `src/charts/Chart.tsx`. A
72121

73122
Make sure that your component renders a React component. your component will be automatically scaled to the size of the report. See the other charts in `src/charts/` for examples.
74123

75-
### 2. Extend the config to make your component selectable
124+
#### 2. Extend the config to make your component selectable
76125

77126
To let users choose your visualization, you must add it to the app's report configuration. This config is located in `src/config/ReportConfig.tsx`, and defined by the dictionary `REPORT_TYPES`.
78127

doc/component-hierarchy.png

718 KB
Loading

doc/standalone-architecture.png

842 KB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "neodash",
3-
"version": "2.0.6",
3+
"version": "2.0.7",
44
"description": "NeoDash - Neo4j Dashboard Builder",
55
"neo4jDesktop": {
66
"apiVersion": "^1.2.0"

public/config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"ssoEnabled": false,
3+
"ssoDiscoveryUrl": "https://example.com",
4+
"standalone": false,
5+
"standaloneProtocol": "neo4j",
6+
"standaloneHost": "localhost",
7+
"standalonePort": "7687",
8+
"standaloneDatabase": "neo4j",
9+
"standaloneDashboardName": "My Dashboard",
10+
"standaloneDashboardDatabase": "dashboards"
11+
}

release-notes.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
## NeoDash 2.0.7
2+
Application functionality:
3+
- Added standalone 'dashboard viewer' mode.
4+
- Added option to save/load dashboards from other Neo4j databases.
5+
6+
Reports/Visualizations:
7+
- Fixed bug in creating line charts.
8+
- Added support for datetime axis in line charts.
9+
- Added auto-locale formatting to number values in single value / table reports.
10+
- Added unified renderer for value types.
11+
- Updated default font size for single value reports.
12+
- Added optional deep-link button for graph visualizations.
13+
- Added option to disable auto-running a report, to let users explore the query first.
14+
- Minor styling tweaks to the graph views.
15+
16+
For Developers:
17+
- Added more documentation on extending the app.
18+
- New security-vetted docker image available on Docker hub.
19+
20+
121
## NeoDash 2.0.6
222
Major version updates to all internal dependencies.
323
NeoDash 2.0.6 uses Node 17+, react 17+ and recent versions of all visualization libraries.

src/application/Application.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import NeoNotificationModal from '../modal/NotificationModal';
1010
import NeoWelcomeScreenModal from '../modal/WelcomeScreenModal';
1111
import { removeReportRequest } from '../page/PageThunks';
1212
import { connect } from 'react-redux';
13-
import { applicationGetConnection, applicationGetShareDetails, applicationGetOldDashboard, applicationHasNeo4jDesktopConnection, applicationHasAboutModalOpen, applicationHasCachedDashboard, applicationHasConnectionModalOpen, applicationIsConnected, applicationHasWelcomeScreenOpen, applicationGetDebugState } from '../application/ApplicationSelectors';
14-
import { createConnectionThunk, createConnectionFromDesktopIntegrationThunk, setDatabaseFromNeo4jDesktopIntegrationThunk, handleSharedDashboardsThunk, onConfirmLoadSharedDashboardThunk } from '../application/ApplicationThunks';
13+
import { applicationGetConnection, applicationGetShareDetails, applicationGetOldDashboard, applicationHasNeo4jDesktopConnection, applicationHasAboutModalOpen, applicationHasCachedDashboard, applicationHasConnectionModalOpen, applicationIsConnected, applicationHasWelcomeScreenOpen, applicationGetDebugState, applicationGetStandaloneSettings, applicationGetSsoSettings } from '../application/ApplicationSelectors';
14+
import { createConnectionThunk, createConnectionFromDesktopIntegrationThunk, setDatabaseFromNeo4jDesktopIntegrationThunk, handleSharedDashboardsThunk, onConfirmLoadSharedDashboardThunk, loadApplicationConfigThunk } from '../application/ApplicationThunks';
1515
import { clearDesktopConnectionProperties, clearNotification, resetShareDetails, setAboutModalOpen, setConnected, setConnectionModalOpen, setDashboardToLoadAfterConnecting, setOldDashboard, setStandAloneMode, setWelcomeScreenOpen } from '../application/ApplicationActions';
1616
import { resetDashboardState } from '../dashboard/DashboardActions';
1717
import { NeoDashboardPlaceholder } from '../dashboard/DashboardPlaceholder';
@@ -24,20 +24,24 @@ import { loadDashboardThunk } from '../dashboard/DashboardThunks';
2424
import { NeoLoadSharedDashboardModal } from '../modal/LoadSharedDashboardModal';
2525

2626
/**
27+
* This is the main application component for NeoDash.
28+
* It contains:
29+
* - The Dashboard component
30+
* - A number of modals (pop-up windows) that handle connections, loading/saving dashboards, etc.
2731
*
32+
* Parts of the application state are retrieved here and passed to the relevant compoenents.
33+
* State-changing actions are also dispatched from here. See `ApplicationThunks.tsx`, `ApplicationActions.tsx` and `ApplicationSelectors.tsx` for more info.
2834
*/
2935
const Application = ({ connection, connected, hasCachedDashboard, oldDashboard, clearOldDashboard,
30-
connectionModalOpen, aboutModalOpen, loadDashboard, hasNeo4jDesktopConnection, shareDetails,
36+
connectionModalOpen, ssoSettings, standaloneSettings, aboutModalOpen, loadDashboard, hasNeo4jDesktopConnection, shareDetails,
3137
createConnection, createConnectionFromDesktopIntegration, onResetShareDetails, onConfirmLoadSharedDashboard,
3238
initializeApplication, resetDashboard, onAboutModalOpen, onAboutModalClose, getDebugState,
3339
welcomeScreenOpen, setWelcomeScreenOpen, onConnectionModalOpen, onConnectionModalClose }) => {
3440

3541
const [initialized, setInitialized] = React.useState(false);
36-
3742
if (!initialized) {
38-
initializeApplication();
3943
setInitialized(true);
40-
44+
initializeApplication(initialized);
4145
}
4246

4347
// Only render the dashboard component if we have an active Neo4j connection.
@@ -55,6 +59,9 @@ const Application = ({ connection, connected, hasCachedDashboard, oldDashboard,
5559
open={connectionModalOpen}
5660
dismissable={connected}
5761
connection={connection}
62+
ssoSettings={ssoSettings}
63+
standalone={standaloneSettings.standalone}
64+
standaloneSettings={standaloneSettings}
5865
createConnection={createConnection}
5966
onConnectionModalClose={onConnectionModalClose} ></NeoConnectionModal>
6067
<NeoWelcomeScreenModal
@@ -87,6 +94,8 @@ const mapStateToProps = state => ({
8794
connection: applicationGetConnection(state),
8895
shareDetails: applicationGetShareDetails(state),
8996
oldDashboard: applicationGetOldDashboard(state),
97+
ssoSettings: applicationGetSsoSettings(state),
98+
standaloneSettings: applicationGetStandaloneSettings(state),
9099
connectionModalOpen: applicationHasConnectionModalOpen(state),
91100
aboutModalOpen: applicationHasAboutModalOpen(state),
92101
welcomeScreenOpen: applicationHasWelcomeScreenOpen(state),
@@ -110,18 +119,8 @@ const mapDispatchToProps = dispatch => ({
110119
},
111120
resetDashboard: _ => dispatch(resetDashboardState()),
112121
clearOldDashboard: _ => dispatch(setOldDashboard(null)),
113-
initializeApplication: _ => {
114-
dispatch(clearDesktopConnectionProperties());
115-
dispatch(setDatabaseFromNeo4jDesktopIntegrationThunk());
116-
const old = localStorage.getItem('neodash-dashboard');
117-
dispatch(setOldDashboard(old));
118-
dispatch(setConnected(false));
119-
dispatch(setDashboardToLoadAfterConnecting(null));
120-
dispatch(setStandAloneMode(false));
121-
dispatch(setWelcomeScreenOpen(true));
122-
dispatch(clearNotification());
123-
dispatch(handleSharedDashboardsThunk());
124-
dispatch(setConnectionModalOpen(false));
122+
initializeApplication: (initialized) => {
123+
dispatch(loadApplicationConfigThunk());
125124
},
126125
onResetShareDetails: _ => {
127126
dispatch(setWelcomeScreenOpen(true));

src/application/ApplicationActions.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* This file contains all state-changing actions relevant for the main application.
3+
*/
14

25
export const CLEAR_NOTIFICATION = 'APPLICATION/CLEAR_NOTIFICATION';
36
export const clearNotification = () => ({
@@ -41,6 +44,12 @@ export const setConnectionProperties = (protocol: string, url: string, port: str
4144
payload: { protocol, url, port, database, username, password },
4245
});
4346

47+
export const SET_BASIC_CONNECTION_PROPERTIES = 'APPLICATION/SET_BASIC_CONNECTION_PROPERTIES';
48+
export const setBasicConnectionProperties = (protocol: string, url: string, port: string, database: string, username: string, password: string) => ({
49+
type: SET_CONNECTION_PROPERTIES,
50+
payload: { protocol, url, port, database, username, password },
51+
});
52+
4453
export const SET_DESKTOP_CONNECTION_PROPERTIES = 'APPLICATION/SET_DESKTOP_CONNECTION_PROPERTIES';
4554
export const setDesktopConnectionProperties = (protocol: string, url: string, port: string, database: string, username: string, password: string) => ({
4655
type: SET_DESKTOP_CONNECTION_PROPERTIES,
@@ -73,14 +82,31 @@ export const setShareDetailsFromUrl = (type: string, id: string, standalone: boo
7382
payload: { type, id, standalone, protocol, url, port, database, username, password },
7483
});
7584

85+
export const SET_STANDALONE_ENABLED = 'APPLICATION/SET_STANDALONE_ENABLED';
86+
export const setStandaloneEnabled = (standalone: boolean, standaloneProtocol: string, standaloneHost: string, standalonePort: string, standaloneDatabase: string, standaloneDashboardName: string, standaloneDashboardDatabase: string ) => ({
87+
type: SET_STANDALONE_ENABLED,
88+
payload: { standalone, standaloneProtocol, standaloneHost, standalonePort, standaloneDatabase, standaloneDashboardName, standaloneDashboardDatabase },
89+
});
90+
7691
export const SET_STANDALONE_MODE = 'APPLICATION/SET_STANDALONE_MODE';
77-
export const setStandAloneMode = (standalone: boolean) => ({
78-
type: SET_STANDALONE_MODE,
92+
export const setStandaloneMode = (standalone: boolean ) => ({
93+
type: SET_STANDALONE_ENABLED,
7994
payload: { standalone },
8095
});
96+
export const SET_STANDALONE_DASHBOARD_DATEBASE = 'APPLICATION/SET_STANDALONE_DASHBOARD_DATEBASE';
97+
export const setStandaloneDashboardDatabase = (dashboardDatabase: string) => ({
98+
type: SET_STANDALONE_DASHBOARD_DATEBASE,
99+
payload: { dashboardDatabase }
100+
});
101+
102+
export const SET_SSO_ENABLED = 'APPLICATION/SET_SSO_ENABLED';
103+
export const setSSOEnabled = (enabled: boolean, discoveryUrl: string) => ({
104+
type: SET_SSO_ENABLED,
105+
payload: { enabled, discoveryUrl },
106+
});
81107

82108
export const SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING = 'APPLICATION/SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING';
83109
export const setDashboardToLoadAfterConnecting = (id: any) => ({
84110
type: SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING,
85111
payload: { id },
86-
});
112+
});

src/application/ApplicationReducer.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/**
2-
* Reducers define changes to the application state when a given action
2+
* Reducers define changes to the application state when a given action is taken.
33
*/
44

55
import {
6-
CLEAR_DESKTOP_CONNECTION_PROPERTIES, CLEAR_NOTIFICATION, CREATE_NOTIFICATION, RESET_SHARE_DETAILS, SET_ABOUT_MODAL_OPEN, SET_CONNECTED,
7-
SET_CONNECTION_MODAL_OPEN, SET_CONNECTION_PROPERTIES, SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING, SET_DESKTOP_CONNECTION_PROPERTIES, SET_OLD_DASHBOARD, SET_SHARE_DETAILS_FROM_URL, SET_STANDALONE_MODE, SET_WELCOME_SCREEN_OPEN
6+
CLEAR_DESKTOP_CONNECTION_PROPERTIES, CLEAR_NOTIFICATION, CREATE_NOTIFICATION,
7+
RESET_SHARE_DETAILS, SET_ABOUT_MODAL_OPEN, SET_CONNECTED,
8+
SET_CONNECTION_MODAL_OPEN, SET_CONNECTION_PROPERTIES,
9+
SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING, SET_DESKTOP_CONNECTION_PROPERTIES, SET_OLD_DASHBOARD,
10+
SET_SHARE_DETAILS_FROM_URL, SET_SSO_ENABLED, SET_STANDALONE_DASHBOARD_DATEBASE, SET_STANDALONE_ENABLED, SET_STANDALONE_MODE, SET_WELCOME_SCREEN_OPEN
811
} from "./ApplicationActions";
912

1013
const update = (state, mutations) =>
@@ -46,7 +49,7 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
4649
return state;
4750
}
4851
case CLEAR_NOTIFICATION: {
49-
state = update(state, { notificationTitle: null, notificationMessage: null })
52+
state = update(state, { notificationTitle: null, notificationMessage: null, notificationIsDismissable: null })
5053
return state;
5154
}
5255
case SET_CONNECTED: {
@@ -69,11 +72,34 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
6972
state = update(state, { welcomeScreenOpen: open })
7073
return state;
7174
}
75+
case SET_STANDALONE_DASHBOARD_DATEBASE : {
76+
const { dashboardDatabase } = payload;
77+
state = update(state, { standaloneDashboardDatabase: dashboardDatabase });
78+
return state;
79+
}
7280
case SET_STANDALONE_MODE: {
7381
const { standalone } = payload;
7482
state = update(state, { standalone: standalone })
7583
return state;
7684
}
85+
case SET_SSO_ENABLED: {
86+
const { enabled, discoveryUrl } = payload;
87+
state = update(state, { ssoEnabled: enabled, ssoDiscoveryUrl: discoveryUrl })
88+
return state;
89+
}
90+
case SET_STANDALONE_ENABLED: {
91+
const { standalone, standaloneProtocol, standaloneHost, standalonePort, standaloneDatabase, standaloneDashboardName, standaloneDashboardDatabase } = payload;
92+
state = update(state, {
93+
standalone: standalone,
94+
standaloneProtocol: standaloneProtocol,
95+
standaloneHost: standaloneHost,
96+
standalonePort: standalonePort,
97+
standaloneDatabase: standaloneDatabase,
98+
standaloneDashboardName: standaloneDashboardName,
99+
standaloneDashboardDatabase: standaloneDashboardDatabase
100+
})
101+
return state;
102+
}
77103
case SET_OLD_DASHBOARD: {
78104
const { text } = payload;
79105
state = update(state, { oldDashboard: text })

src/application/ApplicationSelectors.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { initialState } from "../dashboard/DashboardReducer";
22
import _ from 'lodash';
33

4+
/**
5+
* Selectors define a way to retrieve parts of the global application state for a sub-component.
6+
*/
7+
48
export const applicationHasNotification = (state: any) => {
59
return state.application.notificationMessage != null;
610
}
@@ -9,6 +13,10 @@ export const getNotification = (state: any) => {
913
return state.application.notificationMessage;
1014
}
1115

16+
export const getNotificationIsDismissable = (state: any) => {
17+
return state.application.notificationTitle !== "Unable to load application configuration";
18+
}
19+
1220
export const getNotificationTitle = (state: any) => {
1321
return state.application.notificationTitle;
1422
}
@@ -45,6 +53,25 @@ export const applicationHasAboutModalOpen = (state: any) => {
4553
return state.application.aboutModalOpen;
4654
}
4755

56+
export const applicationGetSsoSettings = (state: any) => {
57+
return {
58+
'ssoEnabled': state.application.ssoEnabled,
59+
'ssoDiscoveryUrl': state.application.ssoDiscoveryUrl
60+
};
61+
}
62+
63+
export const applicationGetStandaloneSettings = (state: any) => {
64+
return {
65+
"standalone": state.application.standalone,
66+
"standaloneProtocol": state.application.standaloneProtocol,
67+
"standaloneHost": state.application.standaloneHost,
68+
"standalonePort": state.application.standalonePort,
69+
"standaloneDatabase": state.application.standaloneDatabase,
70+
"standaloneDashboardName": state.application.standaloneDashboardName,
71+
"standaloneDashboardDatabase": state.application.standaloneDashboardDatabase
72+
}
73+
}
74+
4875
export const applicationHasWelcomeScreenOpen = (state: any) => {
4976
return state.application.welcomeScreenOpen;
5077
}
@@ -63,7 +90,7 @@ export const applicationHasCachedDashboard = (state: any) => {
6390
export const applicationGetDebugState = (state: any) => {
6491
const copy = JSON.parse(JSON.stringify(state));
6592
copy.application.connection.password = "************";
66-
if(copy.application.desktopConnection){
93+
if (copy.application.desktopConnection) {
6794
copy.application.desktopConnection.password = "************";
6895
}
6996
return copy;

0 commit comments

Comments
 (0)