Skip to content

Commit ac8eda1

Browse files
authored
Improved handling of draft dashboards in the sidebar, allowing to distinguish between save/overwrite (#734)
* Improved handling of draft dashboards in the sidebar, allowing to distinguish between logical saves/overwrites * Ordering dashboard list by name rather than edit date
1 parent 48fe130 commit ac8eda1

File tree

7 files changed

+81
-45
lines changed

7 files changed

+81
-45
lines changed

src/dashboard/DashboardThunks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ export const loadDashboardListFromNeo4jThunk = (driver, database, callback) => (
502502
runCypherQuery(
503503
driver,
504504
database,
505-
'MATCH (n:_Neodash_Dashboard) RETURN n.uuid as uuid, n.title as title, toString(n.date) as date, n.user as author, n.version as version ORDER BY date DESC',
505+
'MATCH (n:_Neodash_Dashboard) RETURN n.uuid as uuid, n.title as title, toString(n.date) as date, n.user as author, n.version as version ORDER BY toLower(n.title) ASC',
506506
{},
507507
1000,
508508
(status) => setStatus(status),

src/dashboard/sidebar/DashboardSidebar.tsx

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ import NeoDashboardSidebarShareModal from './modal/DashboardSidebarShareModal';
4141
import LegacyShareModal from './modal/legacy/LegacyShareModal';
4242
import { NEODASH_VERSION } from '../DashboardReducer';
4343

44+
// Which (small) pop-up menu is currently open for the sidebar.
4445
enum Menu {
4546
DASHBOARD = 0,
4647
DATABASE = 1,
4748
CREATE = 2,
4849
NONE = 3,
4950
}
5051

52+
// Which (large) pop-up modal is currently open for the sidebar.
5153
enum Modal {
5254
CREATE = 0,
5355
IMPORT = 1,
@@ -61,6 +63,9 @@ enum Modal {
6163
NONE = 9,
6264
}
6365

66+
// We use "index = -1" to represent a non-saved draft dashboard in the sidebar's dashboard list.
67+
const UNSAVED_DASHBOARD_INDEX = -1;
68+
6469
/**
6570
* A component responsible for rendering the sidebar on the left of the screen.
6671
*/
@@ -83,10 +88,10 @@ export const NeoDashboardSidebar = ({
8388
}) => {
8489
const { driver } = useContext<Neo4jContextState>(Neo4jContext);
8590
const [expanded, setOnExpanded] = useState(false);
86-
const [selectedDashboardIndex, setSelectedDashboardIndex] = React.useState(-1);
91+
const [selectedDashboardIndex, setSelectedDashboardIndex] = React.useState(UNSAVED_DASHBOARD_INDEX);
8792
const [dashboardDatabase, setDashboardDatabase] = React.useState(database ? database : 'neo4j');
8893
const [databases, setDatabases] = useState([]);
89-
const [inspectedIndex, setInspectedIndex] = useState(-1);
94+
const [inspectedIndex, setInspectedIndex] = useState(UNSAVED_DASHBOARD_INDEX);
9095
const [searchText, setSearchText] = useState('');
9196
const [menuAnchor, setMenuAnchor] = useState<HTMLElement | null>(null);
9297
const [menuOpen, setMenuOpen] = useState(Menu.NONE);
@@ -103,7 +108,7 @@ export const NeoDashboardSidebar = ({
103108
if (dashboard && dashboard.uuid) {
104109
const index = list.findIndex((element) => element.uuid == dashboard.uuid);
105110
setSelectedDashboardIndex(index);
106-
if (index == -1) {
111+
if (index == UNSAVED_DASHBOARD_INDEX) {
107112
// If we can't find the currently dashboard in the database, we are drafting a new one.
108113
setDraft(true);
109114
}
@@ -121,8 +126,9 @@ export const NeoDashboardSidebar = ({
121126
// Creates new dashboard in draft state (not yet saved to Neo4j)
122127
deleteDashboardFromNeo4j(driver, dashboardDatabase, uuid, () => {
123128
if (uuid == dashboard.uuid) {
124-
setSelectedDashboardIndex(0);
129+
setSelectedDashboardIndex(UNSAVED_DASHBOARD_INDEX);
125130
resetLocalDashboard();
131+
loadDashboardListFromNeo4j();
126132
setDraft(true);
127133
}
128134
setTimeout(() => {
@@ -154,19 +160,27 @@ export const NeoDashboardSidebar = ({
154160
}
155161
);
156162
}}
163+
overwrite={selectedDashboardIndex >= 0}
157164
handleClose={() => setModalOpen(Modal.NONE)}
158165
/>
159166

160167
<NeoDashboardSidebarLoadModal
161168
open={modalOpen == Modal.LOAD}
162169
onConfirm={() => {
163-
setModalOpen(Modal.LOAD);
164-
const { uuid } = dashboards[inspectedIndex];
165-
loadDashboardFromNeo4j(driver, dashboardDatabase, uuid, (file) => {
166-
setDraft(false);
167-
loadDashboard(uuid, file);
168-
setSelectedDashboardIndex(inspectedIndex);
169-
});
170+
if (inspectedIndex == UNSAVED_DASHBOARD_INDEX) {
171+
// Someone attempted to load the unsaved draft dashboard... this isn't possible, we create a fresh one.
172+
setSelectedDashboardIndex(UNSAVED_DASHBOARD_INDEX);
173+
createDashboard();
174+
} else {
175+
// Load one of the dashboards from the database.
176+
setModalOpen(Modal.LOAD);
177+
const { uuid } = dashboards[inspectedIndex];
178+
loadDashboardFromNeo4j(driver, dashboardDatabase, uuid, (file) => {
179+
setDraft(false);
180+
loadDashboard(uuid, file);
181+
setSelectedDashboardIndex(inspectedIndex);
182+
});
183+
}
170184
}}
171185
handleClose={() => setModalOpen(Modal.NONE)}
172186
/>
@@ -190,6 +204,7 @@ export const NeoDashboardSidebar = ({
190204
onConfirm={() => {
191205
setModalOpen(Modal.NONE);
192206
createDashboard();
207+
setSelectedDashboardIndex(UNSAVED_DASHBOARD_INDEX);
193208
}}
194209
handleClose={() => setModalOpen(Modal.NONE)}
195210
/>
@@ -211,6 +226,7 @@ export const NeoDashboardSidebar = ({
211226
onImport={(text) => {
212227
setModalOpen(Modal.NONE);
213228
setDraft(true);
229+
setSelectedDashboardIndex(UNSAVED_DASHBOARD_INDEX);
214230
loadDashboard(createUUID(), text);
215231
}}
216232
handleClose={() => setModalOpen(Modal.NONE)}
@@ -271,6 +287,7 @@ export const NeoDashboardSidebar = ({
271287
}}
272288
/>
273289
<NeoDashboardSidebarDashboardMenu
290+
draft={draft && selectedDashboardIndex == inspectedIndex}
274291
open={menuOpen == Menu.DASHBOARD}
275292
anchorEl={menuAnchor}
276293
handleInfoClicked={() => {
@@ -281,6 +298,14 @@ export const NeoDashboardSidebar = ({
281298
});
282299
setModalOpen(Modal.INFO);
283300
}}
301+
handleDiscardClicked={() => {
302+
setMenuOpen(Menu.NONE);
303+
setModalOpen(Modal.LOAD);
304+
}}
305+
handleSaveClicked={() => {
306+
setMenuOpen(Menu.NONE);
307+
setModalOpen(Modal.SAVE);
308+
}}
284309
handleLoadClicked={() => {
285310
setMenuOpen(Menu.NONE);
286311
if (draft) {
@@ -323,6 +348,7 @@ export const NeoDashboardSidebar = ({
323348
if (draft) {
324349
setModalOpen(Modal.CREATE);
325350
} else {
351+
setSelectedDashboardIndex(UNSAVED_DASHBOARD_INDEX);
326352
createDashboard();
327353
}
328354
}}
@@ -425,15 +451,18 @@ export const NeoDashboardSidebar = ({
425451
onChange={(e) => setSearchText(e.target.value)}
426452
/>
427453
</SideNavigationGroupHeader>
428-
{draft && !readonly ? (
454+
{draft && selectedDashboardIndex == UNSAVED_DASHBOARD_INDEX && !readonly ? (
429455
<DashboardSidebarListItem
430456
version={NEODASH_VERSION}
431457
selected={draft}
432458
title={title}
433459
saved={false}
434460
onSelect={() => {}}
435-
onSave={() => setModalOpen(Modal.SAVE)}
436-
onSettingsOpen={() => {}}
461+
onSettingsOpen={(event) => {
462+
setInspectedIndex(UNSAVED_DASHBOARD_INDEX);
463+
setMenuOpen(Menu.DASHBOARD);
464+
setMenuAnchor(event.currentTarget);
465+
}}
437466
/>
438467
) : (
439468
<></>
@@ -444,13 +473,13 @@ export const NeoDashboardSidebar = ({
444473
// index stored in list
445474
return (
446475
<DashboardSidebarListItem
447-
selected={!draft && selectedDashboardIndex == d.index}
448-
title={d.title}
476+
selected={selectedDashboardIndex == d.index}
477+
title={draft && selectedDashboardIndex == d.index ? title : d.title}
449478
version={d.version}
450-
saved={true}
479+
saved={!(draft && selectedDashboardIndex == d.index)}
451480
readonly={readonly}
452481
onSelect={() => {
453-
if (draft) {
482+
if (draft && d.index !== selectedDashboardIndex) {
454483
setInspectedIndex(d.index);
455484
setModalOpen(Modal.LOAD);
456485
} else {
@@ -460,7 +489,6 @@ export const NeoDashboardSidebar = ({
460489
});
461490
}
462491
}}
463-
onSave={() => {}}
464492
onSettingsOpen={(event) => {
465493
setInspectedIndex(d.index);
466494
setMenuOpen(Menu.DASHBOARD);

src/dashboard/sidebar/DashboardSidebarListItem.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,7 @@ import { CloudArrowUpIconOutline, EllipsisVerticalIconOutline } from '@neo4j-ndl
44
import Tooltip from '@mui/material/Tooltip';
55
import { NEODASH_VERSION } from '../DashboardReducer';
66

7-
export const DashboardSidebarListItem = ({
8-
title,
9-
selected,
10-
readonly,
11-
saved,
12-
version,
13-
onSelect,
14-
onSave,
15-
onSettingsOpen,
16-
}) => {
7+
export const DashboardSidebarListItem = ({ title, selected, readonly, saved, version, onSelect, onSettingsOpen }) => {
178
return (
189
<SideNavigationGroupHeader>
1910
<div style={{ display: 'contents', width: '100%' }}>
@@ -56,7 +47,7 @@ export const DashboardSidebarListItem = ({
5647
marginRight: '10px',
5748
}}
5849
onClick={(event) => {
59-
saved == false ? onSave() : onSettingsOpen(event);
50+
saved == false ? onSettingsOpen(event) : onSettingsOpen(event);
6051
}}
6152
>
6253
{saved == true ? (
@@ -67,8 +58,8 @@ export const DashboardSidebarListItem = ({
6758
/>
6859
</Tooltip>
6960
) : (
70-
<Tooltip title='Save' aria-label='save' disableInteractive>
71-
<CloudArrowUpIconOutline
61+
<Tooltip title='Settings' aria-label='settings' disableInteractive>
62+
<EllipsisVerticalIconOutline
7263
color='rgb(var(--palette-warning-text))'
7364
style={{ float: 'right', marginRight: '-6px' }}
7465
className='btn-icon-base-r'

src/dashboard/sidebar/menu/DashboardSidebarDashboardMenu.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
import React from 'react';
22
import { Menu, MenuItem, MenuItems } from '@neo4j-ndl/react';
33
import {
4+
ArchiveBoxIconOutline,
5+
ArrowUturnLeftIconOutline,
46
CloudArrowUpIconOutline,
57
DocumentDuplicateIconOutline,
68
DocumentTextIconOutline,
79
InformationCircleIconOutline,
810
ShareIconOutline,
911
TrashIconOutline,
12+
XMarkIconOutline,
1013
} from '@neo4j-ndl/react/icons';
1114

1215
/**
1316
* Configures setting the current Neo4j database connection for the dashboard.
1417
*/
1518
export const NeoDashboardSidebarDashboardMenu = ({
1619
anchorEl,
20+
draft,
1721
open,
1822
handleInfoClicked,
23+
handleSaveClicked,
24+
handleDiscardClicked,
1925
handleLoadClicked,
2026
handleExportClicked,
2127
handleShareClicked,
@@ -37,14 +43,21 @@ export const NeoDashboardSidebarDashboardMenu = ({
3743
onClose={handleClose}
3844
size='small'
3945
>
40-
<MenuItems>
41-
<MenuItem onClick={handleInfoClicked} icon={<InformationCircleIconOutline />} title='Info' />
42-
<MenuItem onClick={handleLoadClicked} icon={<CloudArrowUpIconOutline />} title='Load' />
43-
{/* <MenuItem onClick={() => {}} icon={<DocumentDuplicateIconOutline />} title='Clone' /> */}
44-
<MenuItem onClick={handleExportClicked} icon={<DocumentTextIconOutline />} title='Export' />
45-
<MenuItem onClick={handleShareClicked} icon={<ShareIconOutline />} title='Share' />
46-
<MenuItem onClick={handleDeleteClicked} icon={<TrashIconOutline />} title='Delete' />
47-
</MenuItems>
46+
{!draft ? (
47+
<MenuItems>
48+
<MenuItem onClick={handleInfoClicked} icon={<InformationCircleIconOutline />} title='Info' />
49+
<MenuItem onClick={handleLoadClicked} icon={<CloudArrowUpIconOutline />} title='Load' />
50+
{/* <MenuItem onClick={() => {}} icon={<DocumentDuplicateIconOutline />} title='Clone' /> */}
51+
<MenuItem onClick={handleExportClicked} icon={<DocumentTextIconOutline />} title='Export' />
52+
<MenuItem onClick={handleShareClicked} icon={<ShareIconOutline />} title='Share' />
53+
<MenuItem onClick={handleDeleteClicked} icon={<TrashIconOutline />} title='Delete' />
54+
</MenuItems>
55+
) : (
56+
<MenuItems>
57+
<MenuItem onClick={handleSaveClicked} icon={<CloudArrowUpIconOutline />} title='Save' />
58+
<MenuItem onClick={handleDiscardClicked} icon={<ArrowUturnLeftIconOutline />} title='Discard Draft' />
59+
</MenuItems>
60+
)}
4861
</Menu>
4962
);
5063
};

src/dashboard/sidebar/modal/DashboardSidebarLoadModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export const NeoDashboardSidebarLoadModal = ({ open, onConfirm, handleClose }) =
1010
<Dialog size='small' open={open == true} onClose={handleClose} aria-labelledby='form-dialog-title'>
1111
<Dialog.Header id='form-dialog-title'>Discard Draft?</Dialog.Header>
1212
<Dialog.Subtitle>
13-
Switching your active dashboard will delete your current draft.
13+
You are discarding your current draft dashboard.
1414
<br />
15-
<b>Save the draft first to ensure your dashboard is stored.</b>
15+
<b>Your draft will not be recoverable.</b>
1616
</Dialog.Subtitle>
1717
<Dialog.Actions>
1818
<Button

src/dashboard/sidebar/modal/DashboardSidebarSaveModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { Button, Dialog } from '@neo4j-ndl/react';
55
/**
66
* Configures setting the current Neo4j database connection for the dashboard.
77
*/
8-
export const NeoDashboardSidebarSaveModal = ({ open, onConfirm, handleClose }) => {
8+
export const NeoDashboardSidebarSaveModal = ({ open, onConfirm, handleClose, overwrite }) => {
99
return (
1010
<Dialog size='small' open={open} onClose={handleClose} aria-labelledby='form-dialog-title'>
1111
<Dialog.Header id='form-dialog-title'>Save to Neo4j</Dialog.Header>
1212
<Dialog.Content>
13-
This will save your current draft as a node to your Neo4j database.
13+
This will <b>{overwrite ? 'overwrite' : 'save'}</b> your current draft as a node in your Neo4j database.
1414
<br />
1515
Ensure you have write permissions to the database to use this feature.
1616
</Dialog.Content>

src/report/ReportQueryRunner.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export async function runCypherQuery(
6161
setStatus(QueryStatus.NO_QUERY);
6262
return;
6363
}
64+
if (!driver) {
65+
setStatus(QueryStatus.ERROR);
66+
return;
67+
}
6468
const session = database ? driver.session({ database: database }) : driver.session();
6569
const transaction = session.beginTransaction({ timeout: queryTimeLimit * 1000, connectionTimeout: 2000 });
6670

0 commit comments

Comments
 (0)