[Maps] Show minusInCircle icon when layer has no data (#36457)

* [Maps] Show minusInCircle icon when layer has no data

* rename getTooltipContent so its less ambigiuos

* Update x-pack/plugins/maps/public/shared/layers/layer.js

Co-Authored-By: gchaps <33642766+gchaps@users.noreply.github.com>

* review feedback
This commit is contained in:
Nathan Reese 2019-05-15 11:10:40 -06:00 committed by GitHub
parent ef8d7ea905
commit 2791becd27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 150 additions and 557 deletions

View file

@ -6,6 +6,7 @@ exports[`LayerTocActions is rendered 1`] = `
anchorPosition="leftUp"
button={
<EuiToolTip
content="simulated tooltip content at zoom: 0"
delay="regular"
position="top"
title="layer 1"
@ -23,9 +24,9 @@ exports[`LayerTocActions is rendered 1`] = `
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
<span>
mockIcon
</span>
</span>
layer 1
</EuiButtonEmpty>
@ -101,6 +102,7 @@ exports[`LayerTocActions should disable fit to data when supportsFitToBounds is
anchorPosition="leftUp"
button={
<EuiToolTip
content="simulated tooltip content at zoom: 0"
delay="regular"
position="top"
title="layer 1"
@ -118,9 +120,9 @@ exports[`LayerTocActions should disable fit to data when supportsFitToBounds is
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
<span>
mockIcon
</span>
</span>
layer 1
</EuiButtonEmpty>
@ -190,107 +192,13 @@ exports[`LayerTocActions should disable fit to data when supportsFitToBounds is
</EuiPopover>
`;
exports[`LayerTocActions should display spinner when layer is loading 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiToolTip
delay="regular"
position="top"
title="layer 1"
>
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<span
className="mapTocEntry__layerNameIcon"
>
<EuiLoadingSpinner
size="m"
/>
</span>
layer 1
</EuiButtonEmpty>
</EuiToolTip>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
isOpen={false}
ownFocus={false}
panelPaddingSize="none"
withTitle={true}
>
<EuiContextMenu
data-test-subj="layerTocActionsPanellayer1"
initialPanelId={0}
panels={
Array [
Object {
"id": 0,
"items": Array [
Object {
"data-test-subj": "fitToBoundsButton",
"disabled": false,
"icon": <EuiIcon
size="m"
type="search"
/>,
"name": "Fit to data",
"onClick": [Function],
"toolTipContent": null,
},
Object {
"data-test-subj": "layerVisibilityToggleButton",
"icon": <EuiIcon
size="m"
type="eye"
/>,
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
size="m"
type="copy"
/>,
"name": "Clone layer",
"onClick": [Function],
},
],
"title": "Layer actions",
},
]
}
/>
</EuiPopover>
`;
exports[`LayerTocActions should not show edit actions in read only mode 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiToolTip
content="simulated tooltip content at zoom: 0"
delay="regular"
position="top"
title="layer 1"
@ -308,9 +216,9 @@ exports[`LayerTocActions should not show edit actions in read only mode 1`] = `
<span
className="mapTocEntry__layerNameIcon"
>
<div>
icon mock
</div>
<span>
mockIcon
</span>
</span>
layer 1
</EuiButtonEmpty>
@ -361,296 +269,3 @@ exports[`LayerTocActions should not show edit actions in read only mode 1`] = `
/>
</EuiPopover>
`;
exports[`LayerTocActions should show visible toggle when layer is not visible 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiToolTip
content="Layer is hidden."
delay="regular"
position="top"
title="layer 1"
>
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<span
className="mapTocEntry__layerNameIcon"
>
<EuiIcon
size="m"
type="eyeClosed"
/>
</span>
layer 1
</EuiButtonEmpty>
</EuiToolTip>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
isOpen={false}
ownFocus={false}
panelPaddingSize="none"
withTitle={true}
>
<EuiContextMenu
data-test-subj="layerTocActionsPanellayer1"
initialPanelId={0}
panels={
Array [
Object {
"id": 0,
"items": Array [
Object {
"data-test-subj": "fitToBoundsButton",
"disabled": false,
"icon": <EuiIcon
size="m"
type="search"
/>,
"name": "Fit to data",
"onClick": [Function],
"toolTipContent": null,
},
Object {
"data-test-subj": "layerVisibilityToggleButton",
"icon": <EuiIcon
size="m"
type="eyeClosed"
/>,
"name": "Show layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
size="m"
type="copy"
/>,
"name": "Clone layer",
"onClick": [Function],
},
],
"title": "Layer actions",
},
]
}
/>
</EuiPopover>
`;
exports[`LayerTocActions should show warning when layer has errors 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiToolTip
content="simulated layer error"
delay="regular"
position="top"
title="layer 1"
>
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<span
className="mapTocEntry__layerNameIcon"
>
<EuiIcon
aria-label="Load warning"
color="warning"
size="m"
type="alert"
/>
</span>
layer 1
</EuiButtonEmpty>
</EuiToolTip>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
isOpen={false}
ownFocus={false}
panelPaddingSize="none"
withTitle={true}
>
<EuiContextMenu
data-test-subj="layerTocActionsPanellayer1"
initialPanelId={0}
panels={
Array [
Object {
"id": 0,
"items": Array [
Object {
"data-test-subj": "fitToBoundsButton",
"disabled": false,
"icon": <EuiIcon
size="m"
type="search"
/>,
"name": "Fit to data",
"onClick": [Function],
"toolTipContent": null,
},
Object {
"data-test-subj": "layerVisibilityToggleButton",
"icon": <EuiIcon
size="m"
type="eye"
/>,
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
size="m"
type="copy"
/>,
"name": "Clone layer",
"onClick": [Function],
},
],
"title": "Layer actions",
},
]
}
/>
</EuiPopover>
`;
exports[`LayerTocActions should show when layer is not visible because zoom settings are outside of the current zoom level 1`] = `
<EuiPopover
anchorClassName="mapLayTocActions__popoverAnchor"
anchorPosition="leftUp"
button={
<EuiToolTip
content="Layer is visible between zoom levels 2 to 3."
delay="regular"
position="top"
title="layer 1"
>
<EuiButtonEmpty
className="mapTocEntry__layerName eui-textLeft"
color="text"
data-test-subj="layerTocActionsPanelToggleButtonlayer1"
flush="left"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<span
className="mapTocEntry__layerNameIcon"
>
<EuiIcon
size="m"
type="expand"
/>
</span>
layer 1
</EuiButtonEmpty>
</EuiToolTip>
}
className="mapLayTocActions"
closePopover={[Function]}
hasArrow={true}
id="contextMenu"
isOpen={false}
ownFocus={false}
panelPaddingSize="none"
withTitle={true}
>
<EuiContextMenu
data-test-subj="layerTocActionsPanellayer1"
initialPanelId={0}
panels={
Array [
Object {
"id": 0,
"items": Array [
Object {
"data-test-subj": "fitToBoundsButton",
"disabled": false,
"icon": <EuiIcon
size="m"
type="search"
/>,
"name": "Fit to data",
"onClick": [Function],
"toolTipContent": null,
},
Object {
"data-test-subj": "layerVisibilityToggleButton",
"icon": <EuiIcon
size="m"
type="eye"
/>,
"name": "Hide layer",
"onClick": [Function],
},
Object {
"data-test-subj": "editLayerButton",
"icon": <EuiIcon
size="m"
type="pencil"
/>,
"name": "Edit layer",
"onClick": [Function],
},
Object {
"data-test-subj": "cloneLayerButton",
"icon": <EuiIcon
size="m"
type="copy"
/>,
"name": "Clone layer",
"onClick": [Function],
},
],
"title": "Layer actions",
},
]
}
/>
</EuiPopover>
`;

View file

@ -11,7 +11,6 @@ import {
EuiPopover,
EuiContextMenu,
EuiIcon,
EuiLoadingSpinner,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -52,7 +51,7 @@ export class LayerTocActions extends Component {
};
_renderPopoverToggleButton() {
const { icon, tooltipContent } = this._getIconAndTooltipContent();
const { icon, tooltipContent } = this.props.layer.getIconAndTooltipContent(this.props.zoom);
return (
<EuiToolTip
position="top"
@ -74,55 +73,6 @@ export class LayerTocActions extends Component {
);
}
_getIconAndTooltipContent() {
const { zoom, layer } = this.props;
let icon;
let tooltipContent;
if (layer.hasErrors()) {
icon = (
<EuiIcon
aria-label={i18n.translate('xpack.maps.layerTocActions.loadWarningAriaLabel', { defaultMessage: 'Load warning' })}
size="m"
type="alert"
color="warning"
/>
);
tooltipContent = layer.getErrors();
} else if (layer.isLayerLoading()) {
icon = (<EuiLoadingSpinner size="m"/>);
} else if (!layer.isVisible()) {
icon = (
<EuiIcon
size="m"
type="eyeClosed"
/>
);
tooltipContent = i18n.translate('xpack.maps.layerTocActions.layerHiddenTooltip', {
defaultMessage: `Layer is hidden.`
});
} else if (!layer.showAtZoomLevel(zoom)) {
const { minZoom, maxZoom } = layer.getZoomConfig();
icon = (
<EuiIcon
size="m"
type="expand"
/>
);
tooltipContent = i18n.translate('xpack.maps.layerTocActions.zoomFeedbackTooltip', {
defaultMessage: `Layer is visible between zoom levels {minZoom} to {maxZoom}.`,
values: { minZoom, maxZoom }
});
} else {
icon = layer.getIcon();
}
return {
icon,
tooltipContent
};
}
_getActionsPanel() {
const actionItems = [
{

View file

@ -10,19 +10,15 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { LayerTocActions } from './layer_toc_actions';
let supportsFitToBounds;
let isLayerLoading;
let isVisible;
let hasErrors;
let showAtZoomLevel;
const layerMock = {
supportsFitToBounds: () => { return supportsFitToBounds; },
isVisible: () => { return isVisible; },
hasErrors: () => { return hasErrors; },
getErrors: () => { return 'simulated layer error'; },
isLayerLoading: () => { return isLayerLoading; },
showAtZoomLevel: () => { return showAtZoomLevel; },
getZoomConfig: () => { return { minZoom: 2, maxZoom: 3 }; },
getIcon: () => { return (<div>icon mock</div>); },
isVisible: () => { return true; },
getIconAndTooltipContent: (zoom) => {
return {
icon: (<span>mockIcon</span>),
tooltipContent: `simulated tooltip content at zoom: ${zoom}`
};
}
};
const defaultProps = {
@ -35,10 +31,6 @@ const defaultProps = {
describe('LayerTocActions', () => {
beforeEach(() => {
supportsFitToBounds = true;
isLayerLoading = false;
isVisible = true;
hasErrors = false;
showAtZoomLevel = true;
});
test('is rendered', async () => {
@ -90,72 +82,4 @@ describe('LayerTocActions', () => {
expect(component)
.toMatchSnapshot();
});
test('should display spinner when layer is loading', async () => {
isLayerLoading = true;
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
test('should show warning when layer has errors', async () => {
hasErrors = true;
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
test('should show visible toggle when layer is not visible', async () => {
isVisible = false;
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
test('should show when layer is not visible because zoom settings are outside of the current zoom level', async () => {
showAtZoomLevel = false;
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
});

View file

@ -11,6 +11,7 @@ import { EuiIcon } from '@elastic/eui';
import { HeatmapStyle } from './styles/heatmap_style';
import { SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
import { isRefreshOnlyQuery } from './util/is_refresh_only_query';
import { i18n } from '@kbn/i18n';
const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__';//unique name to store scaled value for weighting
@ -188,12 +189,25 @@ export class HeatmapLayer extends AbstractLayer {
return 'heatmap';
}
getIcon() {
return (
<EuiIcon
type={this.getLayerTypeIconName()}
/>
);
getCustomIconAndTooltipContent() {
const sourceDataRequest = this.getSourceDataRequest();
const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
if (!featureCollection || featureCollection.features.length === 0) {
return {
icon: (
<EuiIcon
size="m"
color="subdued"
type="minusInCircle"
/>
),
tooltipContent: i18n.translate('xpack.maps.heatmapLayer.noResultsFoundTooltip', {
defaultMessage: `No results found.`
})
};
}
return super.getCustomIconAndTooltipContent();
}
}

View file

@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import React from 'react';
import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui';
import turf from 'turf';
import turfBooleanContains from '@turf/boolean-contains';
import { DataRequest } from './util/data_request';
import { SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
import uuid from 'uuid/v4';
import { copyPersistentState } from '../../store/util';
import { i18n } from '@kbn/i18n';
const SOURCE_UPDATE_REQUIRED = true;
const NO_SOURCE_UPDATE_REQUIRED = false;
@ -101,8 +104,66 @@ export class AbstractLayer {
return this._descriptor.label ? this._descriptor.label : '';
}
getIcon() {
console.warn('Icon not available for this layer type');
getCustomIconAndTooltipContent() {
return {
icon: (
<EuiIcon
size="m"
type={this.getLayerTypeIconName()}
/>
)
};
}
getIconAndTooltipContent(zoomLevel) {
let icon;
let tooltipContent = null;
if (this.hasErrors()) {
icon = (
<EuiIcon
aria-label={i18n.translate('xpack.maps.layer.loadWarningAriaLabel', { defaultMessage: 'Load warning' })}
size="m"
type="alert"
color="warning"
/>
);
tooltipContent = this.getErrors();
} else if (this.isLayerLoading()) {
icon = (<EuiLoadingSpinner size="m"/>);
} else if (!this.isVisible()) {
icon = (
<EuiIcon
size="m"
type="eyeClosed"
/>
);
tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', {
defaultMessage: `Layer is hidden.`
});
} else if (!this.showAtZoomLevel(zoomLevel)) {
const { minZoom, maxZoom } = this.getZoomConfig();
icon = (
<EuiIcon
size="m"
type="expand"
/>
);
tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', {
defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`,
values: { minZoom, maxZoom }
});
} else {
const customIconAndTooltipContent = this.getCustomIconAndTooltipContent();
if (customIconAndTooltipContent) {
icon = customIconAndTooltipContent.icon;
tooltipContent = customIconAndTooltipContent.tooltipContent;
}
}
return {
icon,
tooltipContent
};
}
hasLegendDetails() {
@ -126,12 +187,7 @@ export class AbstractLayer {
}
showAtZoomLevel(zoom) {
if (zoom >= this._descriptor.minZoom && zoom <= this._descriptor.maxZoom) {
return true;
}
return false;
return zoom >= this._descriptor.minZoom && zoom <= this._descriptor.maxZoom;
}
getMinZoom() {

View file

@ -196,4 +196,21 @@ export class ESSearchSource extends AbstractESSource {
return { name: field.name, label: field.name };
});
}
getSourceTooltipContent(sourceDataRequest) {
const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
const meta = sourceDataRequest ? sourceDataRequest.getMeta() : {};
if (meta.areResultsTrimmed) {
return i18n.translate('xpack.maps.esSearch.resultsTrimmedMsg', {
defaultMessage: `Results limited to first {count} matching documents.`,
values: { count: featureCollection.features.length }
});
}
return i18n.translate('xpack.maps.esSearch.featureCountMsg', {
defaultMessage: `Found {count} documents.`,
values: { count: featureCollection.features.length }
});
}
}

View file

@ -114,4 +114,8 @@ export class AbstractVectorSource extends AbstractSource {
isJoinable() {
return true;
}
getSourceTooltipContent(/* sourceDataRequest */) {
return null;
}
}

View file

@ -6,8 +6,6 @@
import { AbstractLayer } from './layer';
import _ from 'lodash';
import React from 'react';
import { EuiIcon } from '@elastic/eui';
import { TileStyle } from '../layers/styles/tile_style';
import { SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
@ -105,15 +103,7 @@ export class TileLayer extends AbstractLayer {
return 'grid';
}
getIcon() {
return (
<EuiIcon
type={this.getLayerTypeIconName()}
/>
);
}
isLayerLoading() {
return false;
}
}

View file

@ -5,6 +5,7 @@
*/
import turf from 'turf';
import React from 'react';
import { AbstractLayer } from './layer';
import { VectorStyle } from './styles/vector_style';
import { LeftInnerJoin } from './joins/left_inner_join';
@ -12,6 +13,8 @@ import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_ID_ORIGIN } from '../../../common
import _ from 'lodash';
import { JoinTooltipProperty } from './tooltips/join_tooltip_property';
import { isRefreshOnlyQuery } from './util/is_refresh_only_query';
import { EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
const EMPTY_FEATURE_COLLECTION = {
type: 'FeatureCollection',
@ -78,8 +81,28 @@ export class VectorLayer extends AbstractLayer {
});
}
getIcon() {
return this._style.getIcon();
getCustomIconAndTooltipContent() {
const sourceDataRequest = this.getSourceDataRequest();
const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
if (!featureCollection || featureCollection.features.length === 0) {
return {
icon: (
<EuiIcon
size="m"
color="subdued"
type="minusInCircle"
/>
),
tooltipContent: i18n.translate('xpack.maps.vectorLayer.noResultsFoundTooltip', {
defaultMessage: `No results found.`
})
};
}
return {
icon: this._style.getIcon(),
tooltipContent: this._source.getSourceTooltipContent(sourceDataRequest)
};
}
getLayerTypeIconName() {