Skip to content

Commit efd5b3f

Browse files
committed
refactor dashboard unsaved listing to show entries when loading
1 parent 0190c26 commit efd5b3f

5 files changed

Lines changed: 87 additions & 49 deletions

File tree

src/plugins/dashboard/public/application/_dashboard_app.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
margin-bottom: 0 !important;
4747
}
4848

49+
.dshUnsavedListingItem__loading {
50+
color: $euiTextSubduedColor !important;
51+
}
52+
4953
.dshUnsavedListingItem__actions {
5054
margin-left: $euiSizeL + $euiSizeXS;
5155
}

src/plugins/dashboard/public/application/dashboard_state_manager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ export class DashboardStateManager {
7878
>;
7979
private readonly stateContainerChangeSub: Subscription;
8080
private readonly dashboardPanelStorage?: DashboardPanelStorage;
81-
private readonly STATE_STORAGE_KEY = '_a';
8281
public readonly kbnUrlStateStorage: IKbnUrlStateStorage;
8382
private readonly stateSyncRef: ISyncStateRef;
8483
private readonly allowByValueEmbeddables: boolean;

src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
EuiTitle,
1717
} from '@elastic/eui';
1818
import React, { useCallback, useEffect, useState } from 'react';
19-
import useMount from 'react-use/lib/useMount';
2019
import { DashboardSavedObject } from '../..';
2120
import {
2221
createConfirmStrings,
@@ -29,15 +28,16 @@ import { DashboardAppServices, DashboardRedirect } from '../types';
2928
import { confirmDiscardUnsavedChanges } from './confirm_overlays';
3029

3130
const DashboardUnsavedItem = ({
32-
dashboard,
31+
id,
32+
title,
3333
onOpenClick,
3434
onDiscardClick,
3535
}: {
36-
dashboard?: DashboardSavedObject;
36+
id: string;
37+
title?: string;
3738
onOpenClick: () => void;
3839
onDiscardClick: () => void;
3940
}) => {
40-
const title = dashboard?.title ?? getNewDashboardTitle();
4141
return (
4242
<div className="dshUnsavedListingItem">
4343
<EuiFlexGroup
@@ -47,12 +47,20 @@ const DashboardUnsavedItem = ({
4747
responsive={false}
4848
>
4949
<EuiFlexItem grow={false}>
50-
<EuiIcon color="text" className="dshUnsavedListingItem__icon" type="dashboardApp" />
50+
<EuiIcon
51+
color="text"
52+
className="dshUnsavedListingItem__icon"
53+
type={title ? 'dashboardApp' : 'clock'}
54+
/>
5155
</EuiFlexItem>
5256
<EuiFlexItem grow={false}>
5357
<EuiTitle size="xxs">
54-
<h4 className="dshUnsavedListingItem__title">
55-
{dashboard?.title ?? getNewDashboardTitle()}
58+
<h4
59+
className={`dshUnsavedListingItem__title ${
60+
title ? '' : 'dshUnsavedListingItem__loading'
61+
}`}
62+
>
63+
{title || dashboardUnsavedListingStrings.getLoadingTitle()}
5664
</h4>
5765
</EuiTitle>
5866
</EuiFlexItem>
@@ -68,9 +76,10 @@ const DashboardUnsavedItem = ({
6876
flush="left"
6977
size="s"
7078
color="primary"
79+
disabled={!title}
7180
onClick={onOpenClick}
72-
data-test-subj={`edit-unsaved-${title.split(' ').join('-')}`}
73-
aria-label={dashboardUnsavedListingStrings.getEditAriaLabel(title)}
81+
data-test-subj={title ? `edit-unsaved-${title.split(' ').join('-')}` : undefined}
82+
aria-label={dashboardUnsavedListingStrings.getEditAriaLabel(title ?? id)}
7483
>
7584
{dashboardUnsavedListingStrings.getEditTitle()}
7685
</EuiButtonEmpty>
@@ -80,9 +89,10 @@ const DashboardUnsavedItem = ({
8089
flush="left"
8190
size="s"
8291
color="danger"
92+
disabled={!title}
8393
onClick={onDiscardClick}
84-
data-test-subj={`discard-unsaved-${title.split(' ').join('-')}`}
85-
aria-label={dashboardUnsavedListingStrings.getDiscardAriaLabel(title)}
94+
data-test-subj={title ? `discard-unsaved-${title.split(' ').join('-')}` : undefined}
95+
aria-label={dashboardUnsavedListingStrings.getDiscardAriaLabel(title ?? id)}
8696
>
8797
{dashboardUnsavedListingStrings.getDiscardTitle()}
8898
</EuiButtonEmpty>
@@ -92,6 +102,10 @@ const DashboardUnsavedItem = ({
92102
);
93103
};
94104

105+
interface UnsavedItemMap {
106+
[key: string]: DashboardSavedObject;
107+
}
108+
95109
export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardRedirect }) => {
96110
const {
97111
services: {
@@ -101,8 +115,11 @@ export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardR
101115
},
102116
} = useKibana<DashboardAppServices>();
103117

104-
const [items, setItems] = useState<JSX.Element[]>([]);
105-
const [dashboardIds, setDashboardIds] = useState<string[]>([]);
118+
const [items, setItems] = useState<UnsavedItemMap>({});
119+
const [mounted, setMounted] = useState(true);
120+
const [dashboardIds, setDashboardIds] = useState<string[]>(
121+
dashboardPanelStorage.getDashboardIdsWithUnsavedChanges()
122+
);
106123

107124
const onOpen = useCallback(
108125
(id?: string) => {
@@ -125,50 +142,52 @@ export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardR
125142
[overlays, dashboardPanelStorage]
126143
);
127144

128-
useMount(() => {
129-
setDashboardIds(dashboardPanelStorage.getDashboardIdsWithUnsavedChanges());
145+
useEffect(() => {
146+
return () => setMounted(false);
130147
});
131148

132149
useEffect(() => {
133-
let hasNewDashboard = false;
134150
const dashPromises = dashboardIds
135-
.filter((id) => {
136-
if (id !== DASHBOARD_PANELS_UNSAVED_ID) {
137-
return true;
138-
}
139-
hasNewDashboard = true;
140-
return false;
141-
})
151+
.filter((id) => id !== DASHBOARD_PANELS_UNSAVED_ID)
142152
.map((dashboardId) => savedDashboards.get(dashboardId));
143153
Promise.all(dashPromises).then((dashboards: DashboardSavedObject[]) => {
144-
const newItems = dashboards.map((dashboard) => (
145-
<DashboardUnsavedItem
146-
key={dashboard.id}
147-
dashboard={dashboard}
148-
onOpenClick={() => onOpen(dashboard.id)}
149-
onDiscardClick={() => onDiscard(dashboard.id)}
150-
/>
151-
));
152-
if (hasNewDashboard) {
153-
newItems.unshift(
154-
<DashboardUnsavedItem
155-
key={DASHBOARD_PANELS_UNSAVED_ID}
156-
onOpenClick={() => onOpen()}
157-
onDiscardClick={() => onDiscard()}
158-
/>
159-
);
154+
const dashboardMap = {};
155+
if (!mounted) {
156+
return;
160157
}
161-
setItems(newItems);
158+
setItems(
159+
dashboards.reduce((map, dashboard) => {
160+
return {
161+
...map,
162+
[dashboard.id || DASHBOARD_PANELS_UNSAVED_ID]: dashboard,
163+
};
164+
}, dashboardMap)
165+
);
162166
});
163-
}, [dashboardIds, onOpen, onDiscard, savedDashboards]);
167+
}, [dashboardIds, savedDashboards, mounted]);
164168

165-
return items.length === 0 ? null : (
169+
return dashboardIds.length === 0 ? null : (
166170
<>
167171
<EuiCallOut
168172
heading="h3"
169-
title={dashboardUnsavedListingStrings.getUnsavedChangesTitle(items.length > 1)}
173+
title={dashboardUnsavedListingStrings.getUnsavedChangesTitle(dashboardIds.length > 1)}
170174
>
171-
{items}
175+
{dashboardIds.map((dashboardId: string) => {
176+
const title: string | undefined =
177+
dashboardId === DASHBOARD_PANELS_UNSAVED_ID
178+
? getNewDashboardTitle()
179+
: items[dashboardId]?.title;
180+
const redirectId = dashboardId === DASHBOARD_PANELS_UNSAVED_ID ? undefined : dashboardId;
181+
return (
182+
<DashboardUnsavedItem
183+
key={dashboardId}
184+
id={dashboardId}
185+
title={title}
186+
onOpenClick={() => onOpen(redirectId)}
187+
onDiscardClick={() => onDiscard(redirectId)}
188+
/>
189+
);
190+
})}
172191
</EuiCallOut>
173192
<EuiSpacer size="m" />
174193
</>

src/plugins/dashboard/public/dashboard_constants.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
* Public License, v 1.
77
*/
88

9+
import { ViewMode } from './services/embeddable';
10+
import { setStateToKbnUrl } from './services/kibana_utils';
11+
912
const DASHBOARD_STATE_STORAGE_KEY = '_a';
1013

1114
export const DashboardConstants = {
@@ -20,11 +23,20 @@ export const DashboardConstants = {
2023
};
2124

2225
export function createDashboardEditUrl(id?: string, editMode?: boolean) {
23-
const edit = editMode ? `?${DASHBOARD_STATE_STORAGE_KEY}=(viewMode:edit)` : '';
24-
if (id) {
25-
return `${DashboardConstants.VIEW_DASHBOARD_URL}/${id}${edit}`;
26+
if (!id) {
27+
return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`;
28+
}
29+
const viewUrl = `${DashboardConstants.VIEW_DASHBOARD_URL}/${id}`;
30+
if (editMode) {
31+
const editUrl = setStateToKbnUrl(
32+
DASHBOARD_STATE_STORAGE_KEY,
33+
{ viewMode: ViewMode.EDIT },
34+
{ useHash: false, storeInHashQuery: false },
35+
viewUrl
36+
);
37+
return editUrl;
2638
}
27-
return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}${edit}`;
39+
return viewUrl;
2840
}
2941

3042
export function createDashboardListingFilterUrl(filter: string | undefined) {

src/plugins/dashboard/public/dashboard_strings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ export const dashboardUnsavedListingStrings = {
358358
: dashboardListingTable.getEntityName(),
359359
},
360360
}),
361+
getLoadingTitle: () =>
362+
i18n.translate('dashboard.listing.unsaved.loading', {
363+
defaultMessage: 'Loading',
364+
}),
361365
getEditAriaLabel: (title: string) =>
362366
i18n.translate('dashboard.listing.unsaved.editAria', {
363367
defaultMessage: 'Continue editing {title}',

0 commit comments

Comments
 (0)