[Dashboard] Redesign empty screen in readonly mode (#54073)

* [Dashboard] Make empty screen nicer in readonly mode

* Adding contact-the-owner part

* Updating text
This commit is contained in:
Maja Grubic 2020-01-07 14:41:02 +00:00 committed by GitHub
parent 8ac233f303
commit ab76ee5a58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 443 additions and 27 deletions

View file

@ -1,5 +1,367 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
<DashboardEmptyScreen
http={
Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
"isAnonymous": [MockFunction],
"register": [MockFunction],
},
"basePath": BasePath {
"basePath": "",
"get": [Function],
"prepend": [Function],
"remove": [Function],
},
"delete": [MockFunction],
"fetch": [MockFunction],
"get": [MockFunction],
"getLoadingCount$": [MockFunction],
"head": [MockFunction],
"intercept": [MockFunction],
"options": [MockFunction],
"patch": [MockFunction],
"post": [MockFunction],
"put": [MockFunction],
}
}
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"relative": Object {
"days": Object {
"units": "day",
},
"hours": Object {
"units": "hour",
},
"minutes": Object {
"units": "minute",
},
"months": Object {
"units": "month",
},
"seconds": Object {
"units": "second",
},
"years": Object {
"units": "year",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
isReadonlyMode={true}
onLinkClick={[MockFunction]}
showLinkToVisualize={true}
uiSettings={
Object {
"get": [MockFunction] {
"calls": Array [
Array [
"theme:darkMode",
],
Array [
"theme:darkMode",
],
Array [
"theme:darkMode",
],
Array [
"theme:darkMode",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
"get$": [MockFunction],
"getAll": [MockFunction],
"getSaved$": [MockFunction],
"getUpdate$": [MockFunction],
"getUpdateErrors$": [MockFunction],
"isCustom": [MockFunction],
"isDeclared": [MockFunction],
"isDefault": [MockFunction],
"isOverridden": [MockFunction],
"overrideLocalDefault": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
}
}
>
<I18nProvider>
<IntlProvider
defaultLocale="en"
formats={
Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"relative": Object {
"days": Object {
"units": "day",
},
"hours": Object {
"units": "hour",
},
"minutes": Object {
"units": "minute",
},
"months": Object {
"units": "month",
},
"seconds": Object {
"units": "second",
},
"years": Object {
"units": "year",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
}
}
locale="en"
messages={Object {}}
textComponent={Symbol(react.fragment)}
>
<PseudoLocaleWrapper>
<EuiPage
className="dshStartScreen"
restrictWidth="500px"
>
<div
className="euiPage euiPage--restrictWidth-custom dshStartScreen"
style={
Object {
"maxWidth": "500px",
}
}
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageContent
className="dshStartScreen__pageContent"
horizontalPosition="center"
paddingSize="none"
verticalPosition="center"
>
<EuiPanel
className="euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter dshStartScreen__pageContent"
paddingSize="none"
>
<div
className="euiPanel euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter dshStartScreen__pageContent"
>
<EuiImage
alt=""
url="/plugins/kibana/home/assets/welcome_graphic_light_2x.png"
>
<figure
className="euiImage"
role="figure"
>
<img
alt=""
className="euiImage__img"
src="/plugins/kibana/home/assets/welcome_graphic_light_2x.png"
/>
</figure>
</EuiImage>
<EuiText
size="m"
>
<div
className="euiText euiText--medium"
>
<p
style={
Object {
"fontWeight": "bold",
}
}
>
This dashboard is empty.
</p>
</div>
</EuiText>
<EuiText
color="subdued"
size="m"
>
<div
className="euiText euiText--medium"
>
<EuiTextColor
color="subdued"
component="div"
>
<div
className="euiTextColor euiTextColor--subdued"
>
You need additional privileges to edit this dashboard.
</div>
</EuiTextColor>
</div>
</EuiText>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</PseudoLocaleWrapper>
</IntlProvider>
</I18nProvider>
</DashboardEmptyScreen>
`;
exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = `
<DashboardEmptyScreen
http={

View file

@ -38,8 +38,7 @@ describe('DashboardEmptyScreen', () => {
function mountComponent(props?: DashboardEmptyScreenProps) {
const compProps = props || defaultProps;
const comp = mountWithIntl(<DashboardEmptyScreen {...compProps} />);
return comp;
return mountWithIntl(<DashboardEmptyScreen {...compProps} />);
}
test('renders correctly with visualize paragraph', () => {
@ -52,8 +51,10 @@ describe('DashboardEmptyScreen', () => {
test('renders correctly without visualize paragraph', () => {
const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } });
expect(component).toMatchSnapshot();
const paragraph = findTestSubject(component, 'linkToVisualizeParagraph');
expect(paragraph.length).toBe(0);
const linkToVisualizeParagraph = findTestSubject(component, 'linkToVisualizeParagraph');
expect(linkToVisualizeParagraph.length).toBe(0);
const enterEditModeParagraph = component.find('.dshStartScreen__panelDesc');
expect(enterEditModeParagraph.length).toBe(1);
});
test('when specified, prop onVisualizeClick is called correctly', () => {
@ -66,4 +67,11 @@ describe('DashboardEmptyScreen', () => {
button.simulate('click');
expect(onVisualizeClick).toHaveBeenCalled();
});
test('renders correctly with readonly mode', () => {
const component = mountComponent({ ...defaultProps, ...{ isReadonlyMode: true } });
expect(component).toMatchSnapshot();
const paragraph = component.find('.dshStartScreen__panelDesc');
expect(paragraph.length).toBe(0);
});
});

View file

@ -161,6 +161,12 @@ export class DashboardAppController {
dashboardStateManager.getIsViewMode() &&
!dashboardConfig.getHideWriteControls();
const getIsEmptyInReadonlyMode = () =>
!dashboardStateManager.getPanels().length &&
!getShouldShowEditHelp() &&
!getShouldShowViewHelp() &&
dashboardConfig.getHideWriteControls();
const addVisualization = () => {
navActions[TopNavIds.VISUALIZE]();
};
@ -193,7 +199,10 @@ export class DashboardAppController {
}
};
const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => {
const getEmptyScreenProps = (
shouldShowEditHelp: boolean,
isEmptyInReadOnlyMode: boolean
): DashboardEmptyScreenProps => {
const emptyScreenProps: DashboardEmptyScreenProps = {
onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode,
showLinkToVisualize: shouldShowEditHelp,
@ -203,6 +212,9 @@ export class DashboardAppController {
if (shouldShowEditHelp) {
emptyScreenProps.onVisualizeClick = addVisualization;
}
if (isEmptyInReadOnlyMode) {
emptyScreenProps.isReadonlyMode = true;
}
return emptyScreenProps;
};
@ -219,6 +231,7 @@ export class DashboardAppController {
}
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
const isEmptyInReadonlyMode = getIsEmptyInReadonlyMode();
return {
id: dashboardStateManager.savedDashboard.id || '',
filters: queryFilter.getFilters(),
@ -231,7 +244,7 @@ export class DashboardAppController {
viewMode: dashboardStateManager.getViewMode(),
panels: embeddablesMap,
isFullScreenMode: dashboardStateManager.getFullScreenMode(),
isEmptyState: shouldShowEditHelp || shouldShowViewHelp,
isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode,
useMargins: dashboardStateManager.getUseMargins(),
lastReloadRequestTime,
title: dashboardStateManager.getTitle(),
@ -275,9 +288,12 @@ export class DashboardAppController {
dashboardContainer.renderEmpty = () => {
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
const isEmptyState = shouldShowEditHelp || shouldShowViewHelp;
const isEmptyInReadOnlyMode = getIsEmptyInReadonlyMode();
const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
return isEmptyState ? (
<DashboardEmptyScreen {...getEmptyScreenProps(shouldShowEditHelp)} />
<DashboardEmptyScreen
{...getEmptyScreenProps(shouldShowEditHelp, isEmptyInReadOnlyMode)}
/>
) : null;
};

View file

@ -37,6 +37,7 @@ export interface DashboardEmptyScreenProps {
onVisualizeClick?: () => void;
uiSettings: IUiSettingsClient;
http: HttpStart;
isReadonlyMode?: boolean;
}
export function DashboardEmptyScreen({
@ -45,6 +46,7 @@ export function DashboardEmptyScreen({
onVisualizeClick,
uiSettings,
http,
isReadonlyMode,
}: DashboardEmptyScreenProps) {
const IS_DARK_THEME = uiSettings.get('theme:darkMode');
const emptyStateGraphicURL = IS_DARK_THEME
@ -98,25 +100,42 @@ export function DashboardEmptyScreen({
constants.addExistingVisualizationLinkText,
constants.addExistingVisualizationLinkAriaLabel
);
const viewMode = (
<EuiPage className="dshStartScreen" restrictWidth="500px">
<EuiPageBody>
<EuiPageContent
verticalPosition="center"
horizontalPosition="center"
paddingSize="none"
className="dshStartScreen__pageContent"
>
<EuiImage url={http.basePath.prepend(emptyStateGraphicURL)} alt="" />
<EuiText size="m">
<p style={{ fontWeight: 'bold' }}>{constants.fillDashboardTitle}</p>
</EuiText>
<EuiSpacer size="m" />
<div className="dshStartScreen__panelDesc">{enterEditModeParagraph}</div>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
const page = (mainText: string, showAdditionalParagraph?: boolean, additionalText?: string) => {
return (
<EuiPage className="dshStartScreen" restrictWidth="500px">
<EuiPageBody>
<EuiPageContent
verticalPosition="center"
horizontalPosition="center"
paddingSize="none"
className="dshStartScreen__pageContent"
>
<EuiImage url={http.basePath.prepend(emptyStateGraphicURL)} alt="" />
<EuiText size="m">
<p style={{ fontWeight: 'bold' }}>{mainText}</p>
</EuiText>
{additionalText ? (
<EuiText size="m" color="subdued">
{additionalText}
</EuiText>
) : null}
{showAdditionalParagraph ? (
<React.Fragment>
<EuiSpacer size="m" />
<div className="dshStartScreen__panelDesc">{enterEditModeParagraph}</div>
</React.Fragment>
) : null}
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};
const readonlyMode = page(
constants.emptyDashboardTitle,
false,
constants.emptyDashboardAdditionalPrivilege
);
const viewMode = page(constants.fillDashboardTitle, true);
const editMode = (
<div data-test-subj="emptyDashboardWidget" className="dshEmptyWidget">
{enterViewModeParagraph}
@ -124,5 +143,6 @@ export function DashboardEmptyScreen({
{linkToVisualizeParagraph}
</div>
);
return <I18nProvider>{showLinkToVisualize ? editMode : viewMode}</I18nProvider>;
const actionableMode = showLinkToVisualize ? editMode : viewMode;
return <I18nProvider>{isReadonlyMode ? readonlyMode : actionableMode}</I18nProvider>;
}

View file

@ -19,6 +19,16 @@
import { i18n } from '@kbn/i18n';
/** READONLY VIEW CONSTANTS **/
export const emptyDashboardTitle: string = i18n.translate('kbn.dashboard.emptyDashboardTitle', {
defaultMessage: 'This dashboard is empty.',
});
export const emptyDashboardAdditionalPrivilege = i18n.translate(
'kbn.dashboard.emptyDashboardAdditionalPrivilege',
{
defaultMessage: 'You need additional privileges to edit this dashboard.',
}
);
/** VIEW MODE CONSTANTS **/
export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', {
defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!',