[Maps] Localization (#30881)

Localize Maps-app UX with Kibana's i18n framework.
This commit is contained in:
Thomas Neirynck 2019-03-06 18:24:41 -05:00 committed by GitHub
parent e365f0b646
commit 5e5a49a905
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1217 additions and 424 deletions

View file

@ -31,6 +31,7 @@
"xpack.infra": "x-pack/plugins/infra",
"xpack.kueryAutocomplete": "x-pack/plugins/kuery_autocomplete",
"xpack.licenseMgmt": "x-pack/plugins/license_management",
"xpack.maps": "x-pack/plugins/maps",
"xpack.ml": "x-pack/plugins/ml",
"xpack.logstash": "x-pack/plugins/logstash",
"xpack.main": "x-pack/plugins/xpack_main",

View file

@ -16,4 +16,8 @@ export const ZOOM_PRECISION = 2;
export const DEFAULT_EMS_TILE_LAYER = 'road_map';
export const APP_ID = 'maps';
export const APP_ICON = 'gisApp';
export const SOURCE_DATA_ID_ORIGIN = 'source';

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
export function getAppTitle() {
return i18n.translate('xpack.maps.appTitle', {
defaultMessage: 'Maps'
});
}
export function getDataSourceLabel() {
return i18n.translate('xpack.maps.source.dataSourceLabel', {
defaultMessage: 'Data source'
});
}
export function getUrlLabel() {
return i18n.translate('xpack.maps.source.urlLabel', {
defaultMessage: 'Url'
});
}

View file

@ -6,30 +6,35 @@
import { resolve } from 'path';
import { initRoutes } from './server/routes';
import ecommerceSavedObjects from './server/sample_data/ecommerce_saved_objects.json';
import fligthsSavedObjects from './server/sample_data/flights_saved_objects.json';
import webLogsSavedObjects from './server/sample_data/web_logs_saved_objects.json';
import { getEcommerceSavedObjects } from './server/sample_data/ecommerce_saved_objects';
import { getFlightsSavedObjects } from './server/sample_data/flights_saved_objects.js';
import { getWebLogsSavedObjects } from './server/sample_data/web_logs_saved_objects.js';
import mappings from './mappings.json';
import { checkLicense } from './check_license';
import { migrations } from './migrations';
import { watchStatusAndLicenseToInitialize } from
'../../server/lib/watch_status_and_license_to_initialize';
import { initTelemetryCollection } from './server/maps_telemetry';
import { i18n } from '@kbn/i18n';
import { APP_ID, APP_ICON } from './common/constants';
import { getAppTitle } from './common/i18n_getters';
export function maps(kibana) {
return new kibana.Plugin({
require: ['kibana', 'elasticsearch', 'xpack_main', 'tile_map', 'task_manager'],
id: 'maps',
id: APP_ID,
configPrefix: 'xpack.maps',
publicDir: resolve(__dirname, 'public'),
uiExports: {
app: {
title: 'Maps',
description: 'Map application',
title: getAppTitle(),
description: i18n.translate('xpack.maps.appDescription', {
defaultMessage: 'Map application'
}),
main: 'plugins/maps/index',
icon: 'plugins/maps/icon.svg',
euiIconType: 'gisApp',
euiIconType: APP_ICON,
},
injectDefaultVars(server) {
const serverConfig = server.config();
@ -86,9 +91,9 @@ export function maps(kibana) {
.feature(this.id)
.registerLicenseCheckResultsGenerator(checkLicense);
server.addSavedObjectsToSampleDataset('ecommerce', ecommerceSavedObjects);
server.addSavedObjectsToSampleDataset('flights', fligthsSavedObjects);
server.addSavedObjectsToSampleDataset('logs', webLogsSavedObjects);
server.addSavedObjectsToSampleDataset('ecommerce', getEcommerceSavedObjects());
server.addSavedObjectsToSampleDataset('flights', getFlightsSavedObjects());
server.addSavedObjectsToSampleDataset('logs', getWebLogsSavedObjects());
server.injectUiAppVars('maps', async () => {
return await server.getInjectedUiAppVars('kibana');
});

View file

@ -6,7 +6,8 @@
import chrome from 'ui/chrome';
import React from 'react';
import { I18nContext } from 'ui/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { render, unmountComponentAtNode } from 'react-dom';
import { uiModules } from 'ui/modules';
import { timefilter } from 'ui/timefilter';
@ -143,9 +144,9 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
const root = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
render(
<Provider store={store}>
<I18nContext>
<I18nProvider>
<GisMap/>
</I18nContext>
</I18nProvider>
</Provider>,
root
);
@ -201,7 +202,9 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
// TODO subscribe to store change and change when store updates title
chrome.breadcrumbs.set([
{ text: 'Maps', href: '#' },
{ text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
defaultMessage: 'Maps'
}), href: '#' },
{ text: $scope.map.title }
]);
@ -216,7 +219,10 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
docTitle.change(savedMap.title);
} catch(err) {
toastNotifications.addDanger({
title: `Error on saving '${savedMap.title}'`,
title: i18n.translate('xpack.maps.mapController.saveErrorMessage', {
defaultMessage: `Error on saving '{title}'`,
values: { title: savedMap.title }
}),
text: err.message,
'data-test-subj': 'saveMapError',
});
@ -225,7 +231,10 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
if (id) {
toastNotifications.addSuccess({
title: `Saved '${savedMap.title}'`,
title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
defaultMessage: `Saved '{title}'`,
values: { title: savedMap.title }
}),
'data-test-subj': 'saveMapSuccess',
});
@ -243,23 +252,35 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
timefilter.disableAutoRefreshSelector();
$scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar
$scope.topNavMenu = [{
key: 'full screen',
description: 'full screen',
key: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', {
defaultMessage: `full screen`
}),
description: i18n.translate('xpack.maps.mapController.fullScreenDescription', {
defaultMessage: `full screen`
}),
testId: 'mapsFullScreenMode',
run() {
store.dispatch(enableFullScreen());
}
}, {
key: 'inspect',
description: 'Open Inspector',
key: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', {
defaultMessage: `inspect`
}),
description: i18n.translate('xpack.maps.mapController.openInspectorDescription', {
defaultMessage: `Open Inspector`
}),
testId: 'openInspectorButton',
run() {
const inspectorAdapters = getInspectorAdapters(store.getState());
Inspector.open(inspectorAdapters, {});
}
}, {
key: 'save',
description: 'Save map',
key: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', {
defaultMessage: `save`
}),
description: i18n.translate('xpack.maps.mapController.saveMapDescription', {
defaultMessage: `Save map`
}),
testId: 'mapSaveButton',
run: async () => {
const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate }) => {

View file

@ -20,6 +20,7 @@ import {
EuiFlyoutBody,
EuiFlyoutFooter,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import _ from 'lodash';
export class AddLayerPanel extends Component {
@ -71,7 +72,10 @@ export class AddLayerPanel extends Component {
}}
fill
>
Add layer
<FormattedMessage
id="xpack.maps.addLayerPanel.addLayerButtonLabel"
defaultMessage="Add layer"
/>
</EuiButton>
);
}
@ -102,7 +106,12 @@ export class AddLayerPanel extends Component {
return (
<Fragment>
<EuiTitle size="xs">
<h2>Choose data source</h2>
<h2>
<FormattedMessage
id="xpack.maps.addLayerPanel.chooseDataSourceTitle"
defaultMessage="Choose data source"
/>
</h2>
</EuiTitle>
{this._renderSourceCards()}
</Fragment>
@ -130,7 +139,10 @@ export class AddLayerPanel extends Component {
onClick={this._clearSource}
iconType="arrowLeft"
>
Change data source
<FormattedMessage
id="xpack.maps.addLayerPanel.changeDataSourceButtonLabel"
defaultMessage="Change data source"
/>
</EuiButtonEmpty>
<EuiSpacer size="s" />
<EuiPanel>
@ -156,7 +168,12 @@ export class AddLayerPanel extends Component {
>
<EuiFlyoutHeader hasBorder className="mapLayerPanel__header">
<EuiTitle size="s">
<h2>Add layer</h2>
<h2>
<FormattedMessage
id="xpack.maps.addLayerPanel.panelTitle"
defaultMessage="Add layer"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
@ -175,7 +192,10 @@ export class AddLayerPanel extends Component {
flush="left"
data-test-subj="layerAddCancelButton"
>
Cancel
<FormattedMessage
id="xpack.maps.addLayerPanel.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>

View file

@ -35,7 +35,11 @@ exports[`LayerPanel is rendered 1`] = `
onClick={[Function]}
type="button"
>
Fit
<FormattedMessage
defaultMessage="Fit"
id="xpack.maps.layerPanel.fitToBoundsButtonLabel"
values={Object {}}
/>
</EuiButtonIcon>
</EuiFlexItem>
<EuiFlexItem

View file

@ -13,6 +13,8 @@ import {
EuiSpacer,
EuiButtonEmpty,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export const FlyoutFooter = ({ cancelLayerPanel, saveLayerEdits, removeLayer,
hasStateChanged }) => {
const removeBtn = (
@ -23,11 +25,24 @@ export const FlyoutFooter = ({ cancelLayerPanel, saveLayerEdits, removeLayer,
flush="right"
data-test-subj="mapRemoveLayerButton"
>
Remove layer
<FormattedMessage
id="xpack.maps.layerPanel.footer.removeLayerButtonLabel"
defaultMessage="Remove layer"
/>
</EuiButtonEmpty>
</EuiFlexItem>
);
const cancelButtonLabel = hasStateChanged ? (<FormattedMessage
id="xpack.maps.layerPanel.footer.cancelButtonLabel"
defaultMessage="Cancel"
/>) : (<FormattedMessage
id="xpack.maps.layerPanel.footer.closeButtonLabel"
defaultMessage="Close"
/>);
return (
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
@ -35,7 +50,7 @@ export const FlyoutFooter = ({ cancelLayerPanel, saveLayerEdits, removeLayer,
onClick={cancelLayerPanel}
flush="left"
>
{hasStateChanged ? 'Cancel' : 'Close'}
{cancelButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem>
@ -49,7 +64,10 @@ export const FlyoutFooter = ({ cancelLayerPanel, saveLayerEdits, removeLayer,
onClick={saveLayerEdits}
fill
>
Save &amp; close
<FormattedMessage
id="xpack.maps.layerPanel.footer.saveAndCloseButtonLabel"
defaultMessage="Save & close"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -6,13 +6,12 @@
import _ from 'lodash';
import React, { Component } from 'react';
import {
EuiFlexItem,
EuiFlexGroup,
EuiButtonIcon,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { JoinExpression } from './join_expression';
import { MetricsExpression } from './metrics_expression';
@ -74,7 +73,10 @@ export class Join extends Component {
} catch (err) {
if (this._isMounted) {
this.setState({
loadError: `Unable to find Index pattern ${indexPatternId}`
loadError: i18n.translate('xpack.maps.layerPanel.join.noIndexPatternErrorMessage', {
defaultMessage: `Unable to find Index pattern {indexPatternId}`,
values: { indexPatternId }
})
});
}
return;
@ -204,8 +206,12 @@ export class Join extends Component {
className="mapJoinItem__delete"
iconType="trash"
color="danger"
aria-label="Delete join"
title="Delete join"
aria-label={i18n.translate('xpack.maps.layerPanel.join.deleteJoinAriaLabel', {
defaultMessage: 'Delete join'
})}
title={i18n.translate('xpack.maps.layerPanel.join.deleteJoinTitle', {
defaultMessage: 'Delete join'
})}
onClick={onRemove}
/>
</EuiFlexGroup>

View file

@ -7,7 +7,6 @@
import _ from 'lodash';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
EuiPopover,
EuiPopoverTitle,
@ -15,9 +14,10 @@ import {
EuiFormRow,
EuiComboBox,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { IndexPatternSelect } from 'ui/index_patterns/components/index_pattern_select';
import { SingleFieldSelect } from '../../../../shared/components/single_field_select';
import { FormattedMessage } from '@kbn/i18n/react';
import {
indexPatternService,
@ -87,10 +87,14 @@ export class JoinExpression extends Component {
return (
<EuiFormRow
label="Left field"
label={
i18n.translate('xpack.maps.layerPanel.joinExpression.leftFieldLabel', {
defaultMessage: 'Left field'
})
}
>
<EuiComboBox
placeholder="Select field"
placeholder={getSelectFieldPlaceholder()}
singleSelection={true}
isClearable={false}
options={options}
@ -108,10 +112,18 @@ export class JoinExpression extends Component {
return (
<EuiFormRow
label="Right source"
label={
i18n.translate('xpack.maps.layerPanel.joinExpression.rightSourceLabel', {
defaultMessage: 'Right source'
})
}
>
<IndexPatternSelect
placeholder="Select index pattern"
placeholder={
i18n.translate('xpack.maps.layerPanel.joinExpression.selectIndexPatternPlaceholder', {
defaultMessage: 'Select index pattern'
})
}
indexPatternId={this.props.rightSourceIndexPatternId}
onChange={this._onRightSourceChange}
isClearable={false}
@ -131,10 +143,14 @@ export class JoinExpression extends Component {
return (
<EuiFormRow
label="Right field"
label={
i18n.translate('xpack.maps.layerPanel.joinExpression.rightFieldLabel', {
defaultMessage: 'Right field'
})
}
>
<SingleFieldSelect
placeholder="Select field"
placeholder={getSelectFieldPlaceholder()}
value={this.props.rightValue}
onChange={this.props.onRightFieldChange}
filterField={filterStringOrNumberFields}
@ -156,7 +172,9 @@ export class JoinExpression extends Component {
return `${leftSourceName}:${leftValue} with ${rightSourceName}:${rightValue}`;
}
return '-- select --';
return i18n.translate('xpack.maps.layerPanel.joinExpression.selectPlaceholder', {
defaultMessage: '-- select --'
});
}
render() {
@ -180,9 +198,18 @@ export class JoinExpression extends Component {
}
>
<div style={{ width: 300 }}>
<EuiPopoverTitle>Join</EuiPopoverTitle>
<EuiPopoverTitle>
<FormattedMessage
id="xpack.maps.layerPanel.joinExpression.joinPopoverTitle"
defaultMessage="Join"
/>
</EuiPopoverTitle>
<EuiFormRow
label="Left source"
label={
i18n.translate('xpack.maps.layerPanel.joinExpression.leftSourceLabel', {
defaultMessage: 'Left source'
})
}
>
<EuiComboBox
selectedOptions={[{ value: leftSourceName, label: leftSourceName }]}
@ -222,3 +249,9 @@ JoinExpression.propTypes = {
rightFields: PropTypes.object, // indexPattern.fields IndexedArray object
onRightFieldChange: PropTypes.func.isRequired,
};
function getSelectFieldPlaceholder() {
return i18n.translate('xpack.maps.layerPanel.joinExpression.selectFieldPlaceholder', {
defaultMessage: 'Select field'
});
}

View file

@ -6,16 +6,15 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import {
EuiPopover,
EuiPopoverTitle,
EuiExpression,
EuiFormErrorText,
} from '@elastic/eui';
import { MetricsEditor } from '../../../../shared/components/metrics_editor';
import { FormattedMessage } from '@kbn/i18n/react';
export class MetricsExpression extends Component {
state = {
@ -37,7 +36,12 @@ export class MetricsExpression extends Component {
_renderMetricsEditor = () => {
if (!this.props.rightFields) {
return (
<EuiFormErrorText>JOIN must be set</EuiFormErrorText>
<EuiFormErrorText>
<FormattedMessage
id="xpack.maps.layerPanel.metricsExpression.joinMustBeSetErrorMessage"
defaultMessage="JOIN must be set"
/>
</EuiFormErrorText>
);
}
@ -69,7 +73,12 @@ export class MetricsExpression extends Component {
return `${type} ${field}`;
});
const useMetricDescription = i18n.translate('xpack.maps.layerPanel.metricsExpression.useMetricsDescription', {
defaultMessage: '{metricsLength, plural, one {and use metric} other {and use metrics}}',
values: {
metricsLength: metricExpressions.length
}
});
return (
<EuiPopover
id="metricsPopover"
@ -82,14 +91,19 @@ export class MetricsExpression extends Component {
button={
<EuiExpression
onClick={this._togglePopover}
description={metricExpressions.length > 1 ? 'and use metrics' : 'and use metric'}
description={useMetricDescription}
uppercase={false}
value={metricExpressions.length > 0 ? metricExpressions.join(', ') : 'count'}
/>
}
>
<div style={{ width: 400 }}>
<EuiPopoverTitle>Metrics</EuiPopoverTitle>
<EuiPopoverTitle>
<FormattedMessage
id="xpack.maps.layerPanel.metricsExpression.metricsPopoverTitle"
defaultMessage="Metrics"
/>
</EuiPopoverTitle>
{this._renderMetricsEditor()}
</div>
</EuiPopover>

View file

@ -16,6 +16,8 @@ import {
} from '@elastic/eui';
import { Join } from './resources/join';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
export function JoinEditor({ joins, layer, onChange }) {
@ -69,10 +71,30 @@ export function JoinEditor({ joins, layer, onChange }) {
<div>
<EuiFlexGroup responsive={false}>
<EuiFlexItem>
<EuiTitle size="xs"><h5>Term joins</h5></EuiTitle>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.layerPanel.joinEditor.termJoinsTitle"
defaultMessage="Term joins"
/>
</h5>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon iconType="plusInCircle" onClick={addJoin} aria-label="Add join" title="Add join" />
<EuiButtonIcon
iconType="plusInCircle"
onClick={addJoin}
aria-label={i18n.translate('xpack.maps.layerPanel.joinEditor.addJoinAriaLabel', {
defaultMessage: 'Add join'
})
}
title={
i18n.translate('xpack.maps.layerPanel.joinEditor.addJoinButtonLabel', {
defaultMessage: 'Add join'
})
}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -18,6 +18,8 @@ import {
EuiCallOut,
} from '@elastic/eui';
import { ValidatedRange } from '../../../shared/components/validated_range';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
export function SettingsPanel(props) {
@ -53,7 +55,11 @@ export function SettingsPanel(props) {
<Fragment>
<EuiCallOut
color="warning"
title="Unable to load layer"
title={
i18n.translate('xpack.maps.layerPanel.settingsPanel.unableToLoadTitle', {
defaultMessage: 'Unable to load layer'
})
}
>
<p data-test-subj="layerErrorMessage">
{props.layer.getErrors()}
@ -67,12 +73,18 @@ export function SettingsPanel(props) {
const renderZoomSliders = () => {
return (
<EuiFormRow
helpText="Display layer when map is in zoom range."
helpText={
i18n.translate('xpack.maps.layerPanel.settingsPanel.zoomFeedbackHelptext', {
defaultMessage: 'Display layer when map is in zoom range.'
})
}
>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Min zoom"
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.minZoomLabel', {
defaultMessage: 'Min zoom'
})}
>
<EuiRange
min={0}
@ -86,7 +98,9 @@ export function SettingsPanel(props) {
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="Max zoom"
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.maxZoomLabel', {
defaultMessage: 'Max zoom'
})}
>
<EuiRange
min={0}
@ -106,7 +120,11 @@ export function SettingsPanel(props) {
const renderLabel = () => {
return (
<EuiFormRow
label="Layer name"
label={
i18n.translate('xpack.maps.layerPanel.settingsPanel.layerNameLabel', {
defaultMessage: 'Layer name'
})
}
>
<EuiFieldText
value={props.label}
@ -119,7 +137,11 @@ export function SettingsPanel(props) {
const renderAlphaSlider = () => {
return (
<EuiFormRow
label="Layer transparency"
label={
i18n.translate('xpack.maps.layerPanel.settingsPanel.layerTransparencyLabel', {
defaultMessage: 'Layer transparency'
})
}
>
<div className="mapAlphaRange">
<ValidatedRange
@ -145,7 +167,14 @@ export function SettingsPanel(props) {
<EuiPanel>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xs"><h5>Settings</h5></EuiTitle>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.layerPanel.settingsPanel.settingsTitle"
defaultMessage="Settings"
/>
</h5>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -10,7 +10,6 @@ import { StyleTabs } from './style_tabs';
import { JoinEditor } from './join_editor';
import { FlyoutFooter } from './flyout_footer';
import { SettingsPanel } from './settings_panel';
import {
EuiButtonIcon,
EuiFlexItem,
@ -26,6 +25,9 @@ import {
EuiLink,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
export class LayerPanel extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
@ -41,7 +43,7 @@ export class LayerPanel extends React.Component {
return null;
}
state = {}
state = {};
componentDidMount() {
this._isMounted = true;
@ -133,11 +135,19 @@ export class LayerPanel extends React.Component {
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="Fit to bounds"
aria-label={
i18n.translate('xpack.maps.layerPanel.fitToBoundsAriaLabel', {
defaultMessage: 'Fit to bounds'
})
}
iconType={selectedLayer.getLayerTypeIconName()}
onClick={this.props.fitToBounds}
>
Fit
<FormattedMessage
id="xpack.maps.layerPanel.fitToBoundsButtonLabel"
defaultMessage="Fit"
/>
</EuiButtonIcon>
</EuiFlexItem>
<EuiFlexItem>
@ -150,7 +160,11 @@ export class LayerPanel extends React.Component {
<div className="mapLayerPanel__sourceDetails">
<EuiAccordion
id="accordion1"
buttonContent="Source details"
buttonContent={
i18n.translate('xpack.maps.layerPanel.sourceDetailsLabel', {
defaultMessage: 'Source details'
})
}
>
<EuiText color="subdued" size="s">
<EuiSpacer size="xs" />

View file

@ -29,7 +29,7 @@ jest.mock('./settings_panel', () => ({
}));
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { LayerPanel } from './view';
@ -53,7 +53,7 @@ const defaultProps = {
describe('LayerPanel', () => {
test('is rendered', async () => {
const component = shallow(
const component = shallowWithIntl(
<LayerPanel
{...defaultProps}
/>
@ -69,7 +69,7 @@ describe('LayerPanel', () => {
});
test('should render empty panel when selectedLayer is null', async () => {
const component = shallow(
const component = shallowWithIntl(
<LayerPanel
{...defaultProps}
selectedLayer={undefined}

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { AttributionControl } from './view';
@ -25,7 +25,7 @@ describe('AttributionControl', () => {
];
}
};
const component = shallow(
const component = shallowWithIntl(
<AttributionControl
layerList={[mockLayer1, mockLayer2]}
/>

View file

@ -30,7 +30,11 @@ exports[`LayerControl is rendered 1`] = `
textTransform="none"
>
<h2>
Layers
<FormattedMessage
defaultMessage="Layers"
id="xpack.maps.layerControl.layersTitle"
values={Object {}}
/>
</h2>
</EuiTitle>
</EuiFlexItem>
@ -47,7 +51,11 @@ exports[`LayerControl is rendered 1`] = `
size="xs"
type="button"
>
Add layer
<FormattedMessage
defaultMessage="Add layer"
id="xpack.maps.layerControl.addLayerButtonLabel"
values={Object {}}
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
@ -92,7 +100,11 @@ exports[`LayerControl props isReadOnly 1`] = `
textTransform="none"
>
<h2>
Layers
<FormattedMessage
defaultMessage="Layers"
id="xpack.maps.layerControl.layersTitle"
values={Object {}}
/>
</h2>
</EuiTitle>
</EuiFlexItem>

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { TOCEntry } from './view';
@ -30,7 +30,7 @@ const defaultProps = {
describe('TOCEntry', () => {
test('is rendered', async () => {
const component = shallow(
const component = shallowWithIntl(
<TOCEntry
{...defaultProps}
/>
@ -47,7 +47,7 @@ describe('TOCEntry', () => {
describe('props', () => {
test('isReadOnly', async () => {
const component = shallow(
const component = shallowWithIntl(
<TOCEntry
{...defaultProps}
isReadOnly={true}

View file

@ -13,6 +13,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import { LayerTOC } from './layer_toc';
import { FormattedMessage } from '@kbn/i18n/react';
export function LayerControl({ isReadOnly, showAddLayerWizard }) {
let addLayer;
@ -25,7 +26,10 @@ export function LayerControl({ isReadOnly, showAddLayerWizard }) {
onClick={showAddLayerWizard}
data-test-subj="addLayerButton"
>
Add layer
<FormattedMessage
id="xpack.maps.layerControl.addLayerButtonLabel"
defaultMessage="Add layer"
/>
</EuiButtonEmpty>
</EuiFlexItem>
);
@ -42,7 +46,12 @@ export function LayerControl({ isReadOnly, showAddLayerWizard }) {
>
<EuiFlexItem>
<EuiTitle size="xs">
<h2>Layers</h2>
<h2>
<FormattedMessage
id="xpack.maps.layerControl.layersTitle"
defaultMessage="Layers"
/>
</h2>
</EuiTitle>
</EuiFlexItem>
{addLayer}

View file

@ -11,7 +11,7 @@ jest.mock('./layer_toc', () => ({
}));
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { LayerControl } from './view';
@ -21,7 +21,7 @@ const defaultProps = {
describe('LayerControl', () => {
test('is rendered', () => {
const component = shallow(
const component = shallowWithIntl(
<LayerControl
{...defaultProps}
/>
@ -33,7 +33,7 @@ describe('LayerControl', () => {
describe('props', () => {
test('isReadOnly', () => {
const component = shallow(
const component = shallowWithIntl(
<LayerControl
{...defaultProps}
isReadOnly={true}

View file

@ -16,6 +16,7 @@ import {
} from '@elastic/eui';
import { SetView } from './set_view';
import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants';
import { FormattedMessage } from '@kbn/i18n/react';
export function ViewControl({ isSetViewOpen, closeSetView, openSetView, mouseCoordinates }) {
const toggleSetViewVisibility = () => {
@ -37,7 +38,10 @@ export function ViewControl({ isSetViewOpen, closeSetView, openSetView, mouseCoo
onClick={toggleSetViewVisibility}
data-test-subj="toggleSetViewVisibilityButton"
>
Go to
<FormattedMessage
id="xpack.maps.viewControl.goToButtonLabel"
defaultMessage="Go to"
/>
</EuiButton>)}
isOpen={isSetViewOpen}
closePopover={closeSetView}
@ -57,8 +61,18 @@ export function ViewControl({ isSetViewOpen, closeSetView, openSetView, mouseCoo
<EuiPanel className="mapWidgetControl mapViewControl__coordinates" paddingSize="none">
<EuiText size="xs">
<p>
<strong>lat:</strong> {lat},{' '}
<strong>lon:</strong> {lon}
<strong>
<FormattedMessage
id="xpack.maps.viewControl.latLabel"
defaultMessage="lat:"
/>
</strong> {lat},{' '}
<strong>
<FormattedMessage
id="xpack.maps.viewControl.lonLabel"
defaultMessage="lon:"
/>
</strong> {lon}
</p>
</EuiText>
</EuiPanel>

View file

@ -5,6 +5,7 @@
*/
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
/**
* Converts Elasticsearch search results into GeoJson FeatureCollection
@ -27,7 +28,11 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType) {
} else if (geoFieldType === 'geo_shape') {
geometries = geoShapeToGeometry(properties[geoFieldName]);
} else {
throw new Error(`Unsupported field type, expected: geo_shape or geo_point, you provided: ${geoFieldType}`);
const errorMessage = i18n.translate('xpack.maps.elasticsearch_geo_utils.unsupportedFieldTypeErrorMessage', {
defaultMessage: 'Unsupported field type, expected: geo_shape or geo_point, you provided: {geoFieldType}',
values: { geoFieldType }
});
throw new Error(errorMessage);
}
// don't include geometry field value in properties
@ -63,8 +68,11 @@ export function geoPointToGeometry(value) {
if (typeof value === 'string') {
const commaSplit = value.split(',');
if (commaSplit.length === 1) {
// Geo-point expressed as a geohash.
throw new Error(`Unable to convert to geojson, geohash not supported`);
const errorMessage = i18n.translate('xpack.maps.elasticsearch_geo_utils.geohashIsUnsupportedErrorMessage', {
defaultMessage: `Unable to convert to geojson, geohash not supported`
});
throw new Error(errorMessage);
}
// Geo-point expressed as a string with the format: "lat,lon".
const lat = parseFloat(commaSplit[0]);
@ -78,7 +86,13 @@ export function geoPointToGeometry(value) {
}
if (!Array.isArray(value)) {
throw new Error(`Unsupported geo_point value: ${value}`);
const errorMessage = i18n.translate('xpack.maps.elasticsearch_geo_utils.unsupportedGeoPointValueErrorMessage', {
defaultMessage: `Unsupported geo_point value: {geoPointValue}`,
values: {
geoPointValue: value
}
});
throw new Error(errorMessage);
}
if (value.length === 2
@ -116,7 +130,10 @@ export function geoShapeToGeometry(value) {
// TODO handle case where value is WKT and convert to geojson
if (typeof value === 'string') {
throw new Error(`Unable to convert WKT to geojson, not supported`);
const errorMessage = i18n.translate('xpack.maps.elasticsearch_geo_utils.wktIsUnsupportedErrorMessage', {
defaultMessage: `Unable to convert WKT to geojson, not supported`,
});
throw new Error(errorMessage);
}
const geoJson = _.cloneDeep(value);
@ -180,7 +197,11 @@ export function createExtentFilter(mapExtent, geoFieldName, geoFieldType) {
}
};
} else {
throw new Error(`Unsupported field type, expected: geo_shape or geo_point, you provided: ${geoFieldType}`);
const errorMessage = i18n.translate('xpack.maps.elasticsearch_geo_utils.unsupportedGeoFieldTypeErrorMessage', {
defaultMessage: `Unsupported field type, expected: geo_shape or geo_point, you provided: {geoFieldType}`,
values: { geoFieldType }
});
throw new Error(errorMessage);
}
}

View file

@ -15,6 +15,9 @@ import {
EuiTableRow,
EuiTableRowCell,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
const DETAILS_TAB_ID = 'details';
const STYLE_TAB_ID = 'mapStyle';
@ -23,11 +26,15 @@ class MapDetails extends Component {
tabs = [{
id: DETAILS_TAB_ID,
name: 'Map details',
name: i18n.translate('xpack.maps.inspector.mapDetailsTitle', {
defaultMessage: 'Map details'
}),
dataTestSubj: 'mapDetailsTab',
}, {
id: STYLE_TAB_ID,
name: 'Mapbox style',
name: i18n.translate('xpack.maps.inspector.mapboxStyleTitle', {
defaultMessage: 'Mapbox style'
}),
dataTestSubj: 'mapboxStyleTab',
}];
@ -60,21 +67,30 @@ class MapDetails extends Component {
<EuiTableBody>
<EuiTableRow>
<EuiTableRowCell>
Center lon
<FormattedMessage
id="xpack.maps.inspector.centerLonLabel"
defaultMessage="Center lon"
/>
</EuiTableRowCell>
<EuiTableRowCell data-test-subj="centerLon">{this.props.centerLon}</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>
Center lat
<FormattedMessage
id="xpack.maps.inspector.centerLatLabel"
defaultMessage="Center lat"
/>
</EuiTableRowCell>
<EuiTableRowCell data-test-subj="centerLat">{this.props.centerLat}</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>
Zoom
<FormattedMessage
id="xpack.maps.inspector.zoomLabel"
defaultMessage="Zoom"
/>
</EuiTableRowCell>
<EuiTableRowCell data-test-subj="zoom">{this.props.zoom}</EuiTableRowCell>
</EuiTableRow>

View file

@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
import { InspectorView } from 'ui/inspector';
import { MapDetails } from './map_details';
import { i18n } from '@kbn/i18n';
class MapViewComponent extends Component {
@ -54,9 +55,13 @@ MapViewComponent.propTypes = {
};
const MapView = {
title: 'Map details',
title: i18n.translate('xpack.maps.inspector.mapDetailsViewTitle', {
defaultMessage: 'Map details'
}),
order: 30,
help: `View the map state`,
help: i18n.translate('xpack.maps.inspector.mapDetailsViewHelpText', {
defaultMessage: 'View the map state'
}),
shouldShow(adapters) {
return Boolean(adapters.map);
},

View file

@ -36,10 +36,10 @@ export async function getDataSources() {
return loadingMetaPromise;
}
/**
* Should only call this after verifying `isMetadataLoaded` equals true
*/
export function getDataSourcesSync() {
if (!isLoaded) {
throw new Error('Metadata is not loaded yet. Use isMetadataLoaded first before calling this function.');
}
return meta;
}

View file

@ -8,14 +8,18 @@ import {
FeatureCatalogueRegistryProvider,
FeatureCatalogueCategory
} from 'ui/registry/feature_catalogue';
import { i18n } from '@kbn/i18n';
import { APP_ID, APP_ICON } from '../common/constants';
import { getAppTitle } from '../common/i18n_getters';
FeatureCatalogueRegistryProvider.register(() => {
return {
id: 'maps',
title: 'Maps',
description: 'Explore geospatial data from Elasticsearch and the Elastic Maps Service',
icon: 'gisApp',
id: APP_ID,
title: getAppTitle(),
description: i18n.translate('xpack.maps.feature.appDescription', {
defaultMessage: 'Explore geospatial data from Elasticsearch and the Elastic Maps Service'
}),
icon: APP_ICON,
path: '/app/maps',
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA

View file

@ -15,6 +15,7 @@ import {
EuiToolTip,
EuiIconTip
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
function flattenPanelTree(tree, array = []) {
array.push(tree);
@ -103,7 +104,7 @@ export class LayerTocActions extends Component {
if (layer.hasErrors()) {
smallLegendIcon = (
<EuiIconTip
aria-label="Load warning"
aria-label={i18n.translate('xpack.maps.layerTocActions.loadWarningAriaLabel', { defaultMessage: 'Load warning' })}
size="m"
type="alert"
color="warning"
@ -118,8 +119,12 @@ export class LayerTocActions extends Component {
smallLegendIcon = (
<EuiToolTip
position="top"
content={`Map is at zoom level ${zoom}.
This layer is only visible between zoom levels ${minZoom} to ${maxZoom}.`}
content={
i18n.translate('xpack.maps.layerTocActions.zoomFeedbackTooltip', {
defaultMessage: `Map is at zoom level {zoom}.
This layer is only visible between zoom levels {minZoom} to {maxZoom}.`,
values: { minZoom, maxZoom, zoom }
})}
>
{icon}
</EuiToolTip>
@ -135,10 +140,14 @@ export class LayerTocActions extends Component {
const visibilityToggle = this._getVisbilityIcon();
const panelTree = {
id: 0,
title: 'Layer actions',
title: i18n.translate('xpack.maps.layerTocActions.layerActionsTitle', {
defaultMessage: 'Layer actions',
}),
items: [
{
name: 'Fit to data',
name: i18n.translate('xpack.maps.layerTocActions.fitToDataTitle', {
defaultMessage: 'Fit to data',
}),
icon: (
<EuiIcon
type="search"
@ -146,7 +155,9 @@ export class LayerTocActions extends Component {
/>
),
'data-test-subj': 'fitToBoundsButton',
toolTipContent: this.state.supportsFitToBounds ? null : 'Layer does not support fit to data',
toolTipContent: this.state.supportsFitToBounds ? null : i18n.translate('xpack.maps.layerTocActions.noFitSupportTooltip', {
defaultMessage: 'Layer does not support fit to data',
}),
disabled: !this.state.supportsFitToBounds,
onClick: () => {
this._closePopover();
@ -154,7 +165,11 @@ export class LayerTocActions extends Component {
},
},
{
name: this.props.layer.isVisible() ? 'Hide layer' : 'Show layer',
name: this.props.layer.isVisible() ? i18n.translate('xpack.maps.layerTocActions.hideLayerTitle', {
defaultMessage: 'Hide layer',
}) : i18n.translate('xpack.maps.layerTocActions.showLayerTitle', {
defaultMessage: 'Show layer',
}),
icon: visibilityToggle,
'data-test-subj': 'layerVisibilityToggleButton',
onClick: () => {

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { LayerTocActions } from './layer_toc_actions';
@ -41,7 +41,7 @@ describe('LayerTocActions', () => {
});
test('is rendered', async () => {
const component = shallow(
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
@ -58,7 +58,7 @@ describe('LayerTocActions', () => {
test('should disable fit to data when supportsFitToBounds is false', async () => {
supportsFitToBounds = false;
const component = shallow(
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
@ -75,7 +75,7 @@ describe('LayerTocActions', () => {
test('should display spinner when layer is loading', async () => {
isLayerLoading = true;
const component = shallow(
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
@ -92,7 +92,7 @@ describe('LayerTocActions', () => {
test('should show warning when layer has errors', async () => {
hasErrors = true;
const component = shallow(
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
@ -109,7 +109,7 @@ describe('LayerTocActions', () => {
test('should show visible toggle when layer is not visible', async () => {
isVisible = false;
const component = shallow(
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>
@ -126,7 +126,7 @@ describe('LayerTocActions', () => {
test('should provide feedback when layer is not visible because of current zoom level', async () => {
showAtZoomLevel = false;
const component = shallow(
const component = shallowWithIntl(
<LayerTocActions
{...defaultProps}
/>

View file

@ -25,6 +25,8 @@ import {
EuiCallOut,
EuiBetaBadge,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
export const EMPTY_FILTER = '';
@ -86,7 +88,9 @@ export class MapListing extends React.Component {
await this.props.delete(this.state.selectedIds);
} catch (error) {
toastNotifications.addDanger({
title: `Unable to delete map(s)`,
title: i18n.translate('xpack.maps.mapListing.unableToDeleteToastTitle', {
defaultMessage: `Unable to delete map(s)`
}),
text: `${error}`,
});
}
@ -168,14 +172,25 @@ export class MapListing extends React.Component {
return (
<EuiOverlayMask>
<EuiConfirmModal
title="Delete selected items?"
title={i18n.translate('xpack.maps.mapListing.deleteSelectedItemsTitle', {
defaultMessage: 'Delete selected items?'
})}
onCancel={this.closeDeleteModal}
onConfirm={this.deleteSelectedItems}
cancelButtonText="Cancel"
confirmButtonText="Delete"
cancelButtonText={i18n.translate('xpack.maps.mapListing.cancelTitle', {
defaultMessage: 'Cancel'
})}
confirmButtonText={i18n.translate('xpack.maps.mapListing.deleteTitle', {
defaultMessage: 'Delete'
})}
defaultFocusedButton="cancel"
>
<p>{`You can't recover deleted items.`}</p>
<p>
<FormattedMessage
id="xpack.maps.mapListing.deleteWarning"
defaultMessage="You can't recover deleted items."
/>
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);
@ -186,14 +201,32 @@ export class MapListing extends React.Component {
return (
<React.Fragment>
<EuiCallOut
title="Listing limit exceeded"
title={
i18n.translate('xpack.maps.mapListing.limitExceededTitle', {
defaultMessage: 'Listing limit exceeded'
})
}
color="warning"
iconType="help"
>
<p>
You have {this.state.totalItems} items,
but your <strong>listingLimit</strong> setting prevents the table below from displaying more than {this.props.listingLimit}.
You can change this setting under <EuiLink href="#/management/kibana/settings">Advanced Settings</EuiLink>.
<FormattedMessage
id="xpack.maps.mapListing.limitHelpDescription"
defaultMessage="You have {totalItems} items,
but your <strong>listingLimit</strong> setting prevents the table below from displaying more than {listingLimit}.
You can change this setting under "
values={{
totalItems: this.state.totalItems,
listingLimit: this.props.listingLimit
}}
/>
<EuiLink href="#/management/kibana/settings">
<FormattedMessage
id="xpack.maps.mapListing.advancedSettingsLinkText"
defaultMessage="Advanced Settings"
/>
</EuiLink>.
</p>
</EuiCallOut>
<EuiSpacer size="m" />
@ -208,10 +241,14 @@ export class MapListing extends React.Component {
}
if (this.hasNoItems()) {
return `Looks like you don't have any maps. Click the create button to create one.`;
return i18n.translate('xpack.maps.mapListing.noItemsDescription', {
defaultMessage: `Looks like you don't have any maps. Click the create button to create one.`
});
}
return 'No items matched your search.';
return i18n.translate('xpack.maps.mapListing.noMatchDescription', {
defaultMessage: 'No items matched your search.'
});
}
renderSearchBar() {
@ -225,7 +262,10 @@ export class MapListing extends React.Component {
data-test-subj="deleteSelectedItems"
key="delete"
>
Delete selected
<FormattedMessage
id="xpack.maps.mapListing.deleteSelectedButtonLabel"
defaultMessage="Delete selected"
/>
</EuiButton>
</EuiFlexItem>
);
@ -236,8 +276,12 @@ export class MapListing extends React.Component {
{deleteBtn}
<EuiFlexItem grow={true}>
<EuiFieldSearch
aria-label="Filter items"
placeholder="Search..."
aria-label={i18n.translate('xpack.maps.mapListing.searchAriaLabel', {
defaultMessage: 'Filter items'
})}
placeholder={i18n.translate('xpack.maps.mapListing.searchPlaceholder', {
defaultMessage: 'Search...'
})}
fullWidth
value={this.state.filter}
onChange={(e) => {
@ -256,7 +300,9 @@ export class MapListing extends React.Component {
const tableColumns = [
{
field: 'title',
name: 'Title',
name: i18n.translate('xpack.maps.mapListing.titleFieldTitle', {
defaultMessage: 'Title'
}),
sortable: true,
render: (field, record) => (
<EuiLink
@ -269,7 +315,9 @@ export class MapListing extends React.Component {
},
{
field: 'description',
name: 'Description',
name: i18n.translate('xpack.maps.mapListing.descriptionFieldTitle', {
defaultMessage: 'Description'
}),
dataType: 'string',
sortable: true,
}
@ -321,7 +369,10 @@ export class MapListing extends React.Component {
data-test-subj="newMapLink"
fill
>
Create map
<FormattedMessage
id="xpack.maps.mapListing.createMapButtonLabel"
defaultMessage="Create map"
/>
</EuiButton>
</EuiFlexItem>
);
@ -335,7 +386,10 @@ export class MapListing extends React.Component {
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1>
Maps
<FormattedMessage
id="xpack.maps.mapListing.listingTableTitle"
defaultMessage="Maps"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
@ -343,7 +397,11 @@ export class MapListing extends React.Component {
<EuiFlexItem grow={false}>
<EuiBetaBadge
label="Beta"
tooltipContent="Maps is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo."
tooltipContent={
i18n.translate('xpack.maps.mapListing.betaMessageBadge', {
defaultMessage: 'Maps is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.'
})
}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -6,15 +6,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import { EuiComboBox } from '@elastic/eui';
const AGG_OPTIONS = [
{ label: 'Average', value: 'avg' },
{ label: 'Count', value: 'count' },
{ label: 'Max', value: 'max' },
{ label: 'Min', value: 'min' },
{ label: 'Sum', value: 'sum' },
{ label: i18n.translate('xpack.maps.metricSelect.averageDropDownOptionLabel', {
defaultMessage: 'Average'
}),
value: 'avg' },
{ label: i18n.translate('xpack.maps.metricSelect.countDropDownOptionLabel', {
defaultMessage: 'Count'
}),
value: 'count' },
{ label: i18n.translate('xpack.maps.metricSelect.maxDropDownOptionLabel', {
defaultMessage: 'Max'
}),
value: 'max' },
{ label: i18n.translate('xpack.maps.metricSelect.minDropDownOptionLabel', {
defaultMessage: 'Min'
}),
value: 'min' },
{ label: i18n.translate('xpack.maps.metricSelect.sumDropDownOptionLabel', {
defaultMessage: 'Sum'
}),
value: 'sum' },
];
export const METRIC_AGGREGATION_VALUES = AGG_OPTIONS.map(({ value }) => { return value; });
@ -34,7 +49,11 @@ export function MetricSelect({ value, onChange, metricsFilter }) {
return (
<EuiComboBox
placeholder="Select aggregation"
placeholder={
i18n.translate('xpack.maps.metricSelect.selectAggregationPlaceholder', {
defaultMessage: 'Select aggregation'
})
}
singleSelection={true}
isClearable={false}
options={options}

View file

@ -6,6 +6,8 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
@ -60,7 +62,9 @@ export function MetricsEditor({ fields, metrics, onChange, allowMultipleMetrics,
fieldSelect = (
<EuiFlexItem>
<SingleFieldSelect
placeholder="Select field"
placeholder={i18n.translate('xpack.maps.metricsEditor.selectFieldPlaceholder', {
defaultMessage: 'Select field'
})}
value={metric.field}
onChange={onFieldChange}
filterField={filterNumberFields}
@ -77,8 +81,12 @@ export function MetricsEditor({ fields, metrics, onChange, allowMultipleMetrics,
<EuiButtonIcon
iconType="trash"
color="danger"
aria-label="Delete metric"
title="Delete metric"
aria-label={i18n.translate('xpack.maps.metricsEditor.deleteMetricAriaLabel', {
defaultMessage: 'Delete metric'
})}
title={i18n.translate('xpack.maps.metricsEditor.deleteMetricButtonLabel', {
defaultMessage: 'Delete metric'
})}
onClick={onRemove}
/>
</EuiFlexItem>
@ -120,8 +128,12 @@ export function MetricsEditor({ fields, metrics, onChange, allowMultipleMetrics,
return (<EuiButtonIcon
iconType="plusInCircle"
onClick={addMetric}
aria-label="Add metric"
title="Add metric"
aria-label={i18n.translate('xpack.maps.metricsEditor.addMetricAriaLabel', {
defaultMessage: 'Add metric'
})}
title={i18n.translate('xpack.maps.metricsEditor.addMetricButtonLabel', {
defaultMessage: 'Add metric'
})}
/>);
}

View file

@ -8,24 +8,45 @@ import chrome from 'ui/chrome';
import React from 'react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
export function NoIndexPatternCallout() {
return (
<EuiCallOut
title="Couldn't find any index patterns with geospatial fields"
title={
i18n.translate('xpack.maps.noIndexPattern.messageTitle', {
defaultMessage: `Couldn't find any index patterns with geospatial fields`
})
}
color="warning"
>
<p>
You&rsquo;ll need to{' '}
<FormattedMessage
id="xpack.maps.noIndexPattern.doThisPrefixDescription"
defaultMessage="You'll need to "
/>
<EuiLink href={chrome.addBasePath('/app/kibana#/management/kibana/index_pattern')}>
create an index pattern
</EuiLink>{' '}
with geospatial fields.
<FormattedMessage
id="xpack.maps.noIndexPattern.doThisLinkTextDescription"
defaultMessage="create an index pattern"
/>
</EuiLink>
<FormattedMessage
id="xpack.maps.noIndexPattern.doThisSuffixDescription"
defaultMessage=" with geospatial fields."
/>
</p>
<p>
Don&rsquo;t have any geospatial data sets?{' '}
<FormattedMessage
id="xpack.maps.noIndexPattern.hintDescription"
defaultMessage="Don't have any geospatial data sets? "
/>
<EuiLink href={chrome.addBasePath('/app/kibana#/home/tutorial_directory/sampleData')}>
Get started with some sample data sets.
<FormattedMessage
id="xpack.maps.noIndexPattern.getStartedLinkText"
defaultMessage="Get started with some sample data sets."
/>
</EuiLink>
</p>
</EuiCallOut>

View file

@ -7,6 +7,7 @@
import React, { Fragment } from 'react';
import { EuiRange, EuiFormErrorText } from '@elastic/eui';
import { FormattedText } from '@kbn/i18n/react';
function isWithinRange(min, max, value) {
if (value >= min && value <= max) {
@ -68,7 +69,11 @@ export class ValidatedRange extends React.Component {
if (!this.state.isValid) {
errorMessage = (
<EuiFormErrorText>
{`Must be between ${min} and ${max}`}
<FormattedText
id="xpack.maps.validatedRange.rangeErrorMessage"
defaultMessage="Must be between {min} and {max}"
values={{ min, max }}
/>
</EuiFormErrorText>
);
}

View file

@ -20,15 +20,14 @@ export class HeatmapLayer extends AbstractLayer {
static createDescriptor(options) {
const heatmapLayerDescriptor = super.createDescriptor(options);
heatmapLayerDescriptor.type = HeatmapLayer.type;
const defaultStyle = HeatmapStyle.createDescriptor('coarse');
heatmapLayerDescriptor.style = defaultStyle;
heatmapLayerDescriptor.style = HeatmapStyle.createDescriptor();
return heatmapLayerDescriptor;
}
constructor({ layerDescriptor, source, style }) {
super({ layerDescriptor, source, style });
if (!style) {
const defaultStyle = HeatmapStyle.createDescriptor('coarse');
const defaultStyle = HeatmapStyle.createDescriptor();
this._style = new HeatmapStyle(defaultStyle);
}
}

View file

@ -13,6 +13,7 @@ import {
import { getEmsVectorFilesMeta } from '../../../../meta';
import { getEmsUnavailableMessage } from '../ems_unavailable_message';
import { i18n } from '@kbn/i18n';
export class EMSFileCreateSourceEditor extends React.Component {
@ -63,11 +64,19 @@ export class EMSFileCreateSourceEditor extends React.Component {
return (
<EuiFormRow
label="Layer"
label={
i18n.translate('xpack.maps.source.emsFile.layerLabel', {
defaultMessage: 'Layer'
})
}
helpText={this.state.emsFileOptionsRaw.length === 0 ? getEmsUnavailableMessage() : null}
>
<EuiComboBox
placeholder="Select EMS vector shapes"
placeholder={
i18n.translate('xpack.maps.source.emsFile.selectPlaceholder', {
defaultMessage: 'Select EMS vector shapes'
})
}
options={options}
selectedOptions={this.state.selectedOption ? [this.state.selectedOption] : []}
onChange={this._onChange}

View file

@ -10,12 +10,18 @@ import { GIS_API_PATH, EMS_FILE } from '../../../../../common/constants';
import { emsServiceSettings } from '../../../../kibana_services';
import { getEmsVectorFilesMeta } from '../../../../meta';
import { EMSFileCreateSourceEditor } from './create_source_editor';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
export class EMSFileSource extends AbstractVectorSource {
static type = EMS_FILE;
static title = 'Vector shapes';
static description = 'Vector shapes of administrative boundaries from Elastic Maps Service';
static title = i18n.translate('xpack.maps.source.emsFileTitle', {
defaultMessage: 'Vector shapes'
});
static description = i18n.translate('xpack.maps.source.emsFileDescription', {
defaultMessage: 'Vector shapes of administrative boundaries from Elastic Maps Service'
});
static icon = 'emsApp';
static createDescriptor(id) {
@ -38,7 +44,12 @@ export class EMSFileSource extends AbstractVectorSource {
const emsFiles = await getEmsVectorFilesMeta();
const meta = emsFiles.find((source => source.id === this._descriptor.id));
if (!meta) {
throw new Error(`Unable to find EMS vector shapes for id: ${this._descriptor.id}`);
throw new Error(i18n.translate('xpack.maps.source.emsFile.unableToFindIdErrorMessage', {
defaultMessage: `Unable to find EMS vector shapes for id: {id}`,
values: {
id: this._descriptor.id
}
}));
}
return meta;
}
@ -59,8 +70,16 @@ export class EMSFileSource extends AbstractVectorSource {
async getImmutableProperties() {
const emsLink = await emsServiceSettings.getEMSHotLink({ id: this._descriptor.id });
return [
{ label: 'Data source', value: EMSFileSource.title },
{ label: 'Layer', value: this._descriptor.id, link: emsLink }
{
label: getDataSourceLabel(),
value: EMSFileSource.title
},
{
label: i18n.translate('xpack.maps.source.emsFile.layerLabel', {
defaultMessage: `Layer`,
}),
value: this._descriptor.id,
link: emsLink }
];
}

View file

@ -13,6 +13,7 @@ import {
import { getEmsTMSServices } from '../../../../meta';
import { getEmsUnavailableMessage } from '../ems_unavailable_message';
import { i18n } from '@kbn/i18n';
export class EMSTMSCreateSourceEditor extends React.Component {
@ -53,7 +54,9 @@ export class EMSTMSCreateSourceEditor extends React.Component {
return (
<EuiFormRow
label="Tile service"
label={i18n.translate('xpack.maps.source.emsTile.label', {
defaultMessage: 'Tile service',
})}
helpText={this.state.emsTmsOptionsRaw.length === 0 ? getEmsUnavailableMessage() : null}
>
<EuiSelect

View file

@ -9,13 +9,19 @@ import { TileLayer } from '../../tile_layer';
import { getEmsTMSServices } from '../../../../meta';
import { EMSTMSCreateSourceEditor } from './create_source_editor';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
export class EMSTMSSource extends AbstractTMSSource {
static type = 'EMS_TMS';
static title = 'Tiles';
static description = 'Map tiles from Elastic Maps Service';
static title = i18n.translate('xpack.maps.source.emsTileTitle', {
defaultMessage: 'Tiles'
});
static description = i18n.translate('xpack.maps.source.emsTileDescription', {
defaultMessage: 'Map tiles from Elastic Maps Service'
});
static icon = 'emsApp';
static createDescriptor(serviceId) {
@ -39,8 +45,16 @@ export class EMSTMSSource extends AbstractTMSSource {
async getImmutableProperties() {
return [
{ label: 'Data source', value: EMSTMSSource.title },
{ label: 'Tile service', value: this._descriptor.id }
{
label: getDataSourceLabel(),
value: EMSTMSSource.title
},
{
label: i18n.translate('xpack.maps.source.emsTile.serviceId', {
defaultMessage: `Tile service`,
}),
value: this._descriptor.id
}
];
}
@ -50,7 +64,10 @@ export class EMSTMSSource extends AbstractTMSSource {
return service.id === this._descriptor.id;
});
if (!meta) {
throw new Error(`Unable to find EMS tile configuration for id: ${this._descriptor.id}`);
throw new Error(i18n.translate('xpack.maps.source.emsTile.errorMessage', {
defaultMessage: `Unable to find EMS tile configuration for id: {id}`,
values: { id: this._descriptor.id }
}));
}
return meta;
}

View file

@ -5,19 +5,18 @@
*/
import chrome from 'ui/chrome';
const NO_EMS_ACCESS_MSG =
'Kibana is unable to access Elastic Maps Service. Contact your system administrator';
const EMS_ACCESS_DISABLED_MSG =
'Access to Elastic Maps Service has been disabled.' +
' Ask your system administrator to set "map.includeElasticMapsService" in kibana.yml.';
import { i18n } from '@kbn/i18n';
export function getEmsUnavailableMessage() {
const isEmsEnabled = chrome.getInjected('isEmsEnabled', true);
if (isEmsEnabled) {
return NO_EMS_ACCESS_MSG;
return i18n.translate('xpack.maps.source.ems.noAccessDescription', {
defaultMessage: 'Kibana is unable to access Elastic Maps Service. Contact your system administrator'
});
}
return EMS_ACCESS_DISABLED_MSG;
return i18n.translate('xpack.maps.source.ems.disabledDescription', {
// eslint-disable-next-line max-len
defaultMessage: 'Access to Elastic Maps Service has been disabled. Ask your system administrator to set "map.includeElasticMapsService" in kibana.yml.'
});
}

View file

@ -13,6 +13,7 @@ import { SingleFieldSelect } from '../../../components/single_field_select';
import { RENDER_AS } from './render_as';
import { indexPatternService } from '../../../../kibana_services';
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
import { i18n } from '@kbn/i18n';
import {
EuiFormRow,
@ -26,15 +27,21 @@ function filterGeoField({ type }) {
const requestTypeOptions = [
{
label: 'points',
label: i18n.translate('xpack.maps.source.esGeoGrid.pointsDropdownOption', {
defaultMessage: 'points'
}),
value: RENDER_AS.POINT
},
{
label: 'grid rectangles',
label: i18n.translate('xpack.maps.source.esGeoGrid.gridRectangleDropdownOption', {
defaultMessage: 'grid rectangles'
}),
value: RENDER_AS.GRID
},
{
label: 'heat map',
label: i18n.translate('xpack.maps.source.esGeoGrid.heatmapDropdownOption', {
defaultMessage: 'heat map'
}),
value: RENDER_AS.HEATMAP
}
];
@ -146,9 +153,14 @@ export class CreateSourceEditor extends Component {
}
return (
<EuiFormRow label="Geospatial field">
<EuiFormRow label={i18n.translate('xpack.maps.source.esGeoGrid.geofieldLabel', {
defaultMessage: 'Geospatial field'
})}
>
<SingleFieldSelect
placeholder="Select geo field"
placeholder={i18n.translate('xpack.maps.source.esGeoGrid.geofieldPlaceholder', {
defaultMessage: 'Select geo field'
})}
value={this.state.geoField}
onChange={this._onGeoFieldSelect}
filterField={filterGeoField}
@ -164,9 +176,14 @@ export class CreateSourceEditor extends Component {
}
return (
<EuiFormRow label="Show as">
<EuiFormRow label={i18n.translate('xpack.maps.source.esGeoGrid.showAsLabel', {
defaultMessage: 'Show as'
})}
>
<EuiComboBox
placeholder="Select a single option"
placeholder={i18n.translate('xpack.maps.source.esGeoGrid.showAsPlaceholder', {
defaultMessage: 'Select a single option'
})}
singleSelection={{ asPlainText: true }}
options={requestTypeOptions}
selectedOptions={[this.state.requestType]}
@ -178,12 +195,17 @@ export class CreateSourceEditor extends Component {
_renderIndexPatternSelect() {
return (
<EuiFormRow label="Index pattern">
<EuiFormRow label={i18n.translate('xpack.maps.source.esGeoGrid.indexPatternLabel', {
defaultMessage: 'Index pattern'
})}
>
<IndexPatternSelect
isDisabled={this.state.noGeoIndexPatternsExist}
indexPatternId={this.state.indexPatternId}
onChange={this.onIndexPatternSelect}
placeholder="Select index pattern"
placeholder={i18n.translate('xpack.maps.source.esGeoGrid.indexPatternPlaceholder', {
defaultMessage: 'Select index pattern'
})}
fieldTypes={['geo_point']}
onNoIndexPatterns={this._onNoIndexPatterns}
/>

View file

@ -21,6 +21,8 @@ import { UpdateSourceEditor } from './update_source_editor';
import { GRID_RESOLUTION } from '../../grid_resolution';
import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID } from '../../../../../common/constants';
import { filterPropertiesForTooltip } from '../../util';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
const COUNT_PROP_LABEL = 'count';
const COUNT_PROP_NAME = 'doc_count';
@ -51,8 +53,12 @@ const aggSchemas = new Schemas([
export class ESGeoGridSource extends AbstractESSource {
static type = ES_GEO_GRID;
static title = 'Grid aggregation';
static description = 'Geospatial data grouped in grids with metrics for each gridded cell';
static title = i18n.translate('xpack.maps.source.esGridTitle', {
defaultMessage: 'Grid aggregation'
});
static description = i18n.translate('xpack.maps.source.esGridDescription', {
defaultMessage: 'Geospatial data grouped in grids with metrics for each gridded cell'
});
static createDescriptor({ indexPatternId, geoField, requestType, resolution }) {
return {
@ -102,10 +108,27 @@ export class ESGeoGridSource extends AbstractESSource {
}
return [
{ label: 'Data source', value: ESGeoGridSource.title },
{ label: 'Index pattern', value: indexPatternTitle },
{ label: 'Geospatial field', value: this._descriptor.geoField },
{ label: 'Show as', value: this._descriptor.requestType },
{
label: getDataSourceLabel(),
value: ESGeoGridSource.title
},
{
label: i18n.translate('xpack.maps.source.esGrid.indexPatternLabel', {
defaultMessage: 'Index pattern'
}),
value: indexPatternTitle },
{
label: i18n.translate('xpack.maps.source.esGrid.geospatialFieldLabel', {
defaultMessage: 'Geospatial field'
}),
value: this._descriptor.geoField
},
{
label: i18n.translate('xpack.maps.source.esGrid.showasFieldLabel', {
defaultMessage: 'Show as'
}),
value: this._descriptor.requestType
},
];
}
@ -145,7 +168,12 @@ export class ESGeoGridSource extends AbstractESSource {
return 4;
}
throw new Error(`Grid resolution param not recognized: ${this._descriptor.resolution}`);
throw new Error(i18n.translate('xpack.maps.source.esGrid.resolutionParamErrorMessage', {
defaultMessage: `Grid resolution param not recognized: {resolution}`,
values: {
resolution: this._descriptor.resolution
}
}));
}
async getGeoJsonWithMeta(layerName, searchFilters) {
@ -172,7 +200,9 @@ export class ESGeoGridSource extends AbstractESSource {
const searchSource = await this._makeSearchSource(searchFilters, 0);
const aggConfigs = new AggConfigs(indexPattern, this._makeAggConfigs(searchFilters.geogridPrecision), aggSchemas.all);
searchSource.setField('aggs', aggConfigs.toDsl());
const esResponse = await this._runEsQuery(layerName, searchSource, 'Elasticsearch geohash_grid aggregation request');
const esResponse = await this._runEsQuery(layerName, searchSource, i18n.translate('xpack.maps.source.esGrid.inspectorDescription', {
defaultMessage: 'Elasticsearch geo grid aggregation request'
}));
const tabifiedResp = tabifyAggResponse(aggConfigs, esResponse);
const { featureCollection } = convertToGeoJson({

View file

@ -12,12 +12,29 @@ import {
EuiFlexItem,
EuiFormLabel
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
const OPTIONS = [
{ value: GRID_RESOLUTION.COARSE, inputDisplay: 'coarse' },
{ value: GRID_RESOLUTION.FINE, inputDisplay: 'fine' },
{ value: GRID_RESOLUTION.MOST_FINE, inputDisplay: 'finest' }
{
value: GRID_RESOLUTION.COARSE,
inputDisplay: i18n.translate('xpack.maps.source.esGrid.coarseDropdownOption', {
defaultMessage: 'coarse'
})
},
{ value: GRID_RESOLUTION.FINE,
inputDisplay: i18n.translate('xpack.maps.source.esGrid.fineDropdownOption', {
defaultMessage: 'fine'
})
},
{
value: GRID_RESOLUTION.MOST_FINE,
inputDisplay: i18n.translate('xpack.maps.source.esGrid.finestDropdownOption', {
defaultMessage: 'finest'
})
}
];
export function ResolutionEditor({ resolution, onChange }) {
@ -26,7 +43,10 @@ export function ResolutionEditor({ resolution, onChange }) {
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={true}>
<EuiFormLabel style={{ marginBottom: 0 }}>
Grid resolution
<FormattedMessage
id="xpack.maps.geoGrid.resolutionLabel"
defaultMessage="Grid resolution"
/>
</EuiFormLabel>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -10,6 +10,7 @@ import { RENDER_AS } from './render_as';
import { MetricsEditor } from '../../../components/metrics_editor';
import { indexPatternService } from '../../../../kibana_services';
import { ResolutionEditor } from './resolution_editor';
import { i18n } from '@kbn/i18n';
export class UpdateSourceEditor extends Component {
@ -33,7 +34,12 @@ export class UpdateSourceEditor extends Component {
} catch (err) {
if (this._isMounted) {
this.setState({
loadError: `Unable to find Index pattern ${this.props.indexPatternId}`
loadError: i18n.translate('xpack.maps.source.esGrid.noIndexPatternErrorMessage', {
defaultMessage: `Unable to find Index pattern {id}`,
values: {
id: this.props.indexPatternId
}
})
});
}
return;

View file

@ -14,6 +14,7 @@ import {
} from '../../../kibana_services';
import { AggConfigs } from 'ui/vis/agg_configs';
import { timefilter } from 'ui/timefilter/timefilter';
import { i18n } from '@kbn/i18n';
const TERMS_AGG_NAME = 'join';
@ -62,6 +63,7 @@ export class ESJoinSource extends AbstractESSource {
static renderEditor({}) {
//no need to localize. this editor is never rendered.
return `<div>editor details</div>`;
}
@ -123,7 +125,12 @@ export class ESJoinSource extends AbstractESSource {
requestDesc: this.getJoinDescription(leftSourceName, leftFieldName),
});
} catch (error) {
throw new Error(`Elasticsearch search request failed, error: ${error.message}`);
throw new Error(i18n.translate('xpack.maps.source.esJoin.errorMessage', {
defaultMessage: `Elasticsearch search request failed, error: {message}`,
values: {
message: error.message
}
}));
}
const metricPropertyNames = configStates
@ -154,10 +161,21 @@ export class ESJoinSource extends AbstractESSource {
return metric.type !== 'count' ? `${metric.type} ${metric.field}` : 'count';
});
const joinStatement = [];
joinStatement.push(`Join ${leftSourceName}:${leftFieldName} with`);
joinStatement.push(i18n.translate('xpack.maps.source.esJoin.joinLeftDescription', {
defaultMessage: `Join {leftSourceName}:{leftFieldName} with`,
values: { leftSourceName, leftFieldName }
}));
joinStatement.push(`${this._descriptor.indexPatternTitle}:${this._descriptor.term}`);
joinStatement.push(`for metrics ${metrics.join(',')}`);
return `Elasticsearch terms aggregation request for ${joinStatement.join(' ')}`;
joinStatement.push(i18n.translate('xpack.maps.source.esJoin.joinMetricsDescription', {
defaultMessage: `for metrics {metrics}`,
values: { metrics: metrics.join(',') }
}));
return i18n.translate('xpack.maps.source.esJoin.joinDescription', {
defaultMessage: `Elasticsearch terms aggregation request for {description}`,
values: {
description: joinStatement.join(' ')
}
});
}
_makeAggConfigs() {
@ -191,6 +209,7 @@ export class ESJoinSource extends AbstractESSource {
}
async getDisplayName() {
//no need to localize. this is never rendered.
return `es_table ${this._descriptor.indexPatternId}`;
}
}

View file

@ -13,6 +13,7 @@ import { IndexPatternSelect } from 'ui/index_patterns/components/index_pattern_s
import { SingleFieldSelect } from '../../../components/single_field_select';
import { indexPatternService } from '../../../../kibana_services';
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
import { i18n } from '@kbn/i18n';
function filterGeoField(field) {
return ['geo_point', 'geo_shape'].includes(field.type);
@ -22,14 +23,14 @@ export class CreateSourceEditor extends Component {
static propTypes = {
onSelect: PropTypes.func.isRequired,
}
};
state = {
isLoadingIndexPattern: false,
indexPatternId: '',
geoField: '',
noGeoIndexPatternsExist: false,
}
};
componentWillUnmount() {
this._isMounted = false;
@ -52,7 +53,7 @@ export class CreateSourceEditor extends Component {
indexPattern: undefined,
geoField: undefined,
}, this.debouncedLoad.bind(null, indexPatternId));
}
};
debouncedLoad = _.debounce(async (indexPatternId) => {
if (!indexPatternId || indexPatternId.length === 0) {
@ -126,10 +127,14 @@ export class CreateSourceEditor extends Component {
return (
<EuiFormRow
label="Geospatial field"
label={i18n.translate('xpack.maps.source.esSearch.geofieldLabel', {
defaultMessage: 'Geospatial field'
})}
>
<SingleFieldSelect
placeholder="Select geo field"
placeholder={i18n.translate('xpack.maps.source.esSearch.selectLabel', {
defaultMessage: 'Select geo field'
})}
value={this.state.geoField}
onChange={this.onGeoFieldSelect}
filterField={filterGeoField}
@ -159,13 +164,18 @@ export class CreateSourceEditor extends Component {
{this._renderNoIndexPatternWarning()}
<EuiFormRow
label="Index pattern"
label={
i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', {
defaultMessage: 'Index pattern'
})}
>
<IndexPatternSelect
isDisabled={this.state.noGeoIndexPatternsExist}
indexPatternId={this.state.indexPatternId}
onChange={this.onIndexPatternSelect}
placeholder="Select index pattern"
placeholder={i18n.translate('xpack.maps.source.esSearch.selectIndexPatternPlaceholder', {
defaultMessage: 'Select index pattern'
})}
fieldTypes={['geo_point', 'geo_shape']}
onNoIndexPatterns={this._onNoIndexPatterns}
/>

View file

@ -13,14 +13,20 @@ import { hitsToGeoJson } from '../../../../elasticsearch_geo_utils';
import { CreateSourceEditor } from './create_source_editor';
import { UpdateSourceEditor } from './update_source_editor';
import { ES_SEARCH } from '../../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
const DEFAULT_LIMIT = 2048;
export class ESSearchSource extends AbstractESSource {
static type = ES_SEARCH;
static title = 'Documents';
static description = 'Geospatial data from a Kibana index pattern';
static title = i18n.translate('xpack.maps.source.esSearchTitle', {
defaultMessage: 'Documents'
});
static description = i18n.translate('xpack.maps.source.esSearchDescription', {
defaultMessage: 'Geospatial data from a Kibana index pattern'
});
static renderEditor({ onPreviewSource, inspectorAdapters }) {
const onSelect = (sourceConfig) => {
@ -96,10 +102,28 @@ export class ESSearchSource extends AbstractESSource {
}
return [
{ label: 'Data source', value: ESSearchSource.title },
{ label: 'Index pattern', value: indexPatternTitle },
{ label: 'Geospatial field', value: this._descriptor.geoField },
{ label: 'Geospatial field type', value: geoFieldType },
{
label: getDataSourceLabel(),
value: ESSearchSource.title
},
{
label: i18n.translate('xpack.maps.source.esSearch.indexPatternLabel', {
defaultMessage: `Index pattern`,
}),
value: indexPatternTitle
},
{
label: i18n.translate('xpack.maps.source.esSearch.geoFieldLabel', {
defaultMessage: 'Geospatial field',
}),
value: this._descriptor.geoField
},
{
label: i18n.translate('xpack.maps.source.esSearch.geoFieldTypeLabel', {
defaultMessage: 'Geospatial field type',
}),
value: geoFieldType
},
];
}

View file

@ -13,6 +13,7 @@ import {
import { MultiFieldSelect } from '../../../components/multi_field_select';
import { indexPatternService } from '../../../../kibana_services';
import { i18n } from '@kbn/i18n';
export class UpdateSourceEditor extends Component {
@ -43,7 +44,12 @@ export class UpdateSourceEditor extends Component {
} catch (err) {
if (this._isMounted) {
this.setState({
loadError: `Unable to find Index pattern ${this.props.indexPatternId}`
loadError: i18n.translate('xpack.maps.source.esSearch.loadErrorMessage', {
defaultMessage: `Unable to find Index pattern {id}`,
values: {
id: this.props.indexPatternId
}
})
});
}
return;
@ -68,10 +74,17 @@ export class UpdateSourceEditor extends Component {
return (
<Fragment>
<EuiFormRow
label="Fields to display in tooltip"
label={
i18n.translate('xpack.maps.source.esSearch.fieldsLabel', {
defaultMessage: `Fields to display in tooltip`
})
}
>
<MultiFieldSelect
placeholder="Select fields"
placeholder={i18n.translate('xpack.maps.source.esSearch.fieldsPlaceholder', {
defaultMessage: `Select fields`
})
}
value={this.props.tooltipProperties}
onChange={this.onTooltipPropertiesSelect}
fields={this.state.fields}
@ -80,7 +93,12 @@ export class UpdateSourceEditor extends Component {
<EuiFormRow>
<EuiSwitch
label="Dynamically filter for data in the visible map area."
label={
i18n.translate('xpack.maps.source.esSearch.extentFilterLabel', {
defaultMessage: `Dynamically filter for data in the visible map area.`
})
}
checked={this.props.filterByMapBounds}
onChange={this.onFilterByMapBoundsChange}
/>

View file

@ -14,6 +14,7 @@ import { createExtentFilter } from '../../../elasticsearch_geo_utils';
import { timefilter } from 'ui/timefilter/timefilter';
import _ from 'lodash';
import { AggConfigs } from 'ui/vis/agg_configs';
import { i18n } from '@kbn/i18n';
export class AbstractESSource extends AbstractVectorSource {
@ -87,7 +88,10 @@ export class AbstractESSource extends AbstractVectorSource {
requestDesc: requestDescription
});
} catch(error) {
throw new Error(`Elasticsearch search request failed, error: ${error.message}`);
throw new Error('xpack.maps.source.esSource.requestFailedErrorMessage', {
defaultMessage: `Elasticsearch search request failed, error: {message}`,
values: { message: error.message }
});
}
}
@ -174,7 +178,10 @@ export class AbstractESSource extends AbstractVectorSource {
this.indexPattern = await indexPatternService.get(this._descriptor.indexPatternId);
return this.indexPattern;
} catch (error) {
throw new Error(`Unable to find Index pattern for id: ${this._descriptor.indexPatternId}`);
throw new Error(i18n.translate('xpack.maps.source.esSource.noIndexPatternErrorMessage', {
defaultMessage: `Unable to find Index pattern for id: {indexPatternId}`,
values: { indexPatternId: this._descriptor.indexPatternId }
}));
}
}
@ -194,7 +201,10 @@ export class AbstractESSource extends AbstractVectorSource {
const indexPattern = await this._getIndexPattern();
const geoField = indexPattern.fields.byName[this._descriptor.geoField];
if (!geoField) {
throw new Error(`Index pattern ${indexPattern.title} no longer contains the geo field ${this._descriptor.geoField}`);
throw new Error(i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', {
defaultMessage: `Index pattern {indexPatternTitle} no longer contains the geo field {geoField}`,
values: { indexPatternTitle: indexPattern.title, geoField: this._descriptor.geoField }
}));
}
return geoField;
}

View file

@ -11,10 +11,7 @@ import {
EuiFormRow,
} from '@elastic/eui';
import { getKibanaRegionList } from '../../../../meta';
const NO_REGIONMAP_LAYERS_MSG =
'No vector layers are available.' +
' Ask your system administrator to set "map.regionmap" in kibana.yml.';
import { i18n } from '@kbn/i18n';
export class CreateSourceEditor extends React.Component {
@ -42,8 +39,6 @@ export class CreateSourceEditor extends React.Component {
render() {
const onChange = ({ target }) => {
const selectedName = target.options[target.selectedIndex].text;
this.props.onSelect({ name: selectedName });
@ -58,8 +53,15 @@ export class CreateSourceEditor extends React.Component {
return (
<EuiFormRow
label="Vector layer"
helpText={this.state.regionmapLayers.length === 0 ? NO_REGIONMAP_LAYERS_MSG : null}
label={
i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerLabel', {
defaultMessage: 'Vector layer'
})
}
helpText={this.state.regionmapLayers.length === 0 ? i18n.translate('xpack.maps.source.kbnRegionMap.noLayerAvailableHelptext', {
defaultMessage: `No vector layers are available. Ask your system administrator to set "map.regionmap" in kibana.yml.`
})
: null}
>
<EuiSelect
hasNoInitialSelection

View file

@ -7,14 +7,20 @@
import { AbstractVectorSource } from '../vector_source';
import React from 'react';
import { CreateSourceEditor } from './create_source_editor';
import { getKibanaRegionList } from '../../../../meta';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
export class KibanaRegionmapSource extends AbstractVectorSource {
static type = 'REGIONMAP_FILE';
static title = 'Custom vector shapes';
static description = 'Vector shapes from static files configured in kibana.yml';
static title = i18n.translate('xpack.maps.source.kbnRegionMapTitle', {
defaultMessage: 'Custom vector shapes'
});
static description = i18n.translate('xpack.maps.source.kbnRegionMapDescription', {
defaultMessage: 'Vector shapes from static files configured in kibana.yml'
})
;
static icon = 'logoKibana';
static createDescriptor(options) {
@ -40,8 +46,15 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
async getImmutableProperties() {
return [
{ label: 'Data source', value: KibanaRegionmapSource.title },
{ label: 'Vector layer', value: this._descriptor.name },
{
label: getDataSourceLabel(),
value: KibanaRegionmapSource.title },
{
label: i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerLabel', {
defaultMessage: 'Vector layer'
}),
value: this._descriptor.name
},
];
}
@ -49,7 +62,13 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
const regionList = await getKibanaRegionList();
const meta = regionList.find(source => source.name === this._descriptor.name);
if (!meta) {
throw new Error(`Unable to find map.regionmap configuration for ${this._descriptor.name}`);
throw new Error(i18n.translate('xpack.maps.source.kbnRegionMap.noConfigErrorMessage', {
defaultMessage: `Unable to find map.regionmap configuration for {name}`,
values: {
name: this._descriptor.name
}
})
);
}
return meta;
}

View file

@ -12,12 +12,7 @@ import {
} from '@elastic/eui';
import { getKibanaTileMap } from '../../../../meta';
const NO_TILEMAP_LAYER_MSG =
'No tilemap layer is available.' +
' Ask your system administrator to set "map.tilemap.url" in kibana.yml.';
import { i18n } from '@kbn/i18n';
export class CreateSourceEditor extends Component {
@ -56,8 +51,15 @@ export class CreateSourceEditor extends Component {
return (
<EuiFormRow
label="Tilemap url"
helpText={this.state.url ? null : NO_TILEMAP_LAYER_MSG}
label={
i18n.translate('xpack.maps.source.kbnTMS.kbnTMS.urlLabel', {
defaultMessage: 'Tilemap url'
})
}
helpText={this.state.url ? null : i18n.translate('xpack.maps.source.kbnTMS.noLayerAvailableHelptext', {
defaultMessage: 'No tilemap layer is available. Ask your system administrator to set "map.tilemap.url" in kibana.yml.'
})
}
>
<EuiFieldText
readOnly

View file

@ -8,12 +8,19 @@ import { AbstractTMSSource } from '../tms_source';
import { TileLayer } from '../../tile_layer';
import { CreateSourceEditor } from './create_source_editor';
import { getKibanaTileMap } from '../../../../meta';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
export class KibanaTilemapSource extends AbstractTMSSource {
static type = 'KIBANA_TILEMAP';
static title = 'Custom Tile Map Service';
static description = 'Map tiles configured in kibana.yml';
static title = i18n.translate('xpack.maps.source.kbnTMSTitle', {
defaultMessage: 'Custom Tile Map Service'
});
static description = i18n.translate('xpack.maps.source.kbnTMSDescription', {
defaultMessage: 'Map tiles configured in kibana.yml'
});
static icon = 'logoKibana';
static createDescriptor() {
@ -33,8 +40,16 @@ export class KibanaTilemapSource extends AbstractTMSSource {
async getImmutableProperties() {
return [
{ label: 'Data source', value: KibanaTilemapSource.title },
{ label: 'Tilemap url', value: (await this.getUrlTemplate()) },
{
label: getDataSourceLabel(),
value: KibanaTilemapSource.title
},
{
label: i18n.translate('xpack.maps.source.kbnTMS.urlLabel', {
defaultMessage: 'Tilemap url'
}),
value: (await this.getUrlTemplate())
},
];
}
@ -55,7 +70,9 @@ export class KibanaTilemapSource extends AbstractTMSSource {
async getUrlTemplate() {
const tilemap = await getKibanaTileMap();
if (!tilemap.url) {
throw new Error(`Unable to find map.tilemap.url configuration in the kibana.yml`);
throw new Error(i18n.translate('xpack.maps.source.kbnTMS.noConfigErrorMessage', {
defaultMessage: `Unable to find map.tilemap.url configuration in the kibana.yml`
}));
}
return tilemap.url;
}

View file

@ -10,6 +10,7 @@ import { VectorStyle } from '../styles/vector_style';
import { AbstractSource } from './source';
import * as topojson from 'topojson-client';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
export class AbstractVectorSource extends AbstractSource {
@ -24,7 +25,10 @@ export class AbstractVectorSource extends AbstractSource {
}
fetchedJson = await response.json();
} catch (e) {
throw new Error(`Unable to fetch vector shapes from url: ${fetchUrl}`);
throw new Error(i18n.translate('xpack.maps.source.vetorSource.requestFailedErrorMessage', {
defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`,
values: { fetchUrl }
}));
}
if (format === 'geojson') {
@ -36,7 +40,10 @@ export class AbstractVectorSource extends AbstractSource {
return topojson.feature(fetchedJson, features);
}
throw new Error(`Unrecognized vector shape format: ${format}`);
throw new Error(i18n.translate('xpack.maps.source.vetorSource.formatErrorMessage', {
defaultMessage: `Unable to fetch vector shapes from url: {format}`,
values: { format }
}));
}
_createDefaultLayerDescriptor(options, mapColors) {

View file

@ -13,12 +13,18 @@ import {
import { AbstractTMSSource } from './tms_source';
import { TileLayer } from '../tile_layer';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
export class WMSSource extends AbstractTMSSource {
static type = 'WMS';
static title = 'Web Map Service';
static description = 'Maps from OGC Standard WMS';
static title = i18n.translate('xpack.maps.source.wmsTitle', {
defaultMessage: 'Web Map Service'
});
static description = i18n.translate('xpack.maps.source.wmsDescription', {
defaultMessage: 'Maps from OGC Standard WMS'
});
static icon = 'grid';
static createDescriptor({ serviceUrl, layers, styles }) {
@ -41,10 +47,14 @@ export class WMSSource extends AbstractTMSSource {
async getImmutableProperties() {
return [
{ label: 'Data source', value: WMSSource.title },
{ label: 'Url', value: this._descriptor.serviceUrl },
{ label: 'Layers', value: this._descriptor.layers },
{ label: 'Styles', value: this._descriptor.styles },
{ label: getDataSourceLabel(), value: WMSSource.title },
{ label: getUrlLabel(), value: this._descriptor.serviceUrl },
{ label: i18n.translate('xpack.maps.source.wms.layersLabel', {
defaultMessage: 'Layers'
}), value: this._descriptor.layers },
{ label: i18n.translate('xpack.maps.source.wms.stylesLabel', {
defaultMessage: 'Styles'
}), value: this._descriptor.styles },
];
}
@ -67,8 +77,7 @@ export class WMSSource extends AbstractTMSSource {
}
getUrlTemplate() {
console.warn('should compose url using url formatter, not string formatting');
const styles = this._descriptor.styles ? this._descriptor.styles : '';
const styles = this._descriptor.styles || '';
// eslint-disable-next-line max-len
return `${this._descriptor.serviceUrl}?bbox={bbox-epsg-3857}&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:3857&transparent=true&width=256&height=256&layers=${this._descriptor.layers}&styles=${styles}`;
}
@ -125,12 +134,26 @@ class WMSEditor extends React.Component {
onChange={(e) => this._handleServiceUrlChange(e)}
/>
</EuiFormRow>
<EuiFormRow label="Layers" helpText={'use comma separated list of layer names'}>
<EuiFormRow
label={i18n.translate('xpack.maps.source.wms.layersLabel', {
defaultMessage: 'Layers'
})}
helpText={i18n.translate('xpack.maps.source.wms.layersHelpText', {
defaultMessage: 'use comma separated list of layer names'
})}
>
<EuiFieldText
onChange={(e) => this._handleLayersChange(e)}
/>
</EuiFormRow>
<EuiFormRow label="Styles" helpText={'use comma separated list of style names'}>
<EuiFormRow
label={i18n.translate('xpack.maps.source.wms.stylesLabel', {
defaultMessage: 'Styles'
})}
helpText={i18n.translate('xpack.maps.source.wms.stylesHelpText', {
defaultMessage: 'use comma separated list of style names'
})}
>
<EuiFieldText
onChange={(e) => this._handleStylesChange(e)}
/>

View file

@ -13,12 +13,18 @@ import {
import { AbstractTMSSource } from './tms_source';
import { TileLayer } from '../tile_layer';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
export class XYZTMSSource extends AbstractTMSSource {
static type = 'EMS_XYZ';
static title = 'Tile Map Service from URL';
static description = 'Map tiles from a URL that includes the XYZ coordinates';
static title = i18n.translate('xpack.maps.source.ems_xyzTitle', {
defaultMessage: 'Tile Map Service from URL'
});
static description = i18n.translate('xpack.maps.source.ems_xyzDescription', {
defaultMessage: 'Map tiles from a URL that includes the XYZ coordinates'
});
static icon = 'grid';
static createDescriptor(urlTemplate) {
@ -39,8 +45,8 @@ export class XYZTMSSource extends AbstractTMSSource {
async getImmutableProperties() {
return [
{ label: 'Data source', value: XYZTMSSource.title },
{ label: 'Url', value: this._descriptor.urlTemplate },
{ label: getDataSourceLabel(), value: XYZTMSSource.title },
{ label: getUrlLabel(), value: this._descriptor.urlTemplate },
];
}
@ -92,7 +98,7 @@ class XYZTMSEditor extends React.Component {
return (
<EuiFormRow label="Url">
<EuiFieldText
placeholder="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"
placeholder={'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'}
onChange={(e) => this._handleTMSInputChange(e)}
/>
</EuiFormRow>

View file

@ -7,6 +7,7 @@
import React from 'react';
import { VectorStyle } from '../vector_style';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
@ -93,7 +94,13 @@ export class StaticDynamicStyleRow extends React.Component {
render() {
const isDynamic = this._isDynamic();
const dynamicTooltipContent =
isDynamic ? 'Use static styling properties to symbolize features.' : 'Use property values to symbolize features.';
isDynamic ?
i18n.translate('xpack.maps.styles.staticDynamic.staticDescription', {
defaultMessage: 'Use static styling properties to symbolize features.'
}) :
i18n.translate('xpack.maps.styles.staticDynamic.dynamicDescription', {
defaultMessage: 'Use property values to symbolize features.'
});
return (
<EuiFlexGroup gutterSize="s">

View file

@ -9,6 +9,7 @@ import React from 'react';
import { EuiComboBox } from '@elastic/eui';
import { SOURCE_DATA_ID_ORIGIN } from '../../../../../../common/constants';
import { i18n } from '@kbn/i18n';
export function FieldSelect({ fields, selectedField, onChange }) {
@ -64,7 +65,11 @@ export function FieldSelect({ fields, selectedField, onChange }) {
singleSelection={{ asPlainText: true }}
isClearable={false}
fullWidth
placeholder="Select a field"
placeholder={
i18n.translate('xpack.maps.styles.vector.selectFieldPlaceholder', {
defaultMessage: 'Select a field'
})
}
/>
);
}

View file

@ -12,6 +12,7 @@ import {
EuiFlexItem,
} from '@elastic/eui';
import { ValidatedRange } from '../../../../../components/validated_range';
import { i18n } from '@kbn/i18n';
import { DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE } from '../../../vector_style_defaults';
export function SizeRangeSelector({ minSize, maxSize, onChange }) {
@ -35,7 +36,11 @@ export function SizeRangeSelector({ minSize, maxSize, onChange }) {
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Min size"
label={
i18n.translate('xpack.maps.styles.vector.size.minLabel', {
defaultMessage: 'Min size'
})
}
compressed
>
<ValidatedRange
@ -50,7 +55,11 @@ export function SizeRangeSelector({ minSize, maxSize, onChange }) {
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="Max size"
label={
i18n.translate('xpack.maps.styles.vector.size.maxLabel', {
defaultMessage: 'Max size'
})
}
compressed
>
<ValidatedRange

View file

@ -12,6 +12,7 @@ import { VectorStyleSizeEditor } from './size/vector_style_size_editor';
import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../../vector_style_defaults';
import { EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
export class VectorStyleEditor extends Component {
state = {
@ -49,7 +50,11 @@ export class VectorStyleEditor extends Component {
<VectorStyleColorEditor
styleProperty="fillColor"
stylePropertyName="Fill color"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.fillColorLabel', {
defaultMessage: 'Fill color'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.fillColor}
ordinalFields={this.state.ordinalFields}
@ -61,7 +66,11 @@ export class VectorStyleEditor extends Component {
<VectorStyleColorEditor
styleProperty="lineColor"
stylePropertyName="Border color"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.borderColorLabel', {
defaultMessage: 'Border color'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineColor}
ordinalFields={this.state.ordinalFields}
@ -73,7 +82,11 @@ export class VectorStyleEditor extends Component {
<VectorStyleSizeEditor
styleProperty="lineWidth"
stylePropertyName="Border width"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.borderWidthLabel', {
defaultMessage: 'Border width'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineWidth}
ordinalFields={this.state.ordinalFields}
@ -85,7 +98,11 @@ export class VectorStyleEditor extends Component {
<VectorStyleSizeEditor
styleProperty="iconSize"
stylePropertyName="Symbol size"
stylePropertyName={
i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', {
defaultMessage: 'Symbol size'
})
}
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.iconSize}
ordinalFields={this.state.ordinalFields}

View file

@ -6,35 +6,31 @@
import { GRID_RESOLUTION } from '../grid_resolution';
import { AbstractStyle } from './abstract_style';
import { i18n } from '@kbn/i18n';
export class HeatmapStyle extends AbstractStyle {
static type = 'HEATMAP';
constructor(styleDescriptor = {}) {
constructor() {
super();
this._descriptor = HeatmapStyle.createDescriptor(
styleDescriptor.refinement,
styleDescriptor.properties
);
this._descriptor = HeatmapStyle.createDescriptor();
}
static canEdit(styleInstance) {
return styleInstance.constructor === HeatmapStyle;
}
static createDescriptor(refinement, properties = {}) {
static createDescriptor() {
return {
type: HeatmapStyle.type,
refinement: refinement || 'coarse',
properties: {
...properties
}
};
}
static getDisplayName() {
return 'Heatmap style';
return i18n.translate('xpack.maps.style.heatmap.displayNameLabel', {
defaultMessage: 'Heatmap style'
});
}
static renderEditor() {
@ -50,7 +46,11 @@ export class HeatmapStyle extends AbstractStyle {
} else if (resolution === GRID_RESOLUTION.MOST_FINE) {
radius = 32;
} else {
throw new Error(`Refinement param not recognized: ${this._descriptor.refinement}`);
const errorMessage = i18n.translate('xpack.maps.style.heatmap.resolutionStyleErrorMessage', {
defaultMessage: `Resolution param not recognized: {resolution}`,
values: { resolution }
});
throw new Error(errorMessage);
}
mbMap.setPaintProperty(layerId, 'heatmap-radius', radius);
mbMap.setPaintProperty(layerId, 'heatmap-weight', {

View file

@ -5,6 +5,7 @@
*/
import { AbstractStyle } from './abstract_style';
import { i18n } from '@kbn/i18n';
export class TileStyle extends AbstractStyle {
@ -29,6 +30,8 @@ export class TileStyle extends AbstractStyle {
}
static getDisplayName() {
return 'Tile style';
return i18n.translate('xpack.maps.style.tile.displayNameLabel', {
defaultMessage: 'Tile style'
});
}
}

View file

@ -6,7 +6,7 @@
import _ from 'lodash';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FillableCircle, FillableVector } from '../../icons/additional_layer_icons';
import { ColorGradient } from '../../icons/color_gradient';
import { getHexColorRangeStrings } from '../../utils/color_utils';
@ -45,7 +45,9 @@ export class VectorStyle extends AbstractStyle {
}
static getDisplayName() {
return 'Vector style';
return i18n.translate('xpack.maps.style.vector.displayNameLabel', {
defaultMessage: 'Vector style'
});
}
static description = '';

View file

@ -11,8 +11,9 @@ import chroma from 'chroma-js';
export function getRGBColorRangeStrings(colorName, numberColors) {
const colorKeys = Object.keys(vislibColorMaps);
if (!colorKeys.includes(colorName)) {
throw `${colorName} not found. Expected one of following values: \
${colorKeys}`;
//This is an internal error and should never occur. If it does, then it is a bug.
throw new Error(`${colorName} not found. Expected one of following values: \
${colorKeys}`);
}
return getLegendColors(vislibColorMaps[colorName].value, numberColors);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable max-len */
import { i18n } from '@kbn/i18n';
export const getFlightsSavedObjects = () => {
return [
{
'id': '5dd88580-1906-11e9-919b-ffe5949a18d2',
'type': 'map',
'updated_at': '2019-01-15T20:44:54.767Z',
'version': 2,
'references': [
{
'name': 'layer_1_source_index_pattern',
'type': 'index-pattern',
'id': 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
},
{
'name': 'layer_2_source_index_pattern',
'type': 'index-pattern',
'id': 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
},
{
'name': 'layer_3_source_index_pattern',
'type': 'index-pattern',
'id': 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
}
],
'migrationVersion': {
'map': '7.1.0'
},
'attributes': {
'title': i18n.translate('xpack.maps.sampleData.flightaSpec.mapsTitle', {
defaultMessage: '[Flights] Origin and Destination Flight Time'
}),
'description': '',
'mapStateJSON': '{"zoom":3.14,"center":{"lon":-89.58746,"lat":38.38637},"timeFilters":{"from":"now-7d","to":"now"},"refreshConfig":{"isPaused":true,"interval":0},"query":{"query":"","language":"kuery"}}',
'layerListJSON': '[{"id":"0hmz5","alpha":1,"sourceDescriptor":{"type":"EMS_TMS","id":"road_map"},"visible":true,"style":{"type":"TILE","properties":{}},"type":"TILE","minZoom":0,"maxZoom":24},{"id":"jzppx","label":"Flights","minZoom":9,"maxZoom":24,"alpha":1,"sourceDescriptor":{"id":"040e0f25-9687-4569-a1e0-76f1a108da56","type":"ES_SEARCH","geoField":"DestLocation","limit":2048,"filterByMapBounds":true,"tooltipProperties":["Carrier","DestCityName","DestCountry","OriginCityName","OriginCountry","FlightDelayMin","FlightTimeMin","DistanceMiles","AvgTicketPrice","FlightDelay"],"indexPatternRefName":"layer_1_source_index_pattern"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"label":"FlightTimeMin","name":"FlightTimeMin","origin":"source"},"color":"Greens"}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"DYNAMIC","options":{"field":{"label":"DistanceMiles","name":"DistanceMiles","origin":"source"},"minSize":1,"maxSize":32}}}},"type":"VECTOR"},{"id":"y4jsz","label":"Flight Origin Location","minZoom":0,"maxZoom":9,"alpha":1,"sourceDescriptor":{"type":"ES_GEO_GRID","resolution":"COARSE","id":"fe893f84-388e-4865-8df4-650748533a77","geoField":"OriginLocation","requestType":"point","metrics":[{"type":"count"},{"type":"avg","field":"FlightTimeMin"}],"indexPatternRefName":"layer_2_source_index_pattern"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"label":"Count","name":"doc_count","origin":"source"},"color":"Blues"}},"lineColor":{"type":"STATIC","options":{"color":"#110081"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"DYNAMIC","options":{"field":{"label":"avg of FlightTimeMin","name":"avg_of_FlightTimeMin","origin":"source"},"minSize":1,"maxSize":32}}}},"type":"VECTOR"},{"id":"x8xpo","label":"Flight Destination Location","minZoom":0,"maxZoom":9,"alpha":1,"sourceDescriptor":{"type":"ES_GEO_GRID","resolution":"COARSE","id":"60a7346a-8c5f-4c03-b7d1-e8b36e847551","geoField":"DestLocation","requestType":"point","metrics":[{"type":"count"},{"type":"avg","field":"FlightDelayMin"}],"indexPatternRefName":"layer_3_source_index_pattern"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"label":"Count","name":"doc_count","origin":"source"},"color":"Reds"}},"lineColor":{"type":"STATIC","options":{"color":"#af0303"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"DYNAMIC","options":{"field":{"label":"avg of FlightDelayMin","name":"avg_of_FlightDelayMin","origin":"source"},"minSize":1,"maxSize":32}}}},"type":"VECTOR"}]',
'uiStateJSON': '{"isDarkMode":false}',
'bounds': {
'type': 'envelope', 'coordinates': [[-139.83779, 56.64828], [-39.33713, 14.04811]] }
}
}
];
};

View file

@ -1,36 +0,0 @@
[
{
"id":"5dd88580-1906-11e9-919b-ffe5949a18d2",
"type":"map",
"updated_at":"2019-01-15T20:44:54.767Z",
"version":2,
"references" : [
{
"name" : "layer_1_source_index_pattern",
"type" : "index-pattern",
"id" : "d3d7af60-4c81-11e8-b3d7-01146121b73d"
},
{
"name" : "layer_2_source_index_pattern",
"type" : "index-pattern",
"id" : "d3d7af60-4c81-11e8-b3d7-01146121b73d"
},
{
"name" : "layer_3_source_index_pattern",
"type" : "index-pattern",
"id" : "d3d7af60-4c81-11e8-b3d7-01146121b73d"
}
],
"migrationVersion" : {
"map" : "7.1.0"
},
"attributes":{
"title":"[Flights] Origin and Destination Flight Time",
"description":"",
"mapStateJSON":"{\"zoom\":3.14,\"center\":{\"lon\":-89.58746,\"lat\":38.38637},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}",
"layerListJSON" : "[{\"id\":\"0hmz5\",\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"jzppx\",\"label\":\"Flights\",\"minZoom\":9,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"id\":\"040e0f25-9687-4569-a1e0-76f1a108da56\",\"type\":\"ES_SEARCH\",\"geoField\":\"DestLocation\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[\"Carrier\",\"DestCityName\",\"DestCountry\",\"OriginCityName\",\"OriginCountry\",\"FlightDelayMin\",\"FlightTimeMin\",\"DistanceMiles\",\"AvgTicketPrice\",\"FlightDelay\"],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"FlightTimeMin\",\"name\":\"FlightTimeMin\",\"origin\":\"source\"},\"color\":\"Greens\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"DistanceMiles\",\"name\":\"DistanceMiles\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":32}}}},\"type\":\"VECTOR\"},{\"id\":\"y4jsz\",\"label\":\"Flight Origin Location\",\"minZoom\":0,\"maxZoom\":9,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"resolution\":\"COARSE\",\"id\":\"fe893f84-388e-4865-8df4-650748533a77\",\"geoField\":\"OriginLocation\",\"requestType\":\"point\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"avg\",\"field\":\"FlightTimeMin\"}],\"indexPatternRefName\":\"layer_2_source_index_pattern\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#110081\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"avg of FlightTimeMin\",\"name\":\"avg_of_FlightTimeMin\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":32}}}},\"type\":\"VECTOR\"},{\"id\":\"x8xpo\",\"label\":\"Flight Destination Location\",\"minZoom\":0,\"maxZoom\":9,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"resolution\":\"COARSE\",\"id\":\"60a7346a-8c5f-4c03-b7d1-e8b36e847551\",\"geoField\":\"DestLocation\",\"requestType\":\"point\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"avg\",\"field\":\"FlightDelayMin\"}],\"indexPatternRefName\":\"layer_3_source_index_pattern\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Reds\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#af0303\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"avg of FlightDelayMin\",\"name\":\"avg_of_FlightDelayMin\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":32}}}},\"type\":\"VECTOR\"}]",
"uiStateJSON":"{\"isDarkMode\":false}",
"bounds":{"type":"envelope","coordinates":[[-139.83779,56.64828],[-39.33713,14.04811]]}
}
}
]

View file

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable max-len */
import { i18n } from '@kbn/i18n';
export const getWebLogsSavedObjects = () => {
return [
{
'id': 'de71f4f0-1902-11e9-919b-ffe5949a18d2',
'type': 'map',
'updated_at': '2019-01-15T20:30:25.436Z',
'version': 5,
'references': [
{
'name': 'layer_1_join_0_index_pattern',
'type': 'index-pattern',
'id': '90943e30-9a47-11e8-b64d-95841ca0b247'
},
{
'name': 'layer_2_source_index_pattern',
'type': 'index-pattern',
'id': '90943e30-9a47-11e8-b64d-95841ca0b247'
},
{
'name': 'layer_3_source_index_pattern',
'type': 'index-pattern',
'id': '90943e30-9a47-11e8-b64d-95841ca0b247'
}
],
'migrationVersion': {
'map': '7.1.0'
},
'attributes': {
'title': i18n.translate('xpack.maps.sampleData.flightaSpec.logsTitle', {
defaultMessage: '[Logs] Total Requests and Bytes'
}),
'description': '',
'mapStateJSON': '{"zoom":3.64,"center":{"lon":-88.92107,"lat":42.16337},"timeFilters":{"from":"now-7d","to":"now"},"refreshConfig":{"isPaused":true,"interval":0},"query":{"language":"kuery","query":""}}',
'layerListJSON': '[{"id":"0hmz5","alpha":1,"sourceDescriptor":{"type":"EMS_TMS","id":"road_map"},"visible":true,"style":{"type":"TILE","properties":{}},"type":"TILE","minZoom":0,"maxZoom":24},{"id":"edh66","label":"Total Requests by Country","minZoom":0,"maxZoom":24,"alpha":0.5,"sourceDescriptor":{"type":"EMS_FILE","id":"world_countries"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"label":"count of kibana_sample_data_logs:geo.src","name":"__kbnjoin__count_groupby_kibana_sample_data_logs.geo.src","origin":"join"},"color":"Greys"}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"STATIC","options":{"size":10}}}},"type":"VECTOR","joins":[{"leftField":"iso2","right":{"id":"673ff994-fc75-4c67-909b-69fcb0e1060e","indexPatternTitle":"kibana_sample_data_logs","term":"geo.src","indexPatternRefName":"layer_1_join_0_index_pattern"}}]},{"id":"gaxya","label":"Actual Requests","minZoom":9,"maxZoom":24,"alpha":1,"sourceDescriptor":{"id":"b7486535-171b-4d3b-bb2e-33c1a0a2854c","type":"ES_SEARCH","geoField":"geo.coordinates","limit":2048,"filterByMapBounds":true,"tooltipProperties":["clientip","timestamp","host","request","response","machine.os","agent","bytes"],"indexPatternRefName":"layer_2_source_index_pattern"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"STATIC","options":{"color":"#2200ff"}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":2}},"iconSize":{"type":"DYNAMIC","options":{"field":{"label":"bytes","name":"bytes","origin":"source"},"minSize":1,"maxSize":23}}}},"type":"VECTOR"},{"id":"tfi3f","label":"Total Requests and Bytes","minZoom":0,"maxZoom":9,"alpha":1,"sourceDescriptor":{"type":"ES_GEO_GRID","resolution":"COARSE","id":"8aaa65b5-a4e9-448b-9560-c98cb1c5ac5b","geoField":"geo.coordinates","requestType":"point","metrics":[{"type":"count"},{"type":"sum","field":"bytes"}],"indexPatternRefName":"layer_3_source_index_pattern"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"label":"Count","name":"doc_count","origin":"source"},"color":"Blues"}},"lineColor":{"type":"STATIC","options":{"color":"#cccccc"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"DYNAMIC","options":{"field":{"label":"sum of bytes","name":"sum_of_bytes","origin":"source"},"minSize":1,"maxSize":25}}}},"type":"VECTOR"}]',
'uiStateJSON': '{"isDarkMode":false}',
'bounds': { 'type': 'envelope', 'coordinates': [[-124.45342, 54.91445], [-53.38872, 26.21461]] }
}
}
];
};

View file

@ -1,36 +0,0 @@
[
{
"id":"de71f4f0-1902-11e9-919b-ffe5949a18d2",
"type":"map",
"updated_at":"2019-01-15T20:30:25.436Z",
"version":5,
"references" : [
{
"name" : "layer_1_join_0_index_pattern",
"type" : "index-pattern",
"id" : "90943e30-9a47-11e8-b64d-95841ca0b247"
},
{
"name" : "layer_2_source_index_pattern",
"type" : "index-pattern",
"id" : "90943e30-9a47-11e8-b64d-95841ca0b247"
},
{
"name" : "layer_3_source_index_pattern",
"type" : "index-pattern",
"id" : "90943e30-9a47-11e8-b64d-95841ca0b247"
}
],
"migrationVersion" : {
"map" : "7.1.0"
},
"attributes":{
"title":"[Logs] Total Requests and Bytes",
"description":"",
"mapStateJSON":"{\"zoom\":3.64,\"center\":{\"lon\":-88.92107,\"lat\":42.16337},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"}}",
"layerListJSON" : "[{\"id\":\"0hmz5\",\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"edh66\",\"label\":\"Total Requests by Country\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.5,\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"world_countries\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count of kibana_sample_data_logs:geo.src\",\"name\":\"__kbnjoin__count_groupby_kibana_sample_data_logs.geo.src\",\"origin\":\"join\"},\"color\":\"Greys\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}}},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"iso2\",\"right\":{\"id\":\"673ff994-fc75-4c67-909b-69fcb0e1060e\",\"indexPatternTitle\":\"kibana_sample_data_logs\",\"term\":\"geo.src\",\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]},{\"id\":\"gaxya\",\"label\":\"Actual Requests\",\"minZoom\":9,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"id\":\"b7486535-171b-4d3b-bb2e-33c1a0a2854c\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[\"clientip\",\"timestamp\",\"host\",\"request\",\"response\",\"machine.os\",\"agent\",\"bytes\"],\"indexPatternRefName\":\"layer_2_source_index_pattern\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#2200ff\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":2}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":23}}}},\"type\":\"VECTOR\"},{\"id\":\"tfi3f\",\"label\":\"Total Requests and Bytes\",\"minZoom\":0,\"maxZoom\":9,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"resolution\":\"COARSE\",\"id\":\"8aaa65b5-a4e9-448b-9560-c98cb1c5ac5b\",\"geoField\":\"geo.coordinates\",\"requestType\":\"point\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"sum\",\"field\":\"bytes\"}],\"indexPatternRefName\":\"layer_3_source_index_pattern\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"sum of bytes\",\"name\":\"sum_of_bytes\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":25}}}},\"type\":\"VECTOR\"}]",
"uiStateJSON":"{\"isDarkMode\":false}",
"bounds":{"type":"envelope","coordinates":[[-124.45342,54.91445],[-53.38872,26.21461]]}
}
}
]