Skip to content

Commit e87885a

Browse files
nielsdejongJipSogetiNiels de Jong
authored
2.0.4 Release (#52)
* Updated version number * Expandable charts when not in edit mode * 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. * Get parameter value earlier to apply as default state * Minor fixes to chart interface, reorganized code to be able to avoid uninitialized variables * Added example on how to use maps when returning dictionaries * Made connection modal dismissable when connected to Neo4j * Made fullscreen reports work for maps and lines, now optionally available through dashboard settings * Fixed load dashboard functionality to automatically pick up the selection stored in the dashboard * Added button for returning to main menu screen * Fixed z-index for fullscreen mode * Revert some changes and add more comments * Resolved rendering issues when loading parameter selection reports with prepopulated parameters * Show placeholder when no query is specified * Reset extra parameters on page load for parameter select report * 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 <[email protected]> * Cleanup of global dashboard settings menu * Improved user interface and examples * Graph modal (#51) * Show modal when clicking node/relation in graph with its properties * Made node property hover & inspection of elements optional through advanced report settings Co-authored-by: Niels de Jong <[email protected]> * Resolved bug in rendering graphs with node pairs that have relationships in two directions between them * Added option to manually specify node labels/property names in selection reports (for large databases) * Updated release notes Co-authored-by: JipSogeti <[email protected]> Co-authored-by: Niels de Jong <[email protected]>
1 parent ad2b5a2 commit e87885a

33 files changed

+524
-178
lines changed

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.2",
3+
"version": "2.0.4",
44
"description": "NeoDash - Neo4j Dashboard Builder",
55
"neo4jDesktop": {
66
"apiVersion": "^1.2.0"

public/embed-test.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Embed test</title>
6+
</head>
7+
8+
<body>
9+
<p>I am an iFrame of the page located at <a href="http://neodash.graphapp.io/embed-test.html" target="_blank">http://neodash.graphapp.io/embed-test.html</a></p>
10+
<p>I'm embedded directly into a dashboard, and dynamically passed the user-made parameter selections.</p>
11+
<p>I will not refresh when selections are updated, but, I can see variables change.</p>
12+
<p>You can use me to embed external visualizations that are updated together with other charts.</p>
13+
<b>Your dashboard variables:</b>
14+
<pre></pre>
15+
<p>
16+
</body>
17+
18+
<script>
19+
var pre = document.querySelector('pre');
20+
21+
var applyHash = function() {
22+
const hashValue = window.location.hash.substr(1);
23+
var output = "";
24+
25+
searchParams = new URLSearchParams(hashValue);
26+
searchParams.forEach(function(value, key) {
27+
output += key + ": " + value + "\n"
28+
});
29+
pre.textContent = output;
30+
}
31+
32+
window.onhashchange = applyHash;
33+
applyHash();
34+
</script>
35+
</html>

public/style.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,14 @@
120120

121121
#center-aligned {
122122
text-align: center;
123-
}
123+
}
124+
125+
.card-view.expanded {
126+
position: absolute;
127+
top: 0;
128+
left: 0;
129+
right: 0;
130+
bottom: 0;
131+
background: white;
132+
z-index: 1299;
133+
}

release-notes.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
## NeoDash 2.0.4
2+
New features:
3+
- Added option dashboard setting to let users view reports in a fullscreen pop-up.
4+
- Added inspection pop-up for graph visualizations.
5+
- Added option to manually specify node labels/property names in parameter selection reports (for large databases).
6+
- Added example of how to user map visualizations from derived properties.
7+
- Added button to return to the welcome screen.
8+
- iFrames can now take live parameter selections in the hash-part of the URL.
9+
10+
Bug fixes:
11+
- Dashboards will now remember the active selection(s) made in parameter select reports.
12+
- Graph visualizations will no longer draw overlapping lines when a pair of nodes shares bidirectional relationships.
13+
- connection screen is now dismissable if an existing connection exists.
14+
15+
Special thanks to @JipSogeti for their contributions to this release.
16+
117
## NeoDash 2.0.3
218
UX improvements + bug fixes.
319
- Parameter selection report:

src/application/Application.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const Application = ({ connection, connected, hasCachedDashboard, oldDashboard,
5252
</NeoAboutModal>
5353
<NeoConnectionModal
5454
open={connectionModalOpen}
55+
dismissable={connected}
5556
connection={connection}
5657
createConnection={createConnection}
5758
onConnectionModalClose={onConnectionModalClose} ></NeoConnectionModal>

src/card/Card.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const NeoCard = ({
2121
editable, // whether the card is editable.
2222
database, // the neo4j database that the card is running against.
2323
globalParameters, // Query parameters that are globally set for the entire dashboard.
24+
dashboardSettings, // Dictionary of settings for the entire dashboard.
2425
onRemovePressed, // action to take when the card is removed. (passed from parent)
2526
onShiftLeftPressed, // action to take when the card is shifted left.
2627
onShiftRightPressed, // action to take when the card is shifted right.
@@ -62,6 +63,7 @@ const NeoCard = ({
6263
<NeoCardView
6364
settingsOpen={settingsOpen}
6465
editable={editable}
66+
dashboardSettings={dashboardSettings}
6567
settings={report.settings ? report.settings : {}}
6668
type={report.type}
6769
database={database}

src/card/CardThunks.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,21 @@ export const updateFieldsThunk = (index, fields) => (dispatch: any, getState: an
9696
}
9797

9898
} else {
99-
10099
if (fields.length > 0) {
101100
// For multi selections, select the Nth item of the result fields as a single item array.
102-
if (selectableFields[selection].multiple) {
103-
dispatch(updateSelection(pagenumber, index, selection, [fields[Math.min(i, fields.length - 1)]]));
101+
if (selectableFields[selection].multiple) {
102+
// only update if the old selection no longer covers the new set of fields...
103+
if(!oldSelection[selection].every(v => fields.includes(v))){
104+
dispatch(updateSelection(pagenumber, index, selection, [fields[Math.min(i, fields.length - 1)]]));
105+
}
106+
104107
} else if (selectableFields[selection].type == SELECTION_TYPES.NODE_PROPERTIES) {
105108
// For node property selections, select the most obvious properties of the node to display.
106109
const selection = {};
107110
fields.forEach(nodeLabelAndProperties => {
108111
const label = nodeLabelAndProperties[0];
109112
const properties = nodeLabelAndProperties.slice(1);
110-
var selectedProp = undefined;
113+
var selectedProp = oldSelection[label] ? oldSelection[label] : undefined;
111114
if(autoAssignSelectedProperties){
112115
DEFAULT_NODE_LABELS.forEach(prop => {
113116
if(properties.indexOf(prop) !== -1){

src/card/settings/CardSettings.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const NeoCardSettings = ({ settingsOpen, query, database, refreshRate, cypherPar
2323
query={query}
2424
database={database}
2525
refreshRate={refreshRate}
26+
reportSettings={reportSettings}
2627
cypherParameters={cypherParameters}
2728
width={width}
2829
height={height}

src/card/settings/CardSettingsContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import NeoCodeField from '../../component/EditableCodeField';
99
import { CARD_SIZES } from '../../config/CardConfig';
1010

1111

12-
const NeoCardSettingsContent = ({ query, database, refreshRate, cypherParameters, width, height, type,
12+
const NeoCardSettingsContent = ({ query, database, reportSettings, refreshRate, cypherParameters, width, height, type,
1313
onQueryUpdate, onSizeUpdate, onRefreshRateUpdate, onCypherParametersUpdate, onTypeUpdate }) => {
1414

1515

@@ -54,7 +54,7 @@ const NeoCardSettingsContent = ({ query, database, refreshRate, cypherParameters
5454

5555

5656
const SettingsComponent = REPORT_TYPES[type].settingsComponent;
57-
const settings = REPORT_TYPES[type]["settingsComponent"] ? <SettingsComponent type={type} database={database} query={query} onQueryUpdate={onQueryUpdate} /> :
57+
const settings = REPORT_TYPES[type]["settingsComponent"] ? <SettingsComponent type={type} settings={reportSettings} database={database} query={query} onQueryUpdate={onQueryUpdate} /> :
5858
<>
5959
<NeoCodeField value={queryText}
6060
language={REPORT_TYPES[type]["inputMode"] ? REPORT_TYPES[type]["inputMode"] : "cypher"}

src/card/settings/custom/CardSettingsContentPropertySelect.tsx

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { debounce, TextField } from '@material-ui/core';
77
import { Autocomplete } from '@material-ui/lab';
88
import NeoFieldSelection from '../../../component/FieldSelection';
99

10-
const NeoCardSettingsContentPropertySelect = ({ type, database, query, onQueryUpdate }) => {
10+
const NeoCardSettingsContentPropertySelect = ({ type, database, settings, query, onQueryUpdate }) => {
1111
const { driver } = useContext<Neo4jContextState>(Neo4jContext);
1212
if (!driver) throw new Error('`driver` not defined. Have you added it into your app as <Neo4jContext.Provider value={{driver}}> ?')
1313

@@ -17,23 +17,24 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, query, onQueryUp
1717
);
1818

1919

20+
const manualPropertyNameSpecification = settings['manualPropertyNameSpecification'];
2021
const label = query.split("`")[1] ? query.split("`")[1] : undefined;
2122
const property = query.split("`")[3] ? query.split("`")[3] : undefined;
22-
23+
2324
const [labelInputText, setLabelInputText] = React.useState(label);
2425
const [labelValue, setLabelValue] = React.useState(label);
2526
const [labelRecords, setLabelRecords] = React.useState([]);
2627
const [propertyInputText, setPropertyInputText] = React.useState(property);
2728
const [propertyValue, setPropertyValue] = React.useState(property);
2829
const [parameterName, setParameterName] = React.useState("");
2930
const [propertyRecords, setPropertyRecords] = React.useState([]);
30-
31+
3132
// Reverse engineer the label, property, ID from the generated query.
3233
const approxParam = query.split("\n")[0].split("$")[1];
33-
const id = (approxParam && approxParam.split("_").length > 3) && !isNaN(parseInt(approxParam.split("_")[approxParam.split("_").length-1])) ? approxParam.split("_")[approxParam.split("_").length-1] : "";
34+
const id = (approxParam && approxParam.split("_").length > 3) && !isNaN(parseInt(approxParam.split("_")[approxParam.split("_").length - 1])) ? approxParam.split("_")[approxParam.split("_").length - 1] : "";
3435
const [idValue, setIdValue] = React.useState(id);
35-
if(!parameterName && labelValue && propertyValue){
36-
setParameterName("neodash_" + (labelValue + "_" + propertyValue + (idValue == "" || idValue.startsWith("_") ? idValue : "_" + idValue)).toLowerCase().replaceAll(" ","_").replaceAll("-", "_"));
36+
if (!parameterName && labelValue && propertyValue) {
37+
setParameterName("neodash_" + (labelValue + "_" + propertyValue + (idValue == "" || idValue.startsWith("_") ? idValue : "_" + idValue)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_"));
3738
}
3839
// Define query callback to allow reports to get extra data on interactions.
3940
const queryCallback = useCallback(
@@ -48,76 +49,84 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, query, onQueryUp
4849
[],
4950
);
5051

52+
function handleNodeLabelSelectionUpdate(newValue) {
53+
setLabelValue(newValue);
54+
setPropertyValue(undefined);
55+
setParameterName("");
56+
if (newValue && propertyValue) {
57+
const new_parameter_name = "neodash_" + (newValue + "_" + propertyValue + (idValue == "" || idValue.startsWith("_") ? idValue : "_" + idValue)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
58+
const newQuery = "// $" + new_parameter_name + "\nMATCH (n:`" + newValue + "`) \nWHERE toLower(toString(n.`" + propertyValue + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + propertyValue + "` as value LIMIT 5";
59+
onQueryUpdate(newQuery);
60+
} else {
61+
setParameterName("");
62+
}
63+
}
5164

65+
function handlePropertyNameSelectionUpdate(newValue) {
66+
setPropertyValue(newValue);
67+
if (newValue && labelValue) {
68+
const new_parameter_name = "neodash_" + (labelValue + "_" + newValue + (idValue == "" || idValue.startsWith("_") ? idValue : "_" + idValue)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
69+
setParameterName(new_parameter_name);
70+
const newQuery = "// $" + new_parameter_name + "\nMATCH (n:`" + labelValue + "`) \nWHERE toLower(toString(n.`" + newValue + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + newValue + "` as value LIMIT 5";
71+
onQueryUpdate(newQuery);
72+
} else {
73+
setParameterName(null);
74+
}
75+
}
5276

5377
return <div>
5478
<p style={{ color: "grey", fontSize: 12, paddingLeft: "5px", border: "1px solid lightgrey", marginTop: "0px" }}>
5579
{REPORT_TYPES[type].helperText}
5680
</p>
5781
<Autocomplete
5882
id="autocomplete-label"
59-
options={labelRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
60-
getOptionLabel={(option) => option}
83+
options={manualPropertyNameSpecification ? [labelValue] : labelRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
84+
getOptionLabel={(option) => option ? option : ""}
6185
style={{ width: 350, marginLeft: "5px", marginTop: "0px" }}
6286
inputValue={labelInputText}
6387
onInputChange={(event, value) => {
6488
setLabelInputText(value);
65-
queryCallback("CALL db.schema.nodeTypeProperties() YIELD nodeLabels UNWIND nodeLabels as nodeLabel WITH nodeLabel WHERE toLower(nodeLabel) CONTAINS toLower($input) RETURN DISTINCT nodeLabel LIMIT 5", { input: value }, setLabelRecords);
66-
}}
67-
value={labelValue}
68-
onChange={(event, newValue) => {
69-
setLabelValue(newValue);
70-
setPropertyValue(undefined);
71-
setParameterName("");
72-
if (newValue && propertyValue) {
73-
const new_parameter_name = "neodash_" + (newValue + "_" + propertyValue + (idValue == "" || idValue.startsWith("_") ? idValue : "_" + idValue)).toLowerCase().replaceAll(" ","_").replaceAll("-", "_");
74-
// setParameterName(new_parameter_name);
75-
const newQuery = "// $" + new_parameter_name + "\nMATCH (n:`" + newValue + "`) \nWHERE toLower(toString(n.`" + propertyValue + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + propertyValue + "` as value LIMIT 5";
76-
onQueryUpdate(newQuery);
89+
if (manualPropertyNameSpecification) {
90+
handleNodeLabelSelectionUpdate(value);
7791
} else {
78-
setParameterName("");
92+
queryCallback("CALL db.schema.nodeTypeProperties() YIELD nodeLabels UNWIND nodeLabels as nodeLabel WITH nodeLabel WHERE toLower(nodeLabel) CONTAINS toLower($input) RETURN DISTINCT nodeLabel LIMIT 5", { input: value }, setLabelRecords);
7993
}
8094
}}
95+
value={labelValue}
96+
onChange={(event, newValue) => handleNodeLabelSelectionUpdate(newValue)}
8197
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={"Node Label"} />}
8298
/>
8399
{labelValue ? <><Autocomplete
84100
id="autocomplete-property"
85-
options={propertyRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
86-
getOptionLabel={(option) => option}
101+
options={manualPropertyNameSpecification ? [propertyValue] : propertyRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
102+
getOptionLabel={(option) => option ? option : ""}
87103
style={{ display: "inline-block", width: 200, marginLeft: "5px", marginTop: "5px" }}
88104
inputValue={propertyInputText}
89105
onInputChange={(event, value) => {
90106
setPropertyInputText(value);
91-
queryCallback("CALL db.schema.nodeTypeProperties() YIELD nodeLabels, propertyName WITH * WHERE $label IN nodeLabels AND toLower(propertyName) CONTAINS toLower($input) RETURN DISTINCT propertyName LIMIT 5", { label: labelValue, input: value }, setPropertyRecords);
92-
}}
93-
value={propertyValue}
94-
onChange={(event, newValue) => {
95-
setPropertyValue(newValue);
96-
97-
if (newValue && labelValue) {
98-
const new_parameter_name = "neodash_" + (labelValue + "_" + newValue + (idValue == "" || idValue.startsWith("_") ? idValue : "_" + idValue)).toLowerCase().replaceAll(" ","_").replaceAll("-", "_");
99-
setParameterName(new_parameter_name);
100-
const newQuery = "// $" + new_parameter_name + "\nMATCH (n:`" + labelValue + "`) \nWHERE toLower(toString(n.`" + newValue + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + newValue + "` as value LIMIT 5";
101-
onQueryUpdate(newQuery);
107+
if (manualPropertyNameSpecification) {
108+
handlePropertyNameSelectionUpdate(value);
102109
} else {
103-
setParameterName(null);
110+
queryCallback("CALL db.schema.nodeTypeProperties() YIELD nodeLabels, propertyName WITH * WHERE $label IN nodeLabels AND toLower(propertyName) CONTAINS toLower($input) RETURN DISTINCT propertyName LIMIT 5", { label: labelValue, input: value }, setPropertyRecords);
104111
}
105112
}}
106-
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={"Property Name"} />}
113+
value={propertyValue}
114+
onChange={(event, newValue) => handlePropertyNameSelectionUpdate(newValue)}
115+
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={"Property Name"} />}
107116
/>
108-
<NeoFieldSelection placeholder='number'
109-
label="Number (optional)" numeric={true} value={idValue}
110-
style={{ width: "140px", marginTop: "5px", marginLeft: "10px" }}
111-
onChange={(value) => {
112-
const newValue = value ? "_" + value : "";
113-
setIdValue(value);
114-
if (propertyValue && labelValue) {
115-
const new_parameter_name = "neodash_" + (labelValue + "_" + propertyValue + newValue).toLowerCase().replaceAll(" ","_").replaceAll("-", "_");
116-
setParameterName(new_parameter_name);
117-
const newQuery = "// $" + new_parameter_name + "\nMATCH (n:`" + labelValue + "`) \nWHERE toLower(toString(n.`" + propertyValue + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + propertyValue + "` as value LIMIT 5";
118-
onQueryUpdate(newQuery);
119-
}
120-
}} /></> : <></>}
117+
<NeoFieldSelection placeholder='number'
118+
label="Number (optional)" numeric={true} value={idValue}
119+
style={{ width: "140px", marginTop: "5px", marginLeft: "10px" }}
120+
onChange={(value) => {
121+
const newValue = value ? "_" + value : "";
122+
setIdValue(value);
123+
if (propertyValue && labelValue) {
124+
const new_parameter_name = "neodash_" + (labelValue + "_" + propertyValue + newValue).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
125+
setParameterName(new_parameter_name);
126+
const newQuery = "// $" + new_parameter_name + "\nMATCH (n:`" + labelValue + "`) \nWHERE toLower(toString(n.`" + propertyValue + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + propertyValue + "` as value LIMIT 5";
127+
onQueryUpdate(newQuery);
128+
}
129+
}} /></> : <></>}
121130
{parameterName ? <p>Use <b>${parameterName}</b> in a query to use the parameter.</p> : <></>}
122131

123132
</div>;

0 commit comments

Comments
 (0)