From 9154facbfbeb6a0707162c817d8544fe5efeb378 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Sun, 12 Dec 2021 15:37:25 +0100 Subject: [PATCH 01/29] Updated version number --- package.json | 2 +- src/modal/AboutModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1c4d0f265..d1e952698 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neodash", - "version": "2.0.2", + "version": "2.0.3", "description": "NeoDash - Neo4j Dashboard Builder", "neo4jDesktop": { "apiVersion": "^1.2.0" diff --git a/src/modal/AboutModal.tsx b/src/modal/AboutModal.tsx index 1c0d65034..05fe5ad57 100644 --- a/src/modal/AboutModal.tsx +++ b/src/modal/AboutModal.tsx @@ -12,7 +12,7 @@ import Badge from '@material-ui/core/Badge'; export const NeoAboutModal = ({ open, handleClose }) => { const app = "NeoDash - Neo4j Dashboard Builder"; const email = "niels.dejong@neo4j.com"; - const version = "2.0.2"; + const version = "2.0.3"; return (
From 582de5bddc232813954d5f38840a0e2e5c8b1500 Mon Sep 17 00:00:00 2001 From: JipSogeti <94383160+JipSogeti@users.noreply.github.com> Date: Wed, 15 Dec 2021 22:13:21 +0100 Subject: [PATCH 02/29] Expandable charts when not in edit mode --- public/style.css | 12 +++++++++++- src/card/view/CardView.tsx | 22 +++++++++++++++++----- src/card/view/CardViewHeader.tsx | 18 +++++++++++++++--- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/public/style.css b/public/style.css index 7639c6592..92258693e 100644 --- a/public/style.css +++ b/public/style.css @@ -120,4 +120,14 @@ #center-aligned { text-align: center; - } \ No newline at end of file + } + +.card-view.expanded { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: white; + z-index: 100001; +} \ No newline at end of file diff --git a/src/card/view/CardView.tsx b/src/card/view/CardView.tsx index 657fb0230..c4527de49 100644 --- a/src/card/view/CardView.tsx +++ b/src/card/view/CardView.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { ReportItemContainer } from '../CardStyle'; import NeoCardViewHeader from './CardViewHeader'; import NeoCardViewFooter from './CardViewFooter'; @@ -13,13 +13,22 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter onGlobalParameterUpdate, onSelectionUpdate, onToggleCardSettings, onTitleUpdate, onFieldsUpdate }) => { const reportHeight = (97 * height) + (148 * Math.floor((height - 1) / 3)); const cardHeight = (120 * height) + (78 * Math.floor((height - 1) / 3)) - 7; + + const [expanded, setExpanded] = useState(false); + + const onToggleCardExpand = () => { + setExpanded(!expanded); + } // @ts-ignore const reportHeader = + onToggleCardSettings={onToggleCardSettings} + onToggleCardExpand={onToggleCardExpand} + expanded={expanded} + > ; // @ts-ignore @@ -31,15 +40,18 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter onSelectionUpdate={onSelectionUpdate} showOptionalSelections={(settings["showOptionalSelections"])} > ; + + const withoutFooter = !REPORT_TYPES[type].selection || (settings && settings.hideSelections); return ( -
+
{reportHeader} {/* if there's no selection for this report, we don't have a footer, so the report can be taller. */} - + { +const NeoCardViewHeader = ({ title, editable, onTitleUpdate, onToggleCardSettings, onToggleCardExpand, expanded }) => { const [text, setText] = React.useState(title); // Ensure that we only trigger a text update event after the user has stopped typing. @@ -42,8 +44,18 @@ const NeoCardViewHeader = ({ title, editable, onTitleUpdate, onToggleCardSetting - return } + const maximizeButton = + + + + const unMaximizeButton = + + + + return } From 451d72a1cb3eb178cc49b6db2448fc5c99c612be Mon Sep 17 00:00:00 2001 From: JipSogeti <94383160+JipSogeti@users.noreply.github.com> Date: Thu, 16 Dec 2021 13:05:15 +0100 Subject: [PATCH 03/29] Apply global parameter value to selection chart All other charts apply the current global parameter value in their Cypher queries. To keep consistent also apply the state to the parameter selection chart. --- src/card/view/CardView.tsx | 5 +++++ src/chart/ParameterSelectionChart.tsx | 3 ++- src/report/Report.tsx | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/card/view/CardView.tsx b/src/card/view/CardView.tsx index c4527de49..1662fef0e 100644 --- a/src/card/view/CardView.tsx +++ b/src/card/view/CardView.tsx @@ -43,6 +43,10 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter const withoutFooter = !REPORT_TYPES[type].selection || (settings && settings.hideSelections); + const getGlobalParameter = (key : string ) : any => { + return globalParameters[key]; + } + return (
{reportHeader} @@ -67,6 +71,7 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter type={type} ChartType={REPORT_TYPES[type].component} setGlobalParameter={onGlobalParameterUpdate} + getGlobalParameter={getGlobalParameter} setFields={onFieldsUpdate} /> {reportFooter} diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index 15b36c7cf..2f543ec9c 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -41,6 +41,7 @@ const NeoParameterSelectionChart = (props: ChartProps) => { const label = query.split("`")[1] ? query.split("`")[1] : ""; const property = query.split("`")[3] ? query.split("`")[3] : ""; + const currentValue = props.getGlobalParameter(parameter) || ""; return
{ setInputText(value); debouncedQueryCallback(query, {input: value}, setExtraRecords); }} - value={value ? value.toString() : ""} + value={value ? value.toString() : currentValue} onChange={(event, newValue) => { setValue(newValue); if(newValue == null && clearParameterOnFieldClear){ diff --git a/src/report/Report.tsx b/src/report/Report.tsx index eed77c93d..159b851f9 100644 --- a/src/report/Report.tsx +++ b/src/report/Report.tsx @@ -24,6 +24,7 @@ export const NeoReport = ({ settings = {}, // An optional dictionary of customization settings to pass to the report. setFields = (f) => { fields = f }, // The callback to update the set of query fields after query execution. setGlobalParameter = () => {}, // callback to update global (dashboard) parameters. + getGlobalParameter = () => {}, // function to get global (dashboard) parameters. refreshRate = 0, // Optionally refresh the report every X seconds. dimensions = { width: 3, height: 3 }, // Size of the report. rowLimit = DEFAULT_ROW_LIMIT, // The maximum number of records to render. @@ -159,7 +160,7 @@ export const NeoReport = ({ } {/* @ts-ignore */ } return (
- +
); } else if (status == QueryStatus.COMPLETE_TRUNCATED) { if (records == null || records.length == 0) { @@ -174,7 +175,7 @@ export const NeoReport = ({
- +
); } else if (status == QueryStatus.TIMED_OUT) { return Date: Thu, 16 Dec 2021 13:16:47 +0100 Subject: [PATCH 04/29] Get parameter value earlier to apply as default state --- src/chart/ParameterSelectionChart.tsx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index 2f543ec9c..e65212982 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -18,30 +18,31 @@ const NeoParameterSelectionChart = (props: ChartProps) => { } + const records = props.records; + const query = records[0]["input"]; + + if(query){ + var parameter = query.split("\n")[0].split("$")[1]; + var label = query.split("`")[1] ? query.split("`")[1] : ""; + var property = query.split("`")[3] ? query.split("`")[3] : ""; + + var currentValue = props.getGlobalParameter(parameter) || ""; + } + const settings = (props.settings) ? props.settings : {}; const clearParameterOnFieldClear = settings.clearParameterOnFieldClear; const [extraRecords, setExtraRecords] = React.useState([]); - const [inputText, setInputText] = React.useState(""); - const [value, setValue] = React.useState(""); + const [inputText, setInputText] = React.useState(currentValue); + const [value, setValue] = React.useState(currentValue); const debouncedQueryCallback = useCallback( debounce(props.queryCallback, 250), [], ); - - - const records = props.records; - const query = records[0]["input"]; if (!query) { return

No selection specified. Open up the report settings and choose a node label and property.

} - - const parameter = query.split("\n")[0].split("$")[1]; - const label = query.split("`")[1] ? query.split("`")[1] : ""; - const property = query.split("`")[3] ? query.split("`")[3] : ""; - - const currentValue = props.getGlobalParameter(parameter) || ""; return
Date: Fri, 17 Dec 2021 14:39:25 +0100 Subject: [PATCH 05/29] Minor fixes to chart interface, reorganized code to be able to avoid uninitialized variables --- src/card/view/CardView.tsx | 12 ++++++------ src/chart/Chart.tsx | 5 +++-- src/chart/ParameterSelectionChart.tsx | 19 ++++++++++--------- src/report/Report.tsx | 10 ++++++---- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/card/view/CardView.tsx b/src/card/view/CardView.tsx index 1662fef0e..80138fdfc 100644 --- a/src/card/view/CardView.tsx +++ b/src/card/view/CardView.tsx @@ -13,9 +13,9 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter onGlobalParameterUpdate, onSelectionUpdate, onToggleCardSettings, onTitleUpdate, onFieldsUpdate }) => { const reportHeight = (97 * height) + (148 * Math.floor((height - 1) / 3)); const cardHeight = (120 * height) + (78 * Math.floor((height - 1) / 3)) - 7; - + const [expanded, setExpanded] = useState(false); - + const onToggleCardExpand = () => { setExpanded(!expanded); } @@ -28,7 +28,7 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter onToggleCardSettings={onToggleCardSettings} onToggleCardExpand={onToggleCardExpand} expanded={expanded} - > + > ; // @ts-ignore @@ -40,13 +40,13 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter onSelectionUpdate={onSelectionUpdate} showOptionalSelections={(settings["showOptionalSelections"])} > ; - + const withoutFooter = !REPORT_TYPES[type].selection || (settings && settings.hideSelections); - const getGlobalParameter = (key : string ) : any => { + const getGlobalParameter = (key: string): any => { return globalParameters[key]; } - + return (
{reportHeader} diff --git a/src/chart/Chart.tsx b/src/chart/Chart.tsx index 6318d08b2..cce377ead 100644 --- a/src/chart/Chart.tsx +++ b/src/chart/Chart.tsx @@ -6,7 +6,8 @@ export interface ChartProps { records: Neo4jRecord[]; // Query output, Neo4j records as returned from the driver. selection?: Record; // A dictionary with the selection made in the report footer. settings?: Record; // A dictionary with the 'advanced settings' specified through the NeoDash interface. - dimensions?: Number[]; // a 2D array with the dimensions of the report (likely not needed, charts automatically fill up space) + dimensions?: {}; // a 2D array with the dimensions of the report (likely not needed, charts automatically fill up space) queryCallback?: (query: string, parameters: Record, records: Neo4jRecord[]) => null; // Optionally, a way for the report to read more data from Neo4j. - setGlobalParameter?: (name : string, value: string) => null; // Allows a chart to update a global dashboard parameter to be used in Cypher queries for other reports. + setGlobalParameter?: (name: string, value: string) => void; // Allows a chart to update a global dashboard parameter to be used in Cypher queries for other reports. + getGlobalParameter?: (name) => string; // Allows a chart to get a global dashboard parameter. } \ No newline at end of file diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index e65212982..9555d0435 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -21,14 +21,17 @@ const NeoParameterSelectionChart = (props: ChartProps) => { const records = props.records; const query = records[0]["input"]; - if(query){ - var parameter = query.split("\n")[0].split("$")[1]; - var label = query.split("`")[1] ? query.split("`")[1] : ""; - var property = query.split("`")[3] ? query.split("`")[3] : ""; - - var currentValue = props.getGlobalParameter(parameter) || ""; + if (!query) { + return

No selection specified. Open up the report settings and choose a node label and property.

} + + const parameter = query.split("\n")[0].split("$")[1]; + const label = query.split("`")[1] ? query.split("`")[1] : ""; + const property = query.split("`")[3] ? query.split("`")[3] : ""; + const currentValue = props.getGlobalParameter && props.getGlobalParameter(parameter) || ""; + + const settings = (props.settings) ? props.settings : {}; const clearParameterOnFieldClear = settings.clearParameterOnFieldClear; @@ -40,9 +43,7 @@ const NeoParameterSelectionChart = (props: ChartProps) => { [], ); - if (!query) { - return

No selection specified. Open up the report settings and choose a node label and property.

- } + return
{ fields = f }, // The callback to update the set of query fields after query execution. - setGlobalParameter = () => {}, // callback to update global (dashboard) parameters. - getGlobalParameter = () => {}, // function to get global (dashboard) parameters. + setGlobalParameter = () => {}, // callback to update global (cypher) parameters. + getGlobalParameter = (key) => {return ""}, // function to get global (cypher) parameters. refreshRate = 0, // Optionally refresh the report every X seconds. dimensions = { width: 3, height: 3 }, // Size of the report. rowLimit = DEFAULT_ROW_LIMIT, // The maximum number of records to render. @@ -160,7 +160,8 @@ export const NeoReport = ({ } {/* @ts-ignore */ } return (
- +
); } else if (status == QueryStatus.COMPLETE_TRUNCATED) { if (records == null || records.length == 0) { @@ -175,7 +176,8 @@ export const NeoReport = ({
- +
); } else if (status == QueryStatus.TIMED_OUT) { return Date: Sun, 19 Dec 2021 13:05:52 +0100 Subject: [PATCH 06/29] Added example on how to use maps when returning dictionaries --- src/config/ExampleConfig.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/config/ExampleConfig.tsx b/src/config/ExampleConfig.tsx index 8a0377c8d..6302d7bbf 100644 --- a/src/config/ExampleConfig.tsx +++ b/src/config/ExampleConfig.tsx @@ -174,6 +174,27 @@ export const EXAMPLE_REPORTS = [ {start: "Cologne", end: "Nijmegen", type: "ROUTE_TO", distance: "180km", id: 103}, {start: "Nijmegen", end: "Tilburg", type: "ROUTE_TO", distance: "92km", id: 104} ] as value +RETURN value + `, + settings: {}, + fields: [], + selection: {}, + type: "map", + chartType: NeoMapChart + }, + { + title: "Map (from properties)", + description: "Use dictionaries to visualize entities that are not real nodes and relationships.", + exampleQuery: `// Plot an artificial relationship.\nMATCH (l1:Location)<--(a:Person),\n (a:Person)-[:KNOWS]-(b:Person),\n (b:Person)-->(l2:Location) +RETURN {id: a.name, label: "Person", point: l1.point}, + {id: b.name, label: "Person", point: l2.point}, + {start: a.name, end: b.name, type: "KNOWS", id: 1} +`, + syntheticQuery: ` + UNWIND [{id: "Dwight", label: "Person", point: point({latitude:41.45954418871592, longitude:-75.75265878192192})}, +{id: "Jim", label: "Person", point: point({latitude:41.41492119160039,longitude: -75.6470002887925})}, +{start: "Dwight", end: "Jim", type: "KNOWS", id: 100} +] as value RETURN value `, settings: {}, From 28aaa1ad23afde7e9d5b9e68fa69556085dd9ae8 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Sun, 19 Dec 2021 13:14:46 +0100 Subject: [PATCH 07/29] Made connection modal dismissable when connected to Neo4j --- src/application/Application.tsx | 1 + src/modal/ConnectionModal.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/application/Application.tsx b/src/application/Application.tsx index a674c8594..59f5c1ac9 100644 --- a/src/application/Application.tsx +++ b/src/application/Application.tsx @@ -52,6 +52,7 @@ const Application = ({ connection, connected, hasCachedDashboard, oldDashboard, diff --git a/src/modal/ConnectionModal.tsx b/src/modal/ConnectionModal.tsx index 576ed55bf..b60eacf70 100644 --- a/src/modal/ConnectionModal.tsx +++ b/src/modal/ConnectionModal.tsx @@ -26,7 +26,7 @@ export default function NeoConnectionModal({ open, connection, dismissable = fal return (
- { dismissable ? onConnectionModalClose : null }} aria-labelledby="form-dialog-title"> + { dismissable ? onConnectionModalClose() : null }} aria-labelledby="form-dialog-title"> Connect to Neo4j From e0c608e0dac742f0686a0f264a81efbe8345f324 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Sun, 19 Dec 2021 14:21:36 +0100 Subject: [PATCH 08/29] Made fullscreen reports work for maps and lines, now optionally available through dashboard settings --- package.json | 2 +- src/card/Card.tsx | 2 ++ src/card/view/CardView.tsx | 6 ++++-- src/card/view/CardViewHeader.tsx | 23 ++++++++++++++++++++--- src/chart/Chart.tsx | 3 ++- src/chart/LineChart.tsx | 6 ++++-- src/chart/MapChart.tsx | 4 +++- src/chart/TableChart.tsx | 6 ++++-- src/config/DashboardConfig.tsx | 7 +++++++ src/modal/AboutModal.tsx | 2 +- src/page/Page.tsx | 4 ++++ src/report/Report.tsx | 30 ++++++++++++++++++++++-------- src/settings/SettingsModal.tsx | 2 +- 13 files changed, 75 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index d1e952698..27397da1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neodash", - "version": "2.0.3", + "version": "2.0.4", "description": "NeoDash - Neo4j Dashboard Builder", "neo4jDesktop": { "apiVersion": "^1.2.0" diff --git a/src/card/Card.tsx b/src/card/Card.tsx index fdcee0b2f..293495391 100644 --- a/src/card/Card.tsx +++ b/src/card/Card.tsx @@ -21,6 +21,7 @@ const NeoCard = ({ editable, // whether the card is editable. database, // the neo4j database that the card is running against. globalParameters, // Query parameters that are globally set for the entire dashboard. + dashboardSettings, // Dictionary of settings for the entire dashboard. onRemovePressed, // action to take when the card is removed. (passed from parent) onShiftLeftPressed, // action to take when the card is shifted left. onShiftRightPressed, // action to take when the card is shifted right. @@ -62,6 +63,7 @@ const NeoCard = ({ { const reportHeight = (97 * height) + (148 * Math.floor((height - 1) / 3)); const cardHeight = (120 * height) + (78 * Math.floor((height - 1) / 3)) - 7; @@ -24,6 +24,7 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter const reportHeader = { +const NeoCardViewHeader = ({ title, editable, onTitleUpdate, fullscreenEnabled, onToggleCardSettings, onToggleCardExpand, expanded }) => { const [text, setText] = React.useState(title); // Ensure that we only trigger a text update event after the user has stopped typing. @@ -54,8 +57,22 @@ const NeoCardViewHeader = ({ title, editable, onTitleUpdate, onToggleCardSetting + // const maximizeButton = + // + // + + // const unMaximizeButton = + // + // + return + {fullscreenEnabled ? (expanded ? unMaximizeButton : maximizeButton) : <>} + {(editable && !expanded) ? settingsButton : <>} + } title={cardTitle} /> } diff --git a/src/chart/Chart.tsx b/src/chart/Chart.tsx index 6318d08b2..f2378b140 100644 --- a/src/chart/Chart.tsx +++ b/src/chart/Chart.tsx @@ -6,7 +6,8 @@ export interface ChartProps { records: Neo4jRecord[]; // Query output, Neo4j records as returned from the driver. selection?: Record; // A dictionary with the selection made in the report footer. settings?: Record; // A dictionary with the 'advanced settings' specified through the NeoDash interface. - dimensions?: Number[]; // a 2D array with the dimensions of the report (likely not needed, charts automatically fill up space) + dimensions?: Number[]; // a 2D array with the dimensions of the report (likely not needed, charts automatically fill up space). + fullscreen: boolean; // flag indicating whether the report is rendered in a fullscreen view. queryCallback?: (query: string, parameters: Record, records: Neo4jRecord[]) => null; // Optionally, a way for the report to read more data from Neo4j. setGlobalParameter?: (name : string, value: string) => null; // Allows a chart to update a global dashboard parameter to be used in Cypher queries for other reports. } \ No newline at end of file diff --git a/src/chart/LineChart.tsx b/src/chart/LineChart.tsx index 39163dbdf..6425d7091 100644 --- a/src/chart/LineChart.tsx +++ b/src/chart/LineChart.tsx @@ -11,8 +11,10 @@ const NeoLineChart = (props: ChartProps) => { if (props.records == null || props.records.length == 0 || props.records[0].keys == null){ return <>No data, re-run the report. } - return + // Wrapping this report ensures a refresh on a switch of fullscreen mode. + return
+
} export default NeoLineChart; \ No newline at end of file diff --git a/src/chart/MapChart.tsx b/src/chart/MapChart.tsx index 22952bf1a..5c7831d54 100644 --- a/src/chart/MapChart.tsx +++ b/src/chart/MapChart.tsx @@ -296,8 +296,10 @@ const NeoMapChart = (props: ChartProps) => { const markers = createMarkers(); const lines = createLines(); + const fullscreen = props.fullscreen ? props.fullscreen : false; + // Draw the component. - return { + const fullscreen = props.fullscreen ? props.fullscreen : false; + if (props.records == null || props.records.length == 0 || props.records[0].keys == null) { return <>No data, re-run the report. } @@ -92,8 +94,8 @@ const NeoTableChart = (props: ChartProps) => { <>, diff --git a/src/config/DashboardConfig.tsx b/src/config/DashboardConfig.tsx index d9009c850..0f9ed315a 100644 --- a/src/config/DashboardConfig.tsx +++ b/src/config/DashboardConfig.tsx @@ -19,6 +19,13 @@ export const DASHBOARD_SETTINGS = { values: [true, false], default: true, helperText: "This controls whether users can edit your dashboard. Disable this to turn the dashboard into presentation mode." + }, + "fullscreenEnabled": { + label: "Enable Fullscreen Report Views", + type: SELECTION_TYPES.LIST, + values: [true, false], + default: false, + helperText: "Enables a 'fullscreen view' button for each report, letting users expand a visualization." } } \ No newline at end of file diff --git a/src/modal/AboutModal.tsx b/src/modal/AboutModal.tsx index 05fe5ad57..8df5b849b 100644 --- a/src/modal/AboutModal.tsx +++ b/src/modal/AboutModal.tsx @@ -12,7 +12,7 @@ import Badge from '@material-ui/core/Badge'; export const NeoAboutModal = ({ open, handleClose }) => { const app = "NeoDash - Neo4j Dashboard Builder"; const email = "niels.dejong@neo4j.com"; - const version = "2.0.3"; + const version = "2.0.4"; return (
diff --git a/src/page/Page.tsx b/src/page/Page.tsx index 05c4f05a4..fbdea4375 100644 --- a/src/page/Page.tsx +++ b/src/page/Page.tsx @@ -6,6 +6,7 @@ import { getReports } from './PageSelectors'; import { removeReportRequest, shiftReportLeftRequest, shiftReportRightRequest } from './PageThunks'; import Grid from '@material-ui/core/Grid'; import { getDashboardIsEditable } from '../settings/SettingsSelectors'; +import { getDashboardSettings } from '../dashboard/DashboardSelectors'; /** @@ -14,6 +15,7 @@ import { getDashboardIsEditable } from '../settings/SettingsSelectors'; export const NeoPage = ( { editable = true, // Whether the page is editable. + dashboardSettings, // global settings for the entire dashboard. reports = [], // list of reports as defined in the dashboard state. onRemovePressed = (index) => { }, // action to take when a report gets removed. onShiftLeftPressed = (index) => { }, // action to take when a report gets shifted left. @@ -32,6 +34,7 @@ export const NeoPage = ( return @@ -50,6 +53,7 @@ export const NeoPage = ( const mapStateToProps = state => ({ isLoaded: true, editable: getDashboardIsEditable(state), + dashboardSettings: getDashboardSettings(state), reports: getReports(state), }); diff --git a/src/report/Report.tsx b/src/report/Report.tsx index eed77c93d..91dc5fc42 100644 --- a/src/report/Report.tsx +++ b/src/report/Report.tsx @@ -23,11 +23,12 @@ export const NeoReport = ({ fields = [], // A list of the return data fields that the query produces. settings = {}, // An optional dictionary of customization settings to pass to the report. setFields = (f) => { fields = f }, // The callback to update the set of query fields after query execution. - setGlobalParameter = () => {}, // callback to update global (dashboard) parameters. + setGlobalParameter = () => { }, // callback to update global (dashboard) parameters. refreshRate = 0, // Optionally refresh the report every X seconds. dimensions = { width: 3, height: 3 }, // Size of the report. rowLimit = DEFAULT_ROW_LIMIT, // The maximum number of records to render. type = "table", // The type of report as a string. + expanded = false, // whether the report is visualized in a fullscreen view. ChartType = NeoTableChart, // The report component to render with the query results. }) => { const [records, setRecords] = useState(null); @@ -100,7 +101,7 @@ export const NeoReport = ({ // When report parameters are changed, re-run the report. useEffect(() => { - + if (timer) { // @ts-ignore clearInterval(timer); @@ -125,10 +126,10 @@ export const NeoReport = ({ // Define query callback to allow reports to get extra data on interactions. const queryCallback = useCallback( (query, parameters, setRecords) => { - runCypherQuery(driver, database, query, parameters, selection, fields, rowLimit, - (status) => { status == QueryStatus.NO_DATA ? setRecords([]) : null}, - (result => setRecords(result)), - () => { return}, HARD_ROW_LIMITING, + runCypherQuery(driver, database, query, parameters, selection, fields, rowLimit, + (status) => { status == QueryStatus.NO_DATA ? setRecords([]) : null }, + (result => setRecords(result)), + () => { return }, HARD_ROW_LIMITING, REPORT_TYPES[type].useRecordMapper == true, false, [], [], [], [], null); }, @@ -159,7 +160,13 @@ export const NeoReport = ({ } {/* @ts-ignore */ } return (
- +
); } else if (status == QueryStatus.COMPLETE_TRUNCATED) { if (records == null || records.length == 0) { @@ -174,7 +181,14 @@ export const NeoReport = ({
- +
); } else if (status == QueryStatus.TIMED_OUT) { return updateDashboardSetting(setting, e)} - />
+ />
)}
From 523931bfe91f68618bd61d5116e5512e87804e2a Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Sun, 19 Dec 2021 17:23:52 +0100 Subject: [PATCH 09/29] Fixed load dashboard functionality to automatically pick up the selection stored in the dashboard --- src/card/CardThunks.tsx | 11 +++++++---- src/card/view/CardViewFooter.tsx | 2 +- src/config/ExampleConfig.tsx | 2 +- src/dashboard/DashboardThunks.tsx | 11 +++++++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/card/CardThunks.tsx b/src/card/CardThunks.tsx index 379939910..d477c2aab 100644 --- a/src/card/CardThunks.tsx +++ b/src/card/CardThunks.tsx @@ -96,18 +96,21 @@ export const updateFieldsThunk = (index, fields) => (dispatch: any, getState: an } } else { - if (fields.length > 0) { // For multi selections, select the Nth item of the result fields as a single item array. - if (selectableFields[selection].multiple) { - dispatch(updateSelection(pagenumber, index, selection, [fields[Math.min(i, fields.length - 1)]])); + if (selectableFields[selection].multiple) { + // only update if the old selection no longer covers the new set of fields... + if(!oldSelection[selection].every(v => fields.includes(v))){ + dispatch(updateSelection(pagenumber, index, selection, [fields[Math.min(i, fields.length - 1)]])); + } + } else if (selectableFields[selection].type == SELECTION_TYPES.NODE_PROPERTIES) { // For node property selections, select the most obvious properties of the node to display. const selection = {}; fields.forEach(nodeLabelAndProperties => { const label = nodeLabelAndProperties[0]; const properties = nodeLabelAndProperties.slice(1); - var selectedProp = undefined; + var selectedProp = oldSelection[label] ? oldSelection[label] : undefined; if(autoAssignSelectedProperties){ DEFAULT_NODE_LABELS.forEach(prop => { if(properties.indexOf(prop) !== -1){ diff --git a/src/card/view/CardViewFooter.tsx b/src/card/view/CardViewFooter.tsx index 870f7eecf..2113d9654 100644 --- a/src/card/view/CardViewFooter.tsx +++ b/src/card/view/CardViewFooter.tsx @@ -40,7 +40,7 @@ const NeoCardViewFooter = ({ fields, settings, selection, type, showOptionalSele onChange={e => onSelectionUpdate(nodeLabel, e.target.value)} value={(selection && selection[nodeLabel]) ? selection[nodeLabel] : ""}> {/* Render choices */} - {properties.length && properties.map((field, index) => { + {properties.length && properties.map && properties.map((field, index) => { return {field} diff --git a/src/config/ExampleConfig.tsx b/src/config/ExampleConfig.tsx index 6302d7bbf..2281c9605 100644 --- a/src/config/ExampleConfig.tsx +++ b/src/config/ExampleConfig.tsx @@ -193,7 +193,7 @@ RETURN {id: a.name, label: "Person", point: l1.point}, syntheticQuery: ` UNWIND [{id: "Dwight", label: "Person", point: point({latitude:41.45954418871592, longitude:-75.75265878192192})}, {id: "Jim", label: "Person", point: point({latitude:41.41492119160039,longitude: -75.6470002887925})}, -{start: "Dwight", end: "Jim", type: "KNOWS", id: 100} +{start: "Dwight", end: "Jim", type: "KNOWS", id: 1} ] as value RETURN value `, diff --git a/src/dashboard/DashboardThunks.tsx b/src/dashboard/DashboardThunks.tsx index 449d0dce1..a771a5258 100644 --- a/src/dashboard/DashboardThunks.tsx +++ b/src/dashboard/DashboardThunks.tsx @@ -103,6 +103,17 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { throw ("Invalid dashboard version: " + dashboard.version); } + // Reverse engineer the minimal set of fields from the selection loaded. + dashboard.pages.forEach(p => { + p.reports.forEach(r => { + if(r.selection){ + r["fields"] = [] + Object.keys(r.selection).forEach(f => { + r["fields"].push([f, r.selection[f]]) + }) + } + }) + }) dispatch(setDashboard(dashboard)) } catch (e) { From c8bc6048019b5b1f5034f6c46c2d863789baee0e Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Sun, 19 Dec 2021 17:37:56 +0100 Subject: [PATCH 10/29] Added button for returning to main menu screen --- src/dashboard/DashboardDrawer.tsx | 27 ++++++++++++++++++++------- src/dashboard/DashboardThunks.tsx | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/dashboard/DashboardDrawer.tsx b/src/dashboard/DashboardDrawer.tsx index c8d74295a..2de7d187e 100644 --- a/src/dashboard/DashboardDrawer.tsx +++ b/src/dashboard/DashboardDrawer.tsx @@ -1,8 +1,8 @@ -import { Drawer, ListItem, IconButton, Divider, ListItemIcon, ListItemText, List } from "@material-ui/core"; +import { Drawer, ListItem, IconButton, Divider, ListItemIcon, ListItemText, List, Button } from "@material-ui/core"; import React from "react"; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; -import SettingsIcon from '@material-ui/icons/Settings'; -import LibraryBooksIcon from '@material-ui/icons/LibraryBooks'; +import OfflineBoltIcon from '@material-ui/icons/OfflineBolt'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; import NeoSaveModal from "../modal/SaveModal"; import NeoLoadModal from "../modal/LoadModal"; @@ -11,7 +11,7 @@ import { NeoAboutModal } from "../modal/AboutModal"; import { NeoDocumentationModal } from "../modal/DocumentationModal"; import { applicationGetConnection, applicationHasAboutModalOpen, applicationIsStandalone } from '../application/ApplicationSelectors'; import { connect } from 'react-redux'; -import { setAboutModalOpen } from '../application/ApplicationActions'; +import { setAboutModalOpen, setConnected, setWelcomeScreenOpen } from '../application/ApplicationActions'; import NeoSettingsModal from "../settings/SettingsModal"; import { createNotificationThunk } from "../page/PageThunks"; import { getDashboardSettings } from "./DashboardSelectors"; @@ -19,7 +19,7 @@ import { updateDashboardSetting } from "../settings/SettingsActions"; export const NeoDrawer = ({ open, hidden, connection, dashboardSettings, updateDashboardSetting, - handleDrawerClose, aboutModalOpen, onShareModalOpen, onAboutModalOpen }) => { + handleDrawerClose, aboutModalOpen, onShareModalOpen, onAboutModalOpen, resetApplication }) => { // Override to hide the drawer when the application is in standalone mode. if (hidden) { @@ -58,7 +58,16 @@ export const NeoDrawer = ({ open, hidden, connection, dashboardSettings, updateD }}> - NeoDash + @@ -109,7 +118,11 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ onAboutModalOpen: _ => dispatch(setAboutModalOpen(true)), - updateDashboardSetting: (setting, value) => dispatch(updateDashboardSetting(setting, value)) + updateDashboardSetting: (setting, value) => dispatch(updateDashboardSetting(setting, value)), + resetApplication: _ => { + dispatch(setWelcomeScreenOpen(true)); + dispatch(setConnected(false)); + } }); diff --git a/src/dashboard/DashboardThunks.tsx b/src/dashboard/DashboardThunks.tsx index a771a5258..4b9b7b751 100644 --- a/src/dashboard/DashboardThunks.tsx +++ b/src/dashboard/DashboardThunks.tsx @@ -114,7 +114,7 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { } }) }) - dispatch(setDashboard(dashboard)) + dispatch(setDashboard(dashboard)); } catch (e) { dispatch(createNotificationThunk("Unable to load dashboard", e)); From 9117b8277f606f80f9b12746d385bfc59dd82c56 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Sun, 19 Dec 2021 17:52:00 +0100 Subject: [PATCH 11/29] Fixed z-index for fullscreen mode --- public/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/style.css b/public/style.css index 92258693e..0f4f851f7 100644 --- a/public/style.css +++ b/public/style.css @@ -129,5 +129,5 @@ right: 0; bottom: 0; background: white; - z-index: 100001; + z-index: 1299; } \ No newline at end of file From d83ac1a75ef370bca18b9fcf340cd04d50a51dd6 Mon Sep 17 00:00:00 2001 From: JipSogeti <94383160+JipSogeti@users.noreply.github.com> Date: Mon, 20 Dec 2021 17:10:17 +0100 Subject: [PATCH 12/29] Revert some changes and add more comments --- src/chart/ParameterSelectionChart.tsx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index 9555d0435..9df0a5515 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -21,17 +21,16 @@ const NeoParameterSelectionChart = (props: ChartProps) => { const records = props.records; const query = records[0]["input"]; - if (!query) { - return

No selection specified. Open up the report settings and choose a node label and property.

+ if(query){ + var parameter = query.split("\n")[0].split("$")[1]; + var label = query.split("`")[1] ? query.split("`")[1] : ""; + var property = query.split("`")[3] ? query.split("`")[3] : ""; + var currentValue = props.getGlobalParameter(parameter) || ""; + } else { + // Don't return here... + // Will cause the application to crash. } - - const parameter = query.split("\n")[0].split("$")[1]; - const label = query.split("`")[1] ? query.split("`")[1] : ""; - const property = query.split("`")[3] ? query.split("`")[3] : ""; - const currentValue = props.getGlobalParameter && props.getGlobalParameter(parameter) || ""; - - const settings = (props.settings) ? props.settings : {}; const clearParameterOnFieldClear = settings.clearParameterOnFieldClear; @@ -43,7 +42,12 @@ const NeoParameterSelectionChart = (props: ChartProps) => { [], ); - + if (!query) { + // For some reason can't return early in the else statement above, or application will crash. + // Crash seems related to useState. + // So instead just return here... + return

No selection specified. Open up the report settings and choose a node label and property.

+ } return
Date: Thu, 23 Dec 2021 09:42:16 +0100 Subject: [PATCH 13/29] Resolved rendering issues when loading parameter selection reports with prepopulated parameters --- src/card/view/CardView.tsx | 2 +- src/chart/ParameterSelectionChart.tsx | 53 +++++++++++++-------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/card/view/CardView.tsx b/src/card/view/CardView.tsx index 80138fdfc..6a72d4680 100644 --- a/src/card/view/CardView.tsx +++ b/src/card/view/CardView.tsx @@ -44,7 +44,7 @@ const NeoCardView = ({ title, database, query, cypherParameters, globalParameter const withoutFooter = !REPORT_TYPES[type].selection || (settings && settings.hideSelections); const getGlobalParameter = (key: string): any => { - return globalParameters[key]; + return globalParameters ? globalParameters[key] : undefined; } return ( diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index 9555d0435..578a7b8b8 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -10,41 +10,38 @@ import Autocomplete from '@material-ui/lab/Autocomplete'; * Renders Neo4j records as their JSON representation. */ const NeoParameterSelectionChart = (props: ChartProps) => { - try{ - useEffect(() => { - debouncedQueryCallback && debouncedQueryCallback(query, { input: inputText }, setExtraRecords); - }, [inputText, query]); - }catch(e){ - - } - const records = props.records; - const query = records[0]["input"]; + useEffect(() => { + debouncedQueryCallback && debouncedQueryCallback(query, { input: inputText }, setExtraRecords); + }, [inputText, query]); + const debouncedQueryCallback = useCallback( + debounce(props.queryCallback, 250), + [], + ); + const records = props.records; + const query = records[0]["input"] ? records[0]["input"] : undefined; + const parameter = query ? query.split("\n")[0].split("$")[1] : "$"; + + const currentValue = (props.getGlobalParameter && props.getGlobalParameter(parameter)) ? props.getGlobalParameter(parameter) : ""; + const [extraRecords, setExtraRecords] = React.useState([]); + const [inputText, setInputText] = React.useState(currentValue); + const [value, setValue] = React.useState(currentValue); + // console.log(value, currentValue, inputText) + + if(value != currentValue && currentValue != inputText ){ + setValue(currentValue); + setInputText(currentValue); + } if (!query) { - return

No selection specified. Open up the report settings and choose a node label and property.

+ return

No selection specified. Open up the report settings and choose a node label and property.

} - - const parameter = query.split("\n")[0].split("$")[1]; const label = query.split("`")[1] ? query.split("`")[1] : ""; const property = query.split("`")[3] ? query.split("`")[3] : ""; - const currentValue = props.getGlobalParameter && props.getGlobalParameter(parameter) || ""; - - const settings = (props.settings) ? props.settings : {}; const clearParameterOnFieldClear = settings.clearParameterOnFieldClear; - const [extraRecords, setExtraRecords] = React.useState([]); - const [inputText, setInputText] = React.useState(currentValue); - const [value, setValue] = React.useState(currentValue); - const debouncedQueryCallback = useCallback( - debounce(props.queryCallback, 250), - [], - ); - - - return
{ inputValue={inputText} onInputChange={(event, value) => { setInputText(value); - debouncedQueryCallback(query, {input: value}, setExtraRecords); + debouncedQueryCallback(query, { input: value }, setExtraRecords); }} value={value ? value.toString() : currentValue} onChange={(event, newValue) => { setValue(newValue); - if(newValue == null && clearParameterOnFieldClear){ + if (newValue == null && clearParameterOnFieldClear) { props.setGlobalParameter(parameter, undefined); - }else{ + } else { props.setGlobalParameter(parameter, newValue); } }} From 5e8006887c813ef3c5694a229563bde09e561aa1 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Thu, 23 Dec 2021 09:56:47 +0100 Subject: [PATCH 14/29] Show placeholder when no query is specified --- src/chart/ParameterSelectionChart.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index 4e341b5a2..5e727778d 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -28,16 +28,19 @@ const NeoParameterSelectionChart = (props: ChartProps) => { const [inputText, setInputText] = React.useState(currentValue); const [value, setValue] = React.useState(currentValue); - if(value != currentValue && currentValue != inputText ){ + // In case the components gets (re)loaded with a different/non-existing selected parameter, set the text to the current global parameter value. + if(query && value != currentValue && currentValue != inputText ){ setValue(currentValue); setInputText(currentValue); } - if (!query) { + + if (!query || query.trim().length == 0) { return

No selection specified. Open up the report settings and choose a node label and property.

} const label = query.split("`")[1] ? query.split("`")[1] : ""; const property = query.split("`")[3] ? query.split("`")[3] : ""; + const settings = (props.settings) ? props.settings : {}; const clearParameterOnFieldClear = settings.clearParameterOnFieldClear; From 5ed29e2062fb18670d8bd96a58db85f323f70cf6 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Thu, 23 Dec 2021 10:57:57 +0100 Subject: [PATCH 15/29] Reset extra parameters on page load for parameter select report --- src/chart/ParameterSelectionChart.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chart/ParameterSelectionChart.tsx b/src/chart/ParameterSelectionChart.tsx index 5e727778d..19f1eba02 100644 --- a/src/chart/ParameterSelectionChart.tsx +++ b/src/chart/ParameterSelectionChart.tsx @@ -30,10 +30,11 @@ const NeoParameterSelectionChart = (props: ChartProps) => { // In case the components gets (re)loaded with a different/non-existing selected parameter, set the text to the current global parameter value. if(query && value != currentValue && currentValue != inputText ){ + setValue(currentValue); setInputText(currentValue); + setExtraRecords([]); } - if (!query || query.trim().length == 0) { return

No selection specified. Open up the report settings and choose a node label and property.

} From f099193a9b041f28043a816aab0331eaa07dddfd Mon Sep 17 00:00:00 2001 From: JipSogeti <94383160+JipSogeti@users.noreply.github.com> Date: Mon, 27 Dec 2021 17:09:45 +0100 Subject: [PATCH 16/29] Pass Parameter Select value via hash to iframe without re-rendering (#49) * Pass Parameter Select value via hash to iframe without re-rendering * Pass all global variables to iframe via hash parameter Configurable via advanced settings on iframe chart. * Added example iFrame to documentation modal Co-authored-by: Niels de Jong --- public/embed-test.html | 35 +++++++++++++++ src/chart/IFrameChart.tsx | 15 ++++--- src/config/ExampleConfig.tsx | 14 ++++++ src/config/ReportConfig.tsx | 9 +++- src/modal/DocumentationModal.tsx | 75 +++++++++++++++++--------------- src/report/Report.tsx | 2 +- 6 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 public/embed-test.html diff --git a/public/embed-test.html b/public/embed-test.html new file mode 100644 index 000000000..937ad095b --- /dev/null +++ b/public/embed-test.html @@ -0,0 +1,35 @@ + + + + + Embed test + + + +

I am an iFrame of the page located at http://neodash.graphapp.io/embed-test.html

+

I'm embedded directly into a dashboard, and dynamically passed the user-made parameter selections.

+

I will not refresh when selections are updated, but, I can see variables change.

+

You can use me to embed external visualizations that are updated together with other charts.

+ Your dashboard variables: +

+    

+ + + + diff --git a/src/chart/IFrameChart.tsx b/src/chart/IFrameChart.tsx index 9f618675b..f3522728b 100644 --- a/src/chart/IFrameChart.tsx +++ b/src/chart/IFrameChart.tsx @@ -1,21 +1,24 @@ import React from 'react'; import { ChartProps } from './Chart'; -import ReactMarkdown from 'react-markdown'; -import gfm from 'remark-gfm'; /** * Renders an iFrame provided by the user. */ const NeoIFrameChart = (props: ChartProps) => { + const [extraRecords, setExtraRecords] = React.useState([]); // Records are overridden to be a single element array with a field called 'input'. const records = props.records; + const passGlobalParameters = props?.settings?.passGlobalParameters const url = records[0]["input"]; - if (url && (url.startsWith("http://") || url.startsWith("https://"))){ - return