Skip to content

Commit

Permalink
refactor dashboard unsaved listing to show entries when loading
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomThomson committed Jan 29, 2021
1 parent 0190c26 commit efd5b3f
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 49 deletions.
4 changes: 4 additions & 0 deletions src/plugins/dashboard/public/application/_dashboard_app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
margin-bottom: 0 !important;
}

.dshUnsavedListingItem__loading {
color: $euiTextSubduedColor !important;
}

.dshUnsavedListingItem__actions {
margin-left: $euiSizeL + $euiSizeXS;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export class DashboardStateManager {
>;
private readonly stateContainerChangeSub: Subscription;
private readonly dashboardPanelStorage?: DashboardPanelStorage;
private readonly STATE_STORAGE_KEY = '_a';
public readonly kbnUrlStateStorage: IKbnUrlStateStorage;
private readonly stateSyncRef: ISyncStateRef;
private readonly allowByValueEmbeddables: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
EuiTitle,
} from '@elastic/eui';
import React, { useCallback, useEffect, useState } from 'react';
import useMount from 'react-use/lib/useMount';
import { DashboardSavedObject } from '../..';
import {
createConfirmStrings,
Expand All @@ -29,15 +28,16 @@ import { DashboardAppServices, DashboardRedirect } from '../types';
import { confirmDiscardUnsavedChanges } from './confirm_overlays';

const DashboardUnsavedItem = ({
dashboard,
id,
title,
onOpenClick,
onDiscardClick,
}: {
dashboard?: DashboardSavedObject;
id: string;
title?: string;
onOpenClick: () => void;
onDiscardClick: () => void;
}) => {
const title = dashboard?.title ?? getNewDashboardTitle();
return (
<div className="dshUnsavedListingItem">
<EuiFlexGroup
Expand All @@ -47,12 +47,20 @@ const DashboardUnsavedItem = ({
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiIcon color="text" className="dshUnsavedListingItem__icon" type="dashboardApp" />
<EuiIcon
color="text"
className="dshUnsavedListingItem__icon"
type={title ? 'dashboardApp' : 'clock'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs">
<h4 className="dshUnsavedListingItem__title">
{dashboard?.title ?? getNewDashboardTitle()}
<h4
className={`dshUnsavedListingItem__title ${
title ? '' : 'dshUnsavedListingItem__loading'
}`}
>
{title || dashboardUnsavedListingStrings.getLoadingTitle()}
</h4>
</EuiTitle>
</EuiFlexItem>
Expand All @@ -68,9 +76,10 @@ const DashboardUnsavedItem = ({
flush="left"
size="s"
color="primary"
disabled={!title}
onClick={onOpenClick}
data-test-subj={`edit-unsaved-${title.split(' ').join('-')}`}
aria-label={dashboardUnsavedListingStrings.getEditAriaLabel(title)}
data-test-subj={title ? `edit-unsaved-${title.split(' ').join('-')}` : undefined}
aria-label={dashboardUnsavedListingStrings.getEditAriaLabel(title ?? id)}
>
{dashboardUnsavedListingStrings.getEditTitle()}
</EuiButtonEmpty>
Expand All @@ -80,9 +89,10 @@ const DashboardUnsavedItem = ({
flush="left"
size="s"
color="danger"
disabled={!title}
onClick={onDiscardClick}
data-test-subj={`discard-unsaved-${title.split(' ').join('-')}`}
aria-label={dashboardUnsavedListingStrings.getDiscardAriaLabel(title)}
data-test-subj={title ? `discard-unsaved-${title.split(' ').join('-')}` : undefined}
aria-label={dashboardUnsavedListingStrings.getDiscardAriaLabel(title ?? id)}
>
{dashboardUnsavedListingStrings.getDiscardTitle()}
</EuiButtonEmpty>
Expand All @@ -92,6 +102,10 @@ const DashboardUnsavedItem = ({
);
};

interface UnsavedItemMap {
[key: string]: DashboardSavedObject;
}

export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardRedirect }) => {
const {
services: {
Expand All @@ -101,8 +115,11 @@ export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardR
},
} = useKibana<DashboardAppServices>();

const [items, setItems] = useState<JSX.Element[]>([]);
const [dashboardIds, setDashboardIds] = useState<string[]>([]);
const [items, setItems] = useState<UnsavedItemMap>({});
const [mounted, setMounted] = useState(true);
const [dashboardIds, setDashboardIds] = useState<string[]>(
dashboardPanelStorage.getDashboardIdsWithUnsavedChanges()
);

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

useMount(() => {
setDashboardIds(dashboardPanelStorage.getDashboardIdsWithUnsavedChanges());
useEffect(() => {
return () => setMounted(false);
});

useEffect(() => {
let hasNewDashboard = false;
const dashPromises = dashboardIds
.filter((id) => {
if (id !== DASHBOARD_PANELS_UNSAVED_ID) {
return true;
}
hasNewDashboard = true;
return false;
})
.filter((id) => id !== DASHBOARD_PANELS_UNSAVED_ID)
.map((dashboardId) => savedDashboards.get(dashboardId));
Promise.all(dashPromises).then((dashboards: DashboardSavedObject[]) => {
const newItems = dashboards.map((dashboard) => (
<DashboardUnsavedItem
key={dashboard.id}
dashboard={dashboard}
onOpenClick={() => onOpen(dashboard.id)}
onDiscardClick={() => onDiscard(dashboard.id)}
/>
));
if (hasNewDashboard) {
newItems.unshift(
<DashboardUnsavedItem
key={DASHBOARD_PANELS_UNSAVED_ID}
onOpenClick={() => onOpen()}
onDiscardClick={() => onDiscard()}
/>
);
const dashboardMap = {};
if (!mounted) {
return;
}
setItems(newItems);
setItems(
dashboards.reduce((map, dashboard) => {
return {
...map,
[dashboard.id || DASHBOARD_PANELS_UNSAVED_ID]: dashboard,
};
}, dashboardMap)
);
});
}, [dashboardIds, onOpen, onDiscard, savedDashboards]);
}, [dashboardIds, savedDashboards, mounted]);

return items.length === 0 ? null : (
return dashboardIds.length === 0 ? null : (
<>
<EuiCallOut
heading="h3"
title={dashboardUnsavedListingStrings.getUnsavedChangesTitle(items.length > 1)}
title={dashboardUnsavedListingStrings.getUnsavedChangesTitle(dashboardIds.length > 1)}
>
{items}
{dashboardIds.map((dashboardId: string) => {
const title: string | undefined =
dashboardId === DASHBOARD_PANELS_UNSAVED_ID
? getNewDashboardTitle()
: items[dashboardId]?.title;
const redirectId = dashboardId === DASHBOARD_PANELS_UNSAVED_ID ? undefined : dashboardId;
return (
<DashboardUnsavedItem
key={dashboardId}
id={dashboardId}
title={title}
onOpenClick={() => onOpen(redirectId)}
onDiscardClick={() => onDiscard(redirectId)}
/>
);
})}
</EuiCallOut>
<EuiSpacer size="m" />
</>
Expand Down
20 changes: 16 additions & 4 deletions src/plugins/dashboard/public/dashboard_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* Public License, v 1.
*/

import { ViewMode } from './services/embeddable';
import { setStateToKbnUrl } from './services/kibana_utils';

const DASHBOARD_STATE_STORAGE_KEY = '_a';

export const DashboardConstants = {
Expand All @@ -20,11 +23,20 @@ export const DashboardConstants = {
};

export function createDashboardEditUrl(id?: string, editMode?: boolean) {
const edit = editMode ? `?${DASHBOARD_STATE_STORAGE_KEY}=(viewMode:edit)` : '';
if (id) {
return `${DashboardConstants.VIEW_DASHBOARD_URL}/${id}${edit}`;
if (!id) {
return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`;
}
const viewUrl = `${DashboardConstants.VIEW_DASHBOARD_URL}/${id}`;
if (editMode) {
const editUrl = setStateToKbnUrl(
DASHBOARD_STATE_STORAGE_KEY,
{ viewMode: ViewMode.EDIT },
{ useHash: false, storeInHashQuery: false },
viewUrl
);
return editUrl;
}
return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}${edit}`;
return viewUrl;
}

export function createDashboardListingFilterUrl(filter: string | undefined) {
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/dashboard/public/dashboard_strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ export const dashboardUnsavedListingStrings = {
: dashboardListingTable.getEntityName(),
},
}),
getLoadingTitle: () =>
i18n.translate('dashboard.listing.unsaved.loading', {
defaultMessage: 'Loading',
}),
getEditAriaLabel: (title: string) =>
i18n.translate('dashboard.listing.unsaved.editAria', {
defaultMessage: 'Continue editing {title}',
Expand Down

0 comments on commit efd5b3f

Please sign in to comment.