Skip to content

Commit 61f1718

Browse files
committed
Added share dashboard functionality
1 parent b7b200d commit 61f1718

File tree

6 files changed

+343
-23
lines changed

6 files changed

+343
-23
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "neodash",
33
"description": "NeoDash - Neo4j Dashboard Builder",
4-
"version": "1.1.0",
4+
"version": "1.1.1",
55
"homepage": "./",
66
"neo4jDesktop": {
77
"apiVersion": "^1.2.0"
@@ -38,6 +38,7 @@
3838
"graphql": "^14.5.8",
3939
"graphql-tag": "^2.10.1",
4040
"jquery": "^3.5.1",
41+
"jsoncrush": "latest",
4142
"leaflet": "latest",
4243
"materialize-css": "^1.0.0",
4344
"neo4j-driver": "latest",

src/NeoDash.js

Lines changed: 203 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import NeoTextInput from "./component/NeoTextInput";
2121
import TextInput from "react-materialize/lib/TextInput";
2222
import Card from "react-materialize/lib/Card";
2323
import NeoTextButton from "./component/NeoTextButton";
24+
import NeoShareModal from "./component/NeoShareModal";
25+
import {JSONCrush} from "jsoncrush";
2426

2527

2628
/**
@@ -31,6 +33,8 @@ import NeoTextButton from "./component/NeoTextButton";
3133
* - Loading/storing dashboards as JSON (optionally from the browser cache)
3234
* - The creation, ordering and deleting of the NeoCard components.
3335
* - Propagating global parameter changes ("Selection" reports) to each of the cards.
36+
*
37+
* TODO: in many places we're modifying state directly. This is not a good practise, and should be refactored.
3438
*/
3539
class NeoDash extends React.Component {
3640
version = '1.1';
@@ -49,11 +53,13 @@ class NeoDash extends React.Component {
4953
// Attempt to load an existing dashboard state from the browser cache.
5054
this.loadDashboardfromBrowserCache();
5155

56+
5257
if (window.neo4jDesktopApi) {
5358
// Set the connection details from the Neo4j Desktop integration API.
5459
this.setConnectionDetailsFromDesktopIntegration();
5560
} else {
5661
// check the browser cache or use default connection values.
62+
5763
this.setConnectionDetailsFromBrowserCache();
5864
}
5965

@@ -83,8 +89,23 @@ class NeoDash extends React.Component {
8389
encryption: (localStorage.getItem('neodash-encryption')) ? localStorage.getItem('neodash-encryption') : 'off',
8490
}
8591

86-
this.createConnectionModal(this.connect, true);
87-
this.stateChanged({label: "HideError"})
92+
let urlParams = new URLSearchParams(window.location.search);
93+
if (urlParams.get("url") !== null) {
94+
this.state.jsonToLoad = urlParams.get("url");
95+
try {
96+
this.state.connectionToLoad = JSON.parse(atob(urlParams.get("connection")));
97+
} catch {
98+
// Unable to parse encoded JSON, don't set a connection
99+
this.state.connectionToLoad = null;
100+
}
101+
this.createExternalDashboardLoadPopupModal();
102+
this.stateChanged({})
103+
// String representations of the data to load, as encoded in hte URL.
104+
105+
} else {
106+
this.createConnectionModal(this.connect, true);
107+
this.stateChanged({label: "HideError"})
108+
}
88109
}
89110

90111
/**
@@ -120,9 +141,10 @@ class NeoDash extends React.Component {
120141

121142
removeDuplicateConnectionModal() {
122143
var select = document.getElementById('root');
123-
if (select.childNodes.length == 9) {
124-
select.removeChild(select.childNodes.item(1));
144+
if (select.childNodes.length == 11) {
145+
select.removeChild(select.childNodes.item(2));
125146
}
147+
126148
}
127149

128150
/**
@@ -158,6 +180,13 @@ class NeoDash extends React.Component {
158180
localStorage.setItem('neodash-password', this.connection.password.toString());
159181
localStorage.setItem('neodash-encryption', this.connection.encryption);
160182

183+
if (this.confirmation) {
184+
this.confirmation = false;
185+
this.stateChanged({
186+
label: "CreateError",
187+
value: "Dashboard loaded! You are connected to " + this.connection.url + "."
188+
})
189+
}
161190

162191
})
163192
.catch(error => {
@@ -174,7 +203,7 @@ class NeoDash extends React.Component {
174203
} finally {
175204

176205
}
177-
if (!this.connected){
206+
if (!this.connected) {
178207
this.createConnectionModal(this.connect, true);
179208
// TODO - this can produce duplicate connection modals
180209
}
@@ -200,11 +229,7 @@ class NeoDash extends React.Component {
200229
}
201230
// If a JSON string is available, try to parse it and set the state.
202231
try {
203-
let loaded = JSON.parse(this.state.json)
204-
// Quietly auto-upgrade to Neodash 1.1...
205-
if (this.version === "1.1" && loaded.version === "1.0"){
206-
this.upgradeDashboardJson(loaded);
207-
}
232+
let loaded = this.parseJson(this.state.json);
208233
if (loaded.version && loaded.version !== this.version) {
209234
this.stateChanged({
210235
label: "CreateError",
@@ -227,6 +252,15 @@ class NeoDash extends React.Component {
227252
}
228253
}
229254

255+
parseJson(text) {
256+
let loaded = JSON.parse(text);
257+
// Quietly auto-upgrade to Neodash 1.1...
258+
if (this.version === "1.1" && loaded.version === "1.0") {
259+
this.upgradeDashboardJson(loaded);
260+
}
261+
return loaded;
262+
}
263+
230264
upgradeDashboardJson(loaded) {
231265
loaded.version = "1.1";
232266
loaded.pages = [
@@ -324,6 +358,7 @@ class NeoDash extends React.Component {
324358
* @param update - a JSON dictionary {update, label} describing the change that was made.
325359
*/
326360
stateChanged(update) {
361+
console.log(update.label)
327362
if (update.label === "ConnectURLChanged") {
328363
this.connection.url = update.value;
329364
}
@@ -346,6 +381,45 @@ class NeoDash extends React.Component {
346381
this.buildJSONFromReportsState();
347382

348383
}
384+
385+
if (update.label === "LoadExternalDashboard") {
386+
fetch(this.state.jsonToLoad)
387+
.then(response => response.text())
388+
.then((data) => {
389+
// this.stateChanged({label: "HideError"})
390+
// this.createExternalDashboardLoadPopupModal()
391+
let parsedJson = this.parseJson(data);
392+
this.state.json = data;
393+
// this.buildJSONFromReportsState();
394+
// console.log(this.state.connectionToLoad)
395+
if (this.state.connectionToLoad) {
396+
this.connection = this.state.connectionToLoad;
397+
if (this.connection.password) {
398+
this.confirmation = true;
399+
this.connect();
400+
return;
401+
}
402+
}
403+
404+
this.stateChanged({
405+
label: "CreateError",
406+
value: "Dashboard loaded! You can now connect to Neo4j."
407+
})
408+
this.stateChanged({label: "OpenConnectionModal"})
409+
var select = document.getElementById('root');
410+
select.removeChild(select.childNodes.item(9));
411+
412+
413+
}).catch(error => {
414+
this.createConnectionModal(this.connect, true)
415+
this.stateChanged({label: "CreateError", value: error.toString()})
416+
417+
})
418+
}
419+
if (update.label === "OpenConnectionModal") {
420+
this.createConnectionModal(this.connect, true)
421+
}
422+
349423
if (update.label === "AskForDeletePage") {
350424
this.createPageDeletionPopupModal();
351425
this.state.count += 1;
@@ -429,6 +503,27 @@ class NeoDash extends React.Component {
429503
if (update.label !== "SaveModalUpdated") {
430504
this.buildJSONFromReportsState();
431505
}
506+
if (update.label === "ShareLinkURLChanged") {
507+
this.state.shareURL = encodeURIComponent(update.value);
508+
this.createShareURLConnectionDetails();
509+
}
510+
511+
if (update.label === "ShareLinkCredentialsChanged") {
512+
this.saveCredentialsInShareLink = !this.saveCredentialsInShareLink;
513+
this.createShareURLConnectionDetails();
514+
if (!this.saveCredentialsInShareLink) {
515+
this.state.shareURLConnectionDetails = null;
516+
}
517+
}
518+
if (update.label === "ShareLinkPasswordChanged") {
519+
this.savePasswordInShareLink = !this.savePasswordInShareLink;
520+
this.createShareURLConnectionDetails();
521+
522+
}
523+
524+
if (update.label === "ShareLinkGenerated") {
525+
this.createShareURLConnectionDetails();
526+
}
432527
this.setState(this.state);
433528
}
434529

@@ -579,6 +674,53 @@ class NeoDash extends React.Component {
579674
]}/>
580675
}
581676

677+
/**
678+
* Creates a pop-up window (Modal). Used for displaying errors and other notifications.
679+
*/
680+
createExternalDashboardLoadPopupModal() {
681+
let header = "NeoDash - Loading Dashboard";
682+
let displayURL = (this.state.jsonToLoad.length > 100) ?
683+
this.state.jsonToLoad.substring(0, 100) + "..." : this.state.jsonToLoad;
684+
685+
let content = <div>
686+
<p>You are loading a dashboard from: </p>
687+
<b><a href={this.state.jsonToLoad} target={"_blank"}>{displayURL}</a></b>
688+
{(this.state.connectionToLoad) ?
689+
<p>You will be connected to <b>{this.state.connectionToLoad.url}</b>.</p> : <></>
690+
}
691+
<p>This will overwrite your current dashboard (if present). Continue?</p>
692+
</div>
693+
694+
695+
// Create the modal object
696+
this.errorModal = <NeoModal header={header}
697+
open={true}
698+
trigger={null}
699+
content={content}
700+
key={this.state.count}
701+
id={this.state.count}
702+
root={document.getElementById("root")}
703+
actions={[
704+
<Button flat modal="close"
705+
node="button"
706+
onClick={e => this.stateChanged({label: "OpenConnectionModal"})}
707+
waves="red">Cancel</Button>,
708+
<NeoTextButton right modal="close"
709+
color={"white-color"}
710+
icon='play_arrow'
711+
node="button"
712+
modal="close"
713+
style={{backgroundColor: "green"}}
714+
onClick={e => {
715+
this.stateChanged({label: "HideError"})
716+
this.stateChanged({label: "LoadExternalDashboard"})
717+
}
718+
}
719+
text={"load"}
720+
waves="green"/>
721+
]}/>
722+
}
723+
582724
/**
583725
* Creates a pop-up window (Modal). Used for displaying errors and other notifications.
584726
*/
@@ -610,10 +752,14 @@ class NeoDash extends React.Component {
610752
*/
611753
handleSpecialCaseErrors(content, header) {
612754
var style = {}
755+
613756
// Special case 1: we're connecting to a database from Neo4j Desktop.
614757
if (content.startsWith("Trying to connect")) {
615758
header = "Connecting...";
616759
}
760+
if (content.startsWith("Dashboard loaded!")) {
761+
header = "Dashboard loaded 🎉";
762+
}
617763
if (content.startsWith("To save a dashboard")) {
618764
header = "Saving and Loading Dashboards";
619765
style = {paddingBottom: "650px"}
@@ -626,7 +772,7 @@ class NeoDash extends React.Component {
626772
content = "Unable to connect to the specified Neo4j database. " +
627773
"The database might be unreachable, or it does not accept " +
628774
((encryption === "on") ? "encrypted" : "unencrypted") + " connections. " + content;
629-
this.state.page +=1;
775+
this.state.page += 1;
630776

631777
}
632778
// Special case 3: we're dealing with someone clicking the 'Get in touch' button.
@@ -690,11 +836,12 @@ class NeoDash extends React.Component {
690836
* Create a modal (pop-up) that's used for saving/loading/exporting dashboards as JSON.
691837
*/
692838
createSaveLoadModal(loadJson) {
693-
let trigger = <NavItem href="" onClick={e => this.stateChanged({})}>Load/Export</NavItem>;
839+
let trigger = <NavItem href="" onClick={e => this.stateChanged({})}>Save/Load</NavItem>;
694840
return <NeoSaveLoadModal json={this.state.json}
695841
loadJson={loadJson}
696842
trigger={trigger}
697843
onQuestionMarkClicked={this.onConnectionHelpClicked}
844+
onCancel={e => this.stateChanged({})}
698845
value={this.state.json}
699846
placeholder={this.props.placeholder}
700847
change={e => {
@@ -708,6 +855,45 @@ class NeoDash extends React.Component {
708855
/>;
709856
}
710857

858+
/**
859+
* Create a modal (pop-up) that's used for saving/loading/exporting dashboards as JSON.
860+
*/
861+
createShareModal(loadJson) {
862+
let trigger = <NavItem href="#">Share</NavItem>;
863+
return <NeoShareModal json={this.props.json}
864+
loadJson={loadJson}
865+
trigger={trigger}
866+
connection={this.saveCredentialsInShareLink}
867+
password={this.savePasswordInShareLink}
868+
onQuestionMarkClicked={this.onConnectionHelpClicked}
869+
onCancel={e => this.stateChanged({})}
870+
value={this.state.shareURL}
871+
connectionValue={this.state.shareURLConnectionDetails}
872+
placeholder={this.props.placeholder}
873+
change={e => {
874+
this.state.json = e.target.value;
875+
this.setState(this.state)
876+
}}
877+
stateChanged={this.stateChanged}
878+
/>;
879+
}
880+
881+
882+
createShareURLConnectionDetails() {
883+
if (this.saveCredentialsInShareLink) {
884+
let password = (this.savePasswordInShareLink) ? this.connection.password : "";
885+
let connection =
886+
{
887+
url: this.connection.url,
888+
username: this.connection.username,
889+
password: password,
890+
database: this.connection.database,
891+
encryption: this.connection.encryption
892+
}
893+
this.state.shareURLConnectionDetails = btoa(JSON.stringify(connection));
894+
}
895+
}
896+
711897
/**
712898
*
713899
* @param connect - method used for opening the connection.
@@ -720,7 +906,7 @@ class NeoDash extends React.Component {
720906
connect={connect}
721907
connection={this.connection}
722908
stateChanged={this.stateChanged}
723-
navClicked={e => this.stateChanged({})}
909+
nfavClicked={e => this.stateChanged({})}
724910
onConnect={this.onConnectClicked(connect)}
725911
onGetInTouchClicked={this.onGetInTouchClicked()}
726912
/>
@@ -730,7 +916,7 @@ class NeoDash extends React.Component {
730916
/**
731917
* Creates the navigation bar of the dashboard.
732918
*/
733-
createDashboardNavbar(saveLoadModal) {
919+
createDashboardNavbar(saveLoadModal, shareModal) {
734920
let dashboardTitle = <Textarea disabled={!this.state.editable} noLayout={true}
735921
className="card-title editable-title"
736922
key={this.state.count}
@@ -806,7 +992,7 @@ class NeoDash extends React.Component {
806992
tabs
807993
}
808994
style={{backgroundColor: '#111'}}>
809-
{saveLoadModal}
995+
{saveLoadModal}{shareModal}
810996
{(this.neoConnectionModal) ? this.neoConnectionModal : <div></div>
811997
}
812998
</Navbar>;
@@ -874,8 +1060,9 @@ class NeoDash extends React.Component {
8741060
*/
8751061
render() {
8761062
let saveLoadModal = this.createSaveLoadModal(this.createCardObjectsFromDashboardState);
1063+
let shareModal = this.createShareModal(this.createCardObjectsFromDashboardState);
8771064
let cardsContainer = this.createCardsContainer()
878-
let navbar = this.createDashboardNavbar(saveLoadModal);
1065+
let navbar = this.createDashboardNavbar(saveLoadModal, shareModal);
8791066
let errorModal = (this.errorModal) ? this.errorModal : "";
8801067
var select = document.getElementById('root');
8811068
return (

0 commit comments

Comments
 (0)