this.setState({ chartsTheme })
+ this.subscription = combineLatest(
+ getServices().theme.chartsTheme$,
+ getServices().theme.chartsBaseTheme$
+ ).subscribe(([chartsTheme, chartsBaseTheme]) =>
+ this.setState({ chartsTheme, chartsBaseTheme })
);
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
- this.subscription = undefined;
}
}
@@ -204,7 +209,7 @@ export class DiscoverHistogram extends Component
({
+export const createLegacyUrlForwardApp = (
+ core: CoreSetup<{}, KibanaLegacyStart>,
+ forwards: ForwardDefinition[]
+): App => ({
id: 'kibana',
chromeless: true,
title: 'Legacy URL migration',
@@ -31,7 +34,8 @@ export const createLegacyUrlForwardApp = (core: CoreSetup, forwards: ForwardDefi
const hash = params.history.location.hash.substr(1);
if (!hash) {
- core.fatalErrors.add('Could not forward URL');
+ const [, , kibanaLegacyStart] = await core.getStartServices();
+ kibanaLegacyStart.navigateToDefaultApp();
}
const [
@@ -44,7 +48,8 @@ export const createLegacyUrlForwardApp = (core: CoreSetup, forwards: ForwardDefi
const result = await navigateToLegacyKibanaUrl(hash, forwards, basePath, application);
if (!result.navigated) {
- core.fatalErrors.add('Could not forward URL');
+ const [, , kibanaLegacyStart] = await core.getStartServices();
+ kibanaLegacyStart.navigateToDefaultApp();
}
return () => {};
diff --git a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx
index 63b9b48ec809e..45592c8a703af 100644
--- a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx
+++ b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
-import React, { Component } from 'react';
+import React, { Component, ReactNode } from 'react';
import { EuiFormRow, EuiDualRange } from '@elastic/eui';
import { EuiFormRowDisplayKeys } from '@elastic/eui/src/components/form/form_row/form_row';
import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range';
@@ -32,7 +32,7 @@ export type ValueMember = EuiDualRangeProps['value'][0];
interface Props extends Omit {
value?: Value;
allowEmptyRange?: boolean;
- label?: string;
+ label?: string | ReactNode;
formRowDisplay?: EuiFormRowDisplayKeys;
onChange?: (val: [string, string]) => void;
min?: number;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss
index 3db09bace079f..c445d456a1703 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss
@@ -2,7 +2,11 @@
display: flex;
flex-direction: column;
flex: 1 1 100%;
- padding: $euiSizeS;
+
+ // border used in lieu of padding to prevent overlapping background-color
+ border-width: $euiSizeS;
+ border-style: solid;
+ border-color: transparent;
.tvbVisTimeSeries {
overflow: hidden;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
index ddfaf3c1428d9..612a7a48bade1 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js
@@ -34,7 +34,7 @@ import { getInterval } from '../../lib/get_interval';
import { areFieldsDifferent } from '../../lib/charts';
import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';
import { STACKED_OPTIONS } from '../../../visualizations/constants';
-import { getCoreStart, getUISettings } from '../../../../services';
+import { getCoreStart } from '../../../../services';
export class TimeseriesVisualization extends Component {
static propTypes = {
@@ -154,7 +154,7 @@ export class TimeseriesVisualization extends Component {
const styles = reactCSS({
default: {
tvbVis: {
- backgroundColor: get(model, 'background_color'),
+ borderColor: get(model, 'background_color'),
},
},
});
@@ -237,7 +237,6 @@ export class TimeseriesVisualization extends Component {
}
});
- const darkMode = getUISettings().get('theme:darkMode');
return (
values.map(({ key, docs }) => ({
@@ -56,7 +56,6 @@ const handleCursorUpdate = (cursor) => {
};
export const TimeSeries = ({
- darkMode,
backgroundColor,
showGrid,
legend,
@@ -90,15 +89,15 @@ export const TimeSeries = ({
const timeZone = getTimezone(uiSettings);
const hasBarChart = series.some(({ bars }) => bars?.show);
- // compute the theme based on the bg color
- const theme = getTheme(darkMode, backgroundColor);
// apply legend style change if bgColor is configured
const classes = classNames('tvbVisTimeSeries', getChartClasses(backgroundColor));
// If the color isn't configured by the user, use the color mapping service
// to assign a color from the Kibana palette. Colors will be shared across the
// session, including dashboards.
- const { colors } = getChartsSetup();
+ const { colors, theme: themeService } = getChartsSetup();
+ const baseTheme = getBaseTheme(themeService.useChartsBaseTheme(), backgroundColor);
+
colors.mappedColors.mapKeys(series.filter(({ color }) => !color).map(({ label }) => label));
const onBrushEndListener = ({ x }) => {
@@ -118,7 +117,7 @@ export const TimeSeries = ({
onBrushEnd={onBrushEndListener}
animateData={false}
onPointerUpdate={handleCursorUpdate}
- theme={
+ theme={[
hasBarChart
? {}
: {
@@ -127,9 +126,14 @@ export const TimeSeries = ({
fill: '#F00',
},
},
- }
- }
- baseTheme={theme}
+ },
+ {
+ background: {
+ color: backgroundColor,
+ },
+ },
+ ]}
+ baseTheme={baseTheme}
tooltip={{
snap: true,
type: tooltipMode === 'show_focused' ? TooltipType.Follow : TooltipType.VerticalCursor,
@@ -269,7 +273,6 @@ TimeSeries.defaultProps = {
};
TimeSeries.propTypes = {
- darkMode: PropTypes.bool,
backgroundColor: PropTypes.string,
showGrid: PropTypes.bool,
legend: PropTypes.bool,
diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts
index 57ca38168ac27..d7e6560a8dc97 100644
--- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts
+++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.test.ts
@@ -17,28 +17,30 @@
* under the License.
*/
-import { getTheme } from './theme';
+import { getBaseTheme } from './theme';
import { LIGHT_THEME, DARK_THEME } from '@elastic/charts';
describe('TSVB theme', () => {
it('should return the basic themes if no bg color is specified', () => {
// use original dark/light theme
- expect(getTheme(false)).toEqual(LIGHT_THEME);
- expect(getTheme(true)).toEqual(DARK_THEME);
+ expect(getBaseTheme(LIGHT_THEME)).toEqual(LIGHT_THEME);
+ expect(getBaseTheme(DARK_THEME)).toEqual(DARK_THEME);
// discard any wrong/missing bg color
- expect(getTheme(true, null)).toEqual(DARK_THEME);
- expect(getTheme(true, '')).toEqual(DARK_THEME);
- expect(getTheme(true, undefined)).toEqual(DARK_THEME);
+ expect(getBaseTheme(DARK_THEME, null)).toEqual(DARK_THEME);
+ expect(getBaseTheme(DARK_THEME, '')).toEqual(DARK_THEME);
+ expect(getBaseTheme(DARK_THEME, undefined)).toEqual(DARK_THEME);
});
it('should return a highcontrast color theme for a different background', () => {
// red use a near full-black color
- expect(getTheme(false, 'red').axes.axisTitleStyle.fill).toEqual('rgb(23,23,23)');
+ expect(getBaseTheme(LIGHT_THEME, 'red').axes.axisTitleStyle.fill).toEqual('rgb(23,23,23)');
// violet increased the text color to full white for higer contrast
- expect(getTheme(false, '#ba26ff').axes.axisTitleStyle.fill).toEqual('rgb(255,255,255)');
+ expect(getBaseTheme(LIGHT_THEME, '#ba26ff').axes.axisTitleStyle.fill).toEqual(
+ 'rgb(255,255,255)'
+ );
// light yellow, prefer the LIGHT_THEME fill color because already with a good contrast
- expect(getTheme(false, '#fff49f').axes.axisTitleStyle.fill).toEqual('#333');
+ expect(getBaseTheme(LIGHT_THEME, '#fff49f').axes.axisTitleStyle.fill).toEqual('#333');
});
});
diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts
index 2694732aa381d..0e13fd7ef68f9 100644
--- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts
+++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/theme.ts
@@ -94,9 +94,15 @@ function isValidColor(color: string | null | undefined): color is string {
}
}
-export function getTheme(darkMode: boolean, bgColor?: string | null): Theme {
+/**
+ * compute base chart theme based on the background color
+ *
+ * @param baseTheme
+ * @param bgColor
+ */
+export function getBaseTheme(baseTheme: Theme, bgColor?: string | null): Theme {
if (!isValidColor(bgColor)) {
- return darkMode ? DARK_THEME : LIGHT_THEME;
+ return baseTheme;
}
const bgLuminosity = computeRelativeLuminosity(bgColor);
diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy
index e511d7a8fc15e..66ebe3478fbec 100644
--- a/vars/kibanaCoverage.groovy
+++ b/vars/kibanaCoverage.groovy
@@ -1,3 +1,46 @@
+def downloadPrevious(title) {
+ def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
+
+ withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
+ kibanaPipeline.bash('''
+
+ gsutil -m cp -r gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/previous.txt . || echo "### Previous Pointer NOT FOUND?"
+
+ if [ -e ./previous.txt ]; then
+ mv previous.txt downloaded_previous.txt
+ echo "### downloaded_previous.txt"
+ cat downloaded_previous.txt
+ fi
+
+ ''', title)
+
+ def previous = sh(script: 'cat downloaded_previous.txt', label: '### Capture Previous Sha', returnStdout: true).trim()
+
+ return previous
+ }
+}
+
+def uploadPrevious(title) {
+ def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
+
+ withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
+ kibanaPipeline.bash('''
+
+ collectPrevious() {
+ PREVIOUS=$(git log --pretty=format:%h -1)
+ echo "### PREVIOUS: ${PREVIOUS}"
+ echo $PREVIOUS > previous.txt
+ }
+ collectPrevious
+
+ gsutil cp previous.txt gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/
+
+
+ ''', title)
+
+ }
+}
+
def uploadCoverageStaticSite(timestamp) {
def uploadPrefix = "gs://elastic-bekitzur-kibana-coverage-live/"
def uploadPrefixWithTimeStamp = "${uploadPrefix}${timestamp}/"
@@ -67,6 +110,7 @@ EOF
cat src/dev/code_coverage/www/index.html
''', "### Combine Index Partials")
}
+
def collectVcsInfo(title) {
kibanaPipeline.bash('''
predicate() {
@@ -125,31 +169,31 @@ def uploadCombinedReports() {
)
}
-def ingestData(jobName, buildNum, buildUrl, title) {
+def ingestData(jobName, buildNum, buildUrl, previousSha, title) {
kibanaPipeline.bash("""
source src/dev/ci_setup/setup_env.sh
yarn kbn bootstrap --prefer-offline
# Using existing target/kibana-coverage folder
- . src/dev/code_coverage/shell_scripts/ingest_coverage.sh '${jobName}' ${buildNum} '${buildUrl}'
+ . src/dev/code_coverage/shell_scripts/ingest_coverage.sh '${jobName}' ${buildNum} '${buildUrl}' ${previousSha}
""", title)
}
-def ingestWithVault(jobName, buildNum, buildUrl, title) {
+def ingestWithVault(jobName, buildNum, buildUrl, previousSha, title) {
def vaultSecret = 'secret/kibana-issues/prod/coverage/elasticsearch'
withVaultSecret(secret: vaultSecret, secret_field: 'host', variable_name: 'HOST_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'username', variable_name: 'USER_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'password', variable_name: 'PASS_FROM_VAULT') {
- ingestData(jobName, buildNum, buildUrl, title)
+ ingestData(jobName, buildNum, buildUrl, previousSha, title)
}
}
}
}
-def ingest(jobName, buildNumber, buildUrl, timestamp, title) {
+def ingest(jobName, buildNumber, buildUrl, timestamp, previousSha, title) {
withEnv([
"TIME_STAMP=${timestamp}",
]) {
- ingestWithVault(jobName, buildNumber, buildUrl, title)
+ ingestWithVault(jobName, buildNumber, buildUrl, previousSha, title)
}
}
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
index 408cdd387cbd8..5f23a9329a583 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts
@@ -56,7 +56,6 @@ describe('timeseriesFetcher', () => {
apmAgentConfigurationIndex: '.apm-agent-configuration',
apmCustomLinkIndex: '.apm-custom-link',
},
- dynamicIndexPattern: null as any,
},
});
});
diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts
index c9e9db13cecae..b34d5535d58cc 100644
--- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts
@@ -11,15 +11,9 @@ import {
localUIFilters,
localUIFilterNames,
} from '../../ui_filters/local_ui_filters/config';
-import {
- esKuery,
- IIndexPattern,
-} from '../../../../../../../src/plugins/data/server';
+import { esKuery } from '../../../../../../../src/plugins/data/server';
-export function getUiFiltersES(
- indexPattern: IIndexPattern | undefined,
- uiFilters: UIFilters
-) {
+export function getUiFiltersES(uiFilters: UIFilters) {
const { kuery, environment, ...localFilterValues } = uiFilters;
const mappedFilters = localUIFilterNames
.filter((name) => name in localFilterValues)
@@ -35,7 +29,7 @@ export function getUiFiltersES(
// remove undefined items from list
const esFilters = [
- getKueryUiFilterES(indexPattern, uiFilters.kuery),
+ getKueryUiFilterES(uiFilters.kuery),
getEnvironmentUiFilterES(uiFilters.environment),
]
.filter((filter) => !!filter)
@@ -44,14 +38,11 @@ export function getUiFiltersES(
return esFilters;
}
-function getKueryUiFilterES(
- indexPattern: IIndexPattern | undefined,
- kuery?: string
-) {
- if (!kuery || !indexPattern) {
+function getKueryUiFilterES(kuery?: string) {
+ if (!kuery) {
return;
}
const ast = esKuery.fromKueryExpression(kuery);
- return esKuery.toElasticsearchQuery(ast, indexPattern) as ESFilter;
+ return esKuery.toElasticsearchQuery(ast) as ESFilter;
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/plugins/apm/server/lib/helpers/es_client.ts
index 892f8f0ddd105..2d730933e2473 100644
--- a/x-pack/plugins/apm/server/lib/helpers/es_client.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/es_client.ts
@@ -19,11 +19,10 @@ import {
ESSearchRequest,
ESSearchResponse,
} from '../../../typings/elasticsearch';
-import { UI_SETTINGS } from '../../../../../../src/plugins/data/server';
import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames';
import { pickKeys } from '../../../common/utils/pick_keys';
import { APMRequestHandlerContext } from '../../routes/typings';
-import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
+import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
// `type` was deprecated in 7.0
export type APMIndexDocumentParams = Omit, 'type'>;
@@ -85,20 +84,19 @@ function addFilterForLegacyData(
}
// add additional params for search (aka: read) requests
-async function getParamsForSearchRequest(
- context: APMRequestHandlerContext,
- params: ESSearchRequest,
- apmOptions?: APMOptions
-) {
- const { uiSettings } = context.core;
- const [indices, includeFrozen] = await Promise.all([
- getApmIndices({
- savedObjectsClient: context.core.savedObjects.client,
- config: context.config,
- }),
- uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
- ]);
-
+function getParamsForSearchRequest({
+ context,
+ params,
+ indices,
+ includeFrozen,
+ includeLegacyData,
+}: {
+ context: APMRequestHandlerContext;
+ params: ESSearchRequest;
+ indices: ApmIndicesConfig;
+ includeFrozen: boolean;
+ includeLegacyData?: boolean;
+}) {
// Get indices for legacy data filter (only those which apply)
const apmIndices = Object.values(
pickKeys(
@@ -112,7 +110,7 @@ async function getParamsForSearchRequest(
)
);
return {
- ...addFilterForLegacyData(apmIndices, params, apmOptions), // filter out pre-7.0 data
+ ...addFilterForLegacyData(apmIndices, params, { includeLegacyData }), // filter out pre-7.0 data
ignore_throttled: !includeFrozen, // whether to query frozen indices or not
};
}
@@ -123,6 +121,8 @@ interface APMOptions {
interface ClientCreateOptions {
clientAsInternalUser?: boolean;
+ indices: ApmIndicesConfig;
+ includeFrozen: boolean;
}
export type ESClient = ReturnType;
@@ -134,7 +134,7 @@ function formatObj(obj: Record) {
export function getESClient(
context: APMRequestHandlerContext,
request: KibanaRequest,
- { clientAsInternalUser = false }: ClientCreateOptions = {}
+ { clientAsInternalUser = false, indices, includeFrozen }: ClientCreateOptions
) {
const {
callAsCurrentUser,
@@ -194,11 +194,13 @@ export function getESClient(
params: TSearchRequest,
apmOptions?: APMOptions
): Promise> => {
- const nextParams = await getParamsForSearchRequest(
+ const nextParams = await getParamsForSearchRequest({
context,
params,
- apmOptions
- );
+ indices,
+ includeFrozen,
+ ...apmOptions,
+ });
return callEs('search', nextParams);
},
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
index 2dd8ed01082fd..14c9378d99192 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
@@ -5,8 +5,8 @@
*/
import moment from 'moment';
+import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
import { KibanaRequest } from '../../../../../../src/core/server';
-import { IIndexPattern } from '../../../../../../src/plugins/data/common';
import { APMConfig } from '../..';
import {
getApmIndices,
@@ -18,17 +18,13 @@ import { getUiFiltersES } from './convert_ui_filters/get_ui_filters_es';
import { APMRequestHandlerContext } from '../../routes/typings';
import { getESClient } from './es_client';
import { ProcessorEvent } from '../../../common/processor_event';
-import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern';
-function decodeUiFilters(
- indexPattern: IIndexPattern | undefined,
- uiFiltersEncoded?: string
-) {
- if (!uiFiltersEncoded || !indexPattern) {
+function decodeUiFilters(uiFiltersEncoded?: string) {
+ if (!uiFiltersEncoded) {
return [];
}
const uiFilters = JSON.parse(uiFiltersEncoded);
- return getUiFiltersES(indexPattern, uiFilters);
+ return getUiFiltersES(uiFilters);
}
// Explicitly type Setup to prevent TS initialization errors
// https://github.com/microsoft/TypeScript/issues/34933
@@ -39,7 +35,6 @@ export interface Setup {
ml?: ReturnType;
config: APMConfig;
indices: ApmIndicesConfig;
- dynamicIndexPattern?: IIndexPattern;
}
export interface SetupTimeRange {
@@ -75,28 +70,33 @@ export async function setupRequest(
const { config } = context;
const { query } = context.params;
- const indices = await getApmIndices({
- savedObjectsClient: context.core.savedObjects.client,
- config,
- });
+ const [indices, includeFrozen] = await Promise.all([
+ getApmIndices({
+ savedObjectsClient: context.core.savedObjects.client,
+ config,
+ }),
+ context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN),
+ ]);
- const dynamicIndexPattern = await getDynamicIndexPattern({
- context,
+ const createClientOptions = {
indices,
- processorEvent: query.processorEvent,
- });
+ includeFrozen,
+ };
- const uiFiltersES = decodeUiFilters(dynamicIndexPattern, query.uiFilters);
+ const uiFiltersES = decodeUiFilters(query.uiFilters);
const coreSetupRequest = {
indices,
- client: getESClient(context, request, { clientAsInternalUser: false }),
+ client: getESClient(context, request, {
+ clientAsInternalUser: false,
+ ...createClientOptions,
+ }),
internalClient: getESClient(context, request, {
clientAsInternalUser: true,
+ ...createClientOptions,
}),
ml: getMlSetup(context, request),
config,
- dynamicIndexPattern,
};
return {
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
index d1f473b485dc3..fb357040f5781 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts
@@ -44,7 +44,6 @@ describe('timeseriesFetcher', () => {
apmAgentConfigurationIndex: 'myIndex',
apmCustomLinkIndex: 'myIndex',
},
- dynamicIndexPattern: null as any,
},
});
});
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
index 5fdd6de06089b..1cecf14f2eeb8 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/get_local_filter_query.ts
@@ -5,7 +5,6 @@
*/
import { omit } from 'lodash';
-import { IIndexPattern } from 'src/plugins/data/server';
import { mergeProjection } from '../../../../common/projections/util/merge_projection';
import { Projection } from '../../../../common/projections/typings';
import { UIFilters } from '../../../../typings/ui_filters';
@@ -13,18 +12,16 @@ import { getUiFiltersES } from '../../helpers/convert_ui_filters/get_ui_filters_
import { localUIFilters, LocalUIFilterName } from './config';
export const getLocalFilterQuery = ({
- indexPattern,
uiFilters,
projection,
localUIFilterName,
}: {
- indexPattern: IIndexPattern | undefined;
uiFilters: UIFilters;
projection: Projection;
localUIFilterName: LocalUIFilterName;
}) => {
const field = localUIFilters[localUIFilterName];
- const filter = getUiFiltersES(indexPattern, omit(uiFilters, field.name));
+ const filter = getUiFiltersES(omit(uiFilters, field.name));
const bucketCountAggregation = projection.body.aggs
? {
diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
index ba12cf3e8282a..588d5c7896db9 100644
--- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
+++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts
@@ -26,7 +26,7 @@ export async function getLocalUIFilters({
uiFilters: UIFilters;
localFilterNames: LocalUIFilterName[];
}) {
- const { client, dynamicIndexPattern } = setup;
+ const { client } = setup;
const projectionWithoutAggs = cloneDeep(projection);
@@ -35,7 +35,6 @@ export async function getLocalUIFilters({
return Promise.all(
localFilterNames.map(async (name) => {
const query = getLocalFilterQuery({
- indexPattern: dynamicIndexPattern,
uiFilters,
projection,
localUIFilterName: name,
diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts
index 018a14ac76689..18bc2986d4061 100644
--- a/x-pack/plugins/apm/server/routes/index_pattern.ts
+++ b/x-pack/plugins/apm/server/routes/index_pattern.ts
@@ -9,6 +9,8 @@ import { createRoute } from './create_route';
import { setupRequest } from '../lib/helpers/setup_request';
import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client';
import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title';
+import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_pattern';
+import { getApmIndices } from '../lib/settings/apm_indices/get_apm_indices';
export const staticIndexPatternRoute = createRoute((core) => ({
method: 'POST',
@@ -34,8 +36,17 @@ export const dynamicIndexPatternRoute = createRoute(() => ({
]),
}),
},
- handler: async ({ context, request }) => {
- const { dynamicIndexPattern } = await setupRequest(context, request);
+ handler: async ({ context }) => {
+ const indices = await getApmIndices({
+ config: context.config,
+ savedObjectsClient: context.core.savedObjects.client,
+ });
+
+ const dynamicIndexPattern = await getDynamicIndexPattern({
+ context,
+ indices,
+ });
+
return { dynamicIndexPattern };
},
}));
diff --git a/x-pack/plugins/apm/server/routes/ui_filters.ts b/x-pack/plugins/apm/server/routes/ui_filters.ts
index 280645d4de8d0..a47d72751dfc4 100644
--- a/x-pack/plugins/apm/server/routes/ui_filters.ts
+++ b/x-pack/plugins/apm/server/routes/ui_filters.ts
@@ -97,10 +97,7 @@ function createLocalFiltersRoute<
query,
setup: {
...setup,
- uiFiltersES: getUiFiltersES(
- setup.dynamicIndexPattern,
- omit(parsedUiFilters, filterNames)
- ),
+ uiFiltersES: getUiFiltersES(omit(parsedUiFilters, filterNames)),
},
});
diff --git a/x-pack/plugins/features/server/plugin.test.ts b/x-pack/plugins/features/server/plugin.test.ts
index 79fd012337b00..3d85c2e9eb751 100644
--- a/x-pack/plugins/features/server/plugin.test.ts
+++ b/x-pack/plugins/features/server/plugin.test.ts
@@ -10,19 +10,13 @@ const initContext = coreMock.createPluginInitializerContext();
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
const typeRegistry = savedObjectsServiceMock.createTypeRegistryMock();
-typeRegistry.getAllTypes.mockReturnValue([
+typeRegistry.getVisibleTypes.mockReturnValue([
{
name: 'foo',
hidden: false,
mappings: { properties: {} },
namespaceType: 'single' as 'single',
},
- {
- name: 'bar',
- hidden: true,
- mappings: { properties: {} },
- namespaceType: 'agnostic' as 'agnostic',
- },
]);
coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry);
diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts
index 149c1acfb5086..5783b20eae648 100644
--- a/x-pack/plugins/features/server/plugin.ts
+++ b/x-pack/plugins/features/server/plugin.ts
@@ -80,10 +80,7 @@ export class Plugin {
private registerOssFeatures(savedObjects: SavedObjectsServiceStart) {
const registry = savedObjects.getTypeRegistry();
- const savedObjectTypes = registry
- .getAllTypes()
- .filter((t) => !t.hidden)
- .map((t) => t.name);
+ const savedObjectTypes = registry.getVisibleTypes().map((t) => t.name);
this.logger.debug(
`Registering OSS features with SO types: ${savedObjectTypes.join(', ')}. "includeTimelion": ${
diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts
index 5b68cd2beeed4..bf6a8de15182d 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts
@@ -32,10 +32,10 @@ export enum KibanaAssetType {
}
export enum ElasticsearchAssetType {
- componentTemplate = 'component-template',
- ingestPipeline = 'ingest-pipeline',
- indexTemplate = 'index-template',
- ilmPolicy = 'ilm-policy',
+ componentTemplate = 'component_template',
+ ingestPipeline = 'ingest_pipeline',
+ indexTemplate = 'index_template',
+ ilmPolicy = 'ilm_policy',
}
export enum AgentAssetType {
@@ -243,13 +243,10 @@ export type AssetReference = Pick & {
* Types of assets which can be installed/removed
*/
export enum IngestAssetType {
- DataFrameTransform = 'data-frame-transform',
- IlmPolicy = 'ilm-policy',
- IndexTemplate = 'index-template',
- ComponentTemplate = 'component-template',
- IngestPipeline = 'ingest-pipeline',
- MlJob = 'ml-job',
- RollupJob = 'rollup-job',
+ IlmPolicy = 'ilm_policy',
+ IndexTemplate = 'index_template',
+ ComponentTemplate = 'component_template',
+ IngestPipeline = 'ingest_pipeline',
}
export enum DefaultPackages {
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx
index 54cb5171f5a3e..31c6d76446447 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx
@@ -17,11 +17,11 @@ export const DisplayedAssets: ServiceNameToAssetTypes = {
export const AssetTitleMap: Record = {
dashboard: 'Dashboard',
- 'ilm-policy': 'ILM Policy',
- 'ingest-pipeline': 'Ingest Pipeline',
+ ilm_policy: 'ILM Policy',
+ ingest_pipeline: 'Ingest Pipeline',
'index-pattern': 'Index Pattern',
- 'index-template': 'Index Template',
- 'component-template': 'Component Template',
+ index_template: 'Index Template',
+ component_template: 'Component Template',
search: 'Saved Search',
visualization: 'Visualization',
input: 'Agent input',
diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json
index 346a5a24c269f..7da5eaed5155e 100644
--- a/x-pack/plugins/lens/kibana.json
+++ b/x-pack/plugins/lens/kibana.json
@@ -10,7 +10,8 @@
"navigation",
"kibanaLegacy",
"visualizations",
- "dashboard"
+ "dashboard",
+ "charts"
],
"optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions"],
"configPath": ["xpack", "lens"],
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx
index f70df855fe0cb..0d60bd588f710 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx
@@ -17,6 +17,7 @@ import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ChangeIndexPattern } from './change_indexpattern';
import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui';
import { documentField } from './document_field';
+import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
const initialState: IndexPatternPrivateState = {
indexPatternRefs: [],
@@ -230,6 +231,7 @@ describe('IndexPattern Data Panel', () => {
fromDate: 'now-7d',
toDate: 'now',
},
+ charts: chartPluginMock.createSetupContract(),
query: { query: '', language: 'lucene' },
filters: [],
showNoDataPopover: jest.fn(),
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx
index 0037609422d77..eb7940634d78e 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx
@@ -47,9 +47,11 @@ export type Props = DatasourceDataPanelProps & {
state: IndexPatternPrivateState,
setState: StateSetter
) => void;
+ charts: ChartsPluginSetup;
};
import { LensFieldIcon } from './lens_field_icon';
import { ChangeIndexPattern } from './change_indexpattern';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
// TODO the typings for EuiContextMenuPanel are incorrect - watchedItemProps is missing. This can be removed when the types are adjusted
const FixedEuiContextMenuPanel = (EuiContextMenuPanel as unknown) as React.FunctionComponent<
@@ -82,6 +84,7 @@ export function IndexPatternDataPanel({
filters,
dateRange,
changeIndexPattern,
+ charts,
showNoDataPopover,
}: Props) {
const { indexPatternRefs, indexPatterns, currentIndexPatternId } = state;
@@ -170,6 +173,7 @@ export function IndexPatternDataPanel({
dragDropContext={dragDropContext}
core={core}
data={data}
+ charts={charts}
onChangeIndexPattern={onChangeIndexPattern}
existingFields={state.existingFields}
/>
@@ -214,6 +218,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
core,
data,
existingFields,
+ charts,
}: Omit & {
data: DataPublicPluginStart;
currentIndexPatternId: string;
@@ -222,6 +227,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
dragDropContext: DragContextState;
onChangeIndexPattern: (newId: string) => void;
existingFields: IndexPatternPrivateState['existingFields'];
+ charts: ChartsPluginSetup;
}) {
const [localState, setLocalState] = useState({
nameFilter: '',
@@ -376,6 +382,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
dateRange,
query,
filters,
+ chartsThemeService: charts.theme,
}),
[core, data, currentIndexPattern, dateRange, query, filters, localState.nameFilter]
);
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx
index e8dfbc250c539..0a3af97f8ad75 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx
@@ -13,6 +13,9 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
import { IndexPattern } from './types';
+import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
+
+const chartsThemeService = chartPluginMock.createSetupContract().theme;
describe('IndexPattern Field Item', () => {
let defaultProps: FieldItemProps;
@@ -80,6 +83,7 @@ describe('IndexPattern Field Item', () => {
searchable: true,
},
exists: true,
+ chartsThemeService,
};
data.fieldFormats = ({
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx
index 1a1a34d30f8a8..815725f4331a6 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx
@@ -20,7 +20,6 @@ import {
EuiText,
EuiToolTip,
} from '@elastic/eui';
-import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
import {
Axis,
BarSeries,
@@ -41,6 +40,7 @@ import {
esQuery,
IIndexPattern,
} from '../../../../../src/plugins/data/public';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import { DraggedField } from './indexpattern';
import { DragDrop } from '../drag_drop';
import { DatasourceDataPanelProps, DataType } from '../types';
@@ -60,6 +60,7 @@ export interface FieldItemProps {
exists: boolean;
query: Query;
dateRange: DatasourceDataPanelProps['dateRange'];
+ chartsThemeService: ChartsPluginSetup['theme'];
filters: Filter[];
hideDetails?: boolean;
}
@@ -254,11 +255,12 @@ function FieldItemPopoverContents(props: State & FieldItemProps) {
dateRange,
core,
sampledValues,
+ chartsThemeService,
data: { fieldFormats },
} = props;
- const IS_DARK_THEME = core.uiSettings.get('theme:darkMode');
- const chartTheme = IS_DARK_THEME ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme;
+ const chartTheme = chartsThemeService.useChartsTheme();
+ const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
let histogramDefault = !!props.histogram;
const totalValuesCount =
@@ -410,6 +412,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) {
-
+
{
let defaultProps: FieldsAccordionProps;
@@ -56,6 +57,7 @@ describe('Fields Accordion', () => {
},
query: { query: '', language: 'lucene' },
filters: [],
+ chartsThemeService: chartPluginMock.createSetupContract().theme,
};
defaultProps = {
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx
index b756cf81a9073..7cc049c107b87 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.tsx
@@ -19,10 +19,12 @@ import { FieldItem } from './field_item';
import { Query, Filter } from '../../../../../src/plugins/data/public';
import { DatasourceDataPanelProps } from '../types';
import { IndexPattern } from './types';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
export interface FieldItemSharedProps {
core: DatasourceDataPanelProps['core'];
data: DataPublicPluginStart;
+ chartsThemeService: ChartsPluginSetup['theme'];
indexPattern: IndexPattern;
highlight?: string;
query: Query;
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts
index 73fd144b9c7f8..45d0ee45fab4c 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts
@@ -9,6 +9,7 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { getIndexPatternDatasource } from './indexpattern';
import { renameColumns } from './rename_columns';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import {
DataPublicPluginSetup,
DataPublicPluginStart,
@@ -19,6 +20,7 @@ export interface IndexPatternDatasourceSetupPlugins {
expressions: ExpressionsSetup;
data: DataPublicPluginSetup;
editorFrame: EditorFrameSetup;
+ charts: ChartsPluginSetup;
}
export interface IndexPatternDatasourceStartPlugins {
@@ -30,7 +32,7 @@ export class IndexPatternDatasource {
setup(
core: CoreSetup,
- { expressions, editorFrame }: IndexPatternDatasourceSetupPlugins
+ { expressions, editorFrame, charts }: IndexPatternDatasourceSetupPlugins
) {
expressions.registerFunction(renameColumns);
@@ -40,6 +42,7 @@ export class IndexPatternDatasource {
core: coreStart,
storage: new Storage(localStorage),
data,
+ charts,
})
) as Promise
);
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts
index 6a79ce450cd9a..3bd0685551a4c 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts
@@ -11,6 +11,7 @@ import { coreMock } from 'src/core/public/mocks';
import { IndexPatternPersistedState, IndexPatternPrivateState } from './types';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
import { Ast } from '@kbn/interpreter/common';
+import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
jest.mock('./loader');
jest.mock('../id_generator');
@@ -140,6 +141,7 @@ describe('IndexPattern Data Source', () => {
storage: {} as IStorageWrapper,
core: coreMock.createStart(),
data: dataPluginMock.createStartContract(),
+ charts: chartPluginMock.createSetupContract(),
});
persistedState = {
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx
index a98f63cf9b360..e9d095bfbcef1 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx
@@ -46,6 +46,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
import { deleteColumn } from './state_helpers';
import { Datasource, StateSetter } from '../index';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
export { OperationType, IndexPatternColumn } from './operations';
@@ -102,10 +103,12 @@ export function getIndexPatternDatasource({
core,
storage,
data,
+ charts,
}: {
core: CoreStart;
storage: IStorageWrapper;
data: DataPublicPluginStart;
+ charts: ChartsPluginSetup;
}) {
const savedObjectsClient = core.savedObjects.client;
const uiSettings = core.uiSettings;
@@ -212,6 +215,7 @@ export function getIndexPatternDatasource({
});
}}
data={data}
+ charts={charts}
{...props}
/>
,
diff --git a/x-pack/plugins/lens/public/pie_visualization/index.ts b/x-pack/plugins/lens/public/pie_visualization/index.ts
index dd828c6c35300..401b6d634c696 100644
--- a/x-pack/plugins/lens/public/pie_visualization/index.ts
+++ b/x-pack/plugins/lens/public/pie_visualization/index.ts
@@ -4,18 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
import { CoreSetup } from 'src/core/public';
import { ExpressionsSetup } from 'src/plugins/expressions/public';
import { pieVisualization } from './pie_visualization';
import { pie, getPieRenderer } from './register_expression';
import { EditorFrameSetup, FormatFactory } from '../types';
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
export interface PieVisualizationPluginSetupPlugins {
editorFrame: EditorFrameSetup;
expressions: ExpressionsSetup;
formatFactory: Promise;
+ charts: ChartsPluginSetup;
}
export interface PieVisualizationPluginStartPlugins {
@@ -27,17 +28,14 @@ export class PieVisualization {
setup(
core: CoreSetup,
- { expressions, formatFactory, editorFrame }: PieVisualizationPluginSetupPlugins
+ { expressions, formatFactory, editorFrame, charts }: PieVisualizationPluginSetupPlugins
) {
expressions.registerFunction(() => pie);
expressions.registerRenderer(
getPieRenderer({
formatFactory,
- chartTheme: core.uiSettings.get('theme:darkMode')
- ? EUI_CHARTS_THEME_DARK.theme
- : EUI_CHARTS_THEME_LIGHT.theme,
- isDarkMode: core.uiSettings.get('theme:darkMode'),
+ chartsThemeService: charts.theme,
})
);
diff --git a/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx b/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx
index bbc6a1dc75c3a..cea84db8b2794 100644
--- a/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx
@@ -8,7 +8,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
-import { PartialTheme } from '@elastic/charts';
import {
IInterpreterRenderHandlers,
ExpressionRenderDefinition,
@@ -17,6 +16,7 @@ import {
import { LensMultiTable, FormatFactory, LensFilterEvent } from '../types';
import { PieExpressionProps, PieExpressionArgs } from './types';
import { PieComponent } from './render_function';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
export interface PieRender {
type: 'render';
@@ -93,8 +93,7 @@ export const pie: ExpressionFunctionDefinition<
export const getPieRenderer = (dependencies: {
formatFactory: Promise;
- chartTheme: PartialTheme;
- isDarkMode: boolean;
+ chartsThemeService: ChartsPluginSetup['theme'];
}): ExpressionRenderDefinition => ({
name: 'lens_pie_renderer',
displayName: i18n.translate('xpack.lens.pie.visualizationName', {
@@ -116,10 +115,9 @@ export const getPieRenderer = (dependencies: {
,
domNode,
diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx
index 2e29513ba548b..cfbeb27efb3d0 100644
--- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx
@@ -11,6 +11,9 @@ import { LensMultiTable } from '../types';
import { PieComponent } from './render_function';
import { PieExpressionArgs } from './types';
import { EmptyPlaceholder } from '../shared_components';
+import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
+
+const chartsThemeService = chartPluginMock.createSetupContract().theme;
describe('PieVisualization component', () => {
let getFormatSpy: jest.Mock;
@@ -57,9 +60,8 @@ describe('PieVisualization component', () => {
return {
data,
formatFactory: getFormatSpy,
- isDarkMode: false,
- chartTheme: {},
onClickValue: jest.fn(),
+ chartsThemeService,
};
}
diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
index 36e8d9660ab70..f349cc4dfd648 100644
--- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx
@@ -19,7 +19,6 @@ import {
PartitionConfig,
PartitionLayer,
PartitionLayout,
- PartialTheme,
PartitionFillLabel,
RecursivePartial,
LayerValue,
@@ -32,6 +31,7 @@ import { getSliceValueWithFallback, getFilterContext } from './render_helpers';
import { EmptyPlaceholder } from '../shared_components';
import './visualization.scss';
import { desanitizeFilterContext } from '../utils';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
const EMPTY_SLICE = Symbol('empty_slice');
@@ -40,15 +40,14 @@ const sortedColors = euiPaletteColorBlindBehindText();
export function PieComponent(
props: PieExpressionProps & {
formatFactory: FormatFactory;
- chartTheme: Exclude;
- isDarkMode: boolean;
+ chartsThemeService: ChartsPluginSetup['theme'];
onClickValue: (data: LensFilterEvent['data']) => void;
}
) {
const [firstTable] = Object.values(props.data.tables);
const formatters: Record> = {};
- const { chartTheme, isDarkMode, onClickValue } = props;
+ const { chartsThemeService, onClickValue } = props;
const {
shape,
groups,
@@ -60,6 +59,9 @@ export function PieComponent(
percentDecimals,
hideLabels,
} = props.args;
+ const isDarkMode = chartsThemeService.useDarkMode();
+ const chartTheme = chartsThemeService.useChartsTheme();
+ const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
if (!hideLabels) {
firstTable.columns.forEach((column) => {
@@ -245,6 +247,8 @@ export function PieComponent(
onClickValue(desanitizeFilterContext(context));
}}
+ theme={chartTheme}
+ baseTheme={chartBaseTheme}
/>
,
- { kibanaLegacy, expressions, data, embeddable, visualizations }: LensPluginSetupDependencies
+ {
+ kibanaLegacy,
+ expressions,
+ data,
+ embeddable,
+ visualizations,
+ charts,
+ }: LensPluginSetupDependencies
) {
const editorFrameSetupInterface = this.editorFrameService.setup(core, {
data,
embeddable,
expressions,
});
- const dependencies = {
+ const dependencies: IndexPatternDatasourceSetupPlugins &
+ XyVisualizationPluginSetupPlugins &
+ DatatableVisualizationPluginSetupPlugins &
+ MetricVisualizationPluginSetupPlugins &
+ PieVisualizationPluginSetupPlugins = {
expressions,
data,
+ charts,
editorFrame: editorFrameSetupInterface,
formatFactory: core
.getStartServices()
diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap
index 48c70e0a4a05b..8cb30037379da 100644
--- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap
+++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap
@@ -5,6 +5,7 @@ exports[`xy_expression XYChart component it renders area 1`] = `
renderer="canvas"
>
;
editorFrame: EditorFrameSetup;
+ charts: ChartsPluginSetup;
}
function getTimeZone(uiSettings: IUiSettingsClient) {
@@ -34,7 +35,7 @@ export class XyVisualization {
setup(
core: CoreSetup,
- { expressions, formatFactory, editorFrame }: XyVisualizationPluginSetupPlugins
+ { expressions, formatFactory, editorFrame, charts }: XyVisualizationPluginSetupPlugins
) {
expressions.registerFunction(() => legendConfig);
expressions.registerFunction(() => yAxisConfig);
@@ -44,9 +45,7 @@ export class XyVisualization {
expressions.registerRenderer(
getXyChartRenderer({
formatFactory,
- chartTheme: core.uiSettings.get('theme:darkMode')
- ? EUI_CHARTS_THEME_DARK.theme
- : EUI_CHARTS_THEME_LIGHT.theme,
+ chartsThemeService: charts.theme,
timeZone: getTimeZone(core.uiSettings),
histogramBarTarget: core.uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
})
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx
index 34f2a9111253b..f433a88e3bdbd 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx
@@ -24,10 +24,13 @@ import { shallow } from 'enzyme';
import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types';
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
const onClickValue = jest.fn();
const onSelectRange = jest.fn();
+const chartsThemeService = chartPluginMock.createSetupContract().theme;
+
const dateHistogramData: LensMultiTable = {
type: 'lens_multitable',
tables: {
@@ -324,7 +327,7 @@ describe('xy_expression', () => {
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -347,7 +350,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'line' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -398,7 +401,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -434,7 +437,7 @@ describe('xy_expression', () => {
args={multiLayerArgs}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -471,7 +474,7 @@ describe('xy_expression', () => {
args={multiLayerArgs}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -509,7 +512,7 @@ describe('xy_expression', () => {
args={multiLayerArgs}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -554,7 +557,7 @@ describe('xy_expression', () => {
args={multiLayerArgs}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -589,7 +592,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -606,7 +609,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -626,7 +629,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -646,7 +649,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_horizontal' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -671,7 +674,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -721,7 +724,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -758,7 +761,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -778,7 +781,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area_stacked' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -801,7 +804,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -822,7 +825,7 @@ describe('xy_expression', () => {
args={args}
formatFactory={getFormatSpy}
timeZone="CEST"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -842,7 +845,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [firstLayer] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -869,7 +872,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -890,7 +893,7 @@ describe('xy_expression', () => {
}}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1196,7 +1199,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], xScaleType: 'ordinal' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1215,7 +1218,7 @@ describe('xy_expression', () => {
args={{ ...args, layers: [{ ...args.layers[0], yScaleType: 'sqrt' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1234,7 +1237,7 @@ describe('xy_expression', () => {
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1252,7 +1255,7 @@ describe('xy_expression', () => {
data={{ ...data }}
args={{ ...args, layers: [{ ...args.layers[0], accessors: ['a'] }] }}
formatFactory={getFormatSpy}
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
timeZone="UTC"
onClickValue={onClickValue}
@@ -1274,7 +1277,7 @@ describe('xy_expression', () => {
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1359,7 +1362,7 @@ describe('xy_expression', () => {
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1417,7 +1420,7 @@ describe('xy_expression', () => {
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
@@ -1473,7 +1476,7 @@ describe('xy_expression', () => {
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
- chartTheme={{}}
+ chartsThemeService={chartsThemeService}
histogramBarTarget={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx
index 17ed04aa0e9c4..3ff7bd7fda304 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx
@@ -15,7 +15,6 @@ import {
AreaSeries,
BarSeries,
Position,
- PartialTheme,
GeometryValue,
XYChartSeriesIdentifier,
} from '@elastic/charts';
@@ -38,6 +37,7 @@ import { XYArgs, SeriesType, visualizationTypes } from './types';
import { VisualizationContainer } from '../visualization_container';
import { isHorizontalChart } from './state_helpers';
import { parseInterval } from '../../../../../src/plugins/data/common';
+import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import { EmptyPlaceholder } from '../shared_components';
import { desanitizeFilterContext } from '../utils';
import { getAxesConfiguration } from './axes_configuration';
@@ -59,7 +59,7 @@ export interface XYRender {
}
type XYChartRenderProps = XYChartProps & {
- chartTheme: PartialTheme;
+ chartsThemeService: ChartsPluginSetup['theme'];
formatFactory: FormatFactory;
timeZone: string;
histogramBarTarget: number;
@@ -115,7 +115,7 @@ export const xyChart: ExpressionFunctionDefinition<
export const getXyChartRenderer = (dependencies: {
formatFactory: Promise;
- chartTheme: PartialTheme;
+ chartsThemeService: ChartsPluginSetup['theme'];
histogramBarTarget: number;
timeZone: string;
}): ExpressionRenderDefinition => ({
@@ -144,7 +144,7 @@ export const getXyChartRenderer = (dependencies: {
{
return !(
@@ -276,6 +278,7 @@ export function XYChart({
legendPosition={legend.position}
showLegendExtra={false}
theme={chartTheme}
+ baseTheme={chartBaseTheme}
tooltip={{
headerFormatter: (d) => xAxisFormatter.convert(d.value),
}}
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index 25f10c7794fdd..98464427cc348 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -223,6 +223,11 @@ export enum SCALING_TYPES {
export const RGBA_0000 = 'rgba(0,0,0,0)';
+export enum MVT_FIELD_TYPE {
+ STRING = 'String',
+ NUMBER = 'Number',
+}
+
export const SPATIAL_FILTERS_LAYER_ID = 'SPATIAL_FILTERS_LAYER_ID';
export enum INITIAL_LOCATION {
diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts
similarity index 100%
rename from x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts
rename to x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts
diff --git a/x-pack/plugins/maps/common/descriptor_types/index.ts b/x-pack/plugins/maps/common/descriptor_types/index.ts
index af0f4487f471b..b0ae065856a5d 100644
--- a/x-pack/plugins/maps/common/descriptor_types/index.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/index.ts
@@ -5,6 +5,6 @@
*/
export * from './data_request_descriptor_types';
-export * from './descriptor_types';
+export * from './sources';
export * from './map_descriptor';
export * from './style_property_descriptor_types';
diff --git a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
index 00380ca12a486..027cc886cd7f7 100644
--- a/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/map_descriptor.ts
@@ -5,6 +5,7 @@
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
+import { GeoJsonProperties } from 'geojson';
import { Query } from '../../../../../src/plugins/data/common';
import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
@@ -39,8 +40,9 @@ export type Goto = {
};
export type TooltipFeature = {
- id: number;
+ id?: number | string;
layerId: string;
+ mbProperties: GeoJsonProperties;
};
export type TooltipState = {
diff --git a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/sources.ts
similarity index 87%
rename from x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts
rename to x-pack/plugins/maps/common/descriptor_types/sources.ts
index c7a706ea64f74..86ace0e32cc84 100644
--- a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/sources.ts
@@ -7,7 +7,14 @@
import { FeatureCollection } from 'geojson';
import { Query } from 'src/plugins/data/public';
-import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants';
+import {
+ AGG_TYPE,
+ GRID_RESOLUTION,
+ RENDER_AS,
+ SORT_ORDER,
+ SCALING_TYPES,
+ MVT_FIELD_TYPE,
+} from '../constants';
import { StyleDescriptor, VectorStyleDescriptor } from './style_property_descriptor_types';
import { DataRequestDescriptor } from './data_request_descriptor_types';
@@ -96,18 +103,34 @@ export type XYZTMSSourceDescriptor = AbstractSourceDescriptor &
urlTemplate: string;
};
-export type TiledSingleLayerVectorSourceDescriptor = AbstractSourceDescriptor & {
+export type MVTFieldDescriptor = {
+ name: string;
+ type: MVT_FIELD_TYPE;
+};
+
+export type TiledSingleLayerVectorSourceSettings = {
urlTemplate: string;
layerName: string;
// These are the min/max zoom levels of the availability of the a particular layerName in the tileset at urlTemplate.
// These are _not_ the visible zoom-range of the data on a map.
- // Tiled data can be displayed at higher levels of zoom than that they are stored in the tileset.
- // e.g. EMS basemap data from level 14 is at most detailed resolution and can be displayed at higher levels
+ // These are important so mapbox does not issue invalid requests based on the zoom level.
+
+ // Tiled layer data cannot be displayed at lower levels of zoom than that they are stored in the tileset.
+ // e.g. building footprints at level 14 cannot be displayed at level 0.
minSourceZoom: number;
+ // Tiled layer data can be displayed at higher levels of zoom than that they are stored in the tileset.
+ // e.g. EMS basemap data from level 14 is at most detailed resolution and can be displayed at higher levels
maxSourceZoom: number;
+
+ fields: MVTFieldDescriptor[];
};
+export type TiledSingleLayerVectorSourceDescriptor = AbstractSourceDescriptor &
+ TiledSingleLayerVectorSourceSettings & {
+ tooltipProperties: string[];
+ };
+
export type GeojsonFileSourceDescriptor = {
__featureCollection: FeatureCollection;
name: string;
diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts
similarity index 100%
rename from x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts
rename to x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts
diff --git a/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts
index 60d437d2321b5..e0f5c79f1d427 100644
--- a/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts
+++ b/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts
@@ -128,6 +128,10 @@ export class ESAggField implements IESAggField {
async getCategoricalFieldMetaRequest(size: number): Promise {
return this._esDocField ? this._esDocField.getCategoricalFieldMetaRequest(size) : null;
}
+
+ supportsAutoDomain(): boolean {
+ return true;
+ }
}
export function esAggFieldsFactory(
diff --git a/x-pack/plugins/maps/public/classes/fields/field.ts b/x-pack/plugins/maps/public/classes/fields/field.ts
index dfd5dc05f7b83..410b38e79ffe4 100644
--- a/x-pack/plugins/maps/public/classes/fields/field.ts
+++ b/x-pack/plugins/maps/public/classes/fields/field.ts
@@ -20,6 +20,12 @@ export interface IField {
isValid(): boolean;
getOrdinalFieldMetaRequest(): Promise;
getCategoricalFieldMetaRequest(size: number): Promise;
+
+ // Determines whether Maps-app can automatically determine the domain of the field-values
+ // if this is not the case (e.g. for .mvt tiled data),
+ // then styling properties that require the domain to be known cannot use this property.
+ supportsAutoDomain(): boolean;
+
supportsFieldMeta(): boolean;
}
@@ -80,4 +86,8 @@ export class AbstractField implements IField {
async getCategoricalFieldMetaRequest(size: number): Promise {
return null;
}
+
+ supportsAutoDomain(): boolean {
+ return true;
+ }
}
diff --git a/x-pack/plugins/maps/public/classes/fields/mvt_field.ts b/x-pack/plugins/maps/public/classes/fields/mvt_field.ts
new file mode 100644
index 0000000000000..eb2bb94b36a69
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/fields/mvt_field.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { AbstractField, IField } from './field';
+import { FIELD_ORIGIN, MVT_FIELD_TYPE } from '../../../common/constants';
+import { ITiledSingleLayerVectorSource, IVectorSource } from '../sources/vector_source';
+import { MVTFieldDescriptor } from '../../../common/descriptor_types';
+
+export class MVTField extends AbstractField implements IField {
+ private readonly _source: ITiledSingleLayerVectorSource;
+ private readonly _type: MVT_FIELD_TYPE;
+ constructor({
+ fieldName,
+ type,
+ source,
+ origin,
+ }: {
+ fieldName: string;
+ source: ITiledSingleLayerVectorSource;
+ origin: FIELD_ORIGIN;
+ type: MVT_FIELD_TYPE;
+ }) {
+ super({ fieldName, origin });
+ this._source = source;
+ this._type = type;
+ }
+
+ getMVTFieldDescriptor(): MVTFieldDescriptor {
+ return {
+ type: this._type,
+ name: this.getName(),
+ };
+ }
+
+ getSource(): IVectorSource {
+ return this._source;
+ }
+
+ async getDataType(): Promise {
+ if (this._type === MVT_FIELD_TYPE.STRING) {
+ return 'string';
+ } else if (this._type === MVT_FIELD_TYPE.NUMBER) {
+ return 'number';
+ } else {
+ throw new Error(`Unrecognized MVT field-type ${this._type}`);
+ }
+ }
+
+ async getLabel(): Promise {
+ return this.getName();
+ }
+
+ supportsAutoDomain() {
+ return false;
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts b/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts
index 6c504daf3e192..f4625e42ab5de 100644
--- a/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts
+++ b/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts
@@ -60,6 +60,10 @@ export class TopTermPercentageField implements IESAggField {
return 0;
}
+ supportsAutoDomain(): boolean {
+ return true;
+ }
+
supportsFieldMeta(): boolean {
return false;
}
diff --git a/x-pack/plugins/maps/public/classes/layers/__tests__/mock_sync_context.ts b/x-pack/plugins/maps/public/classes/layers/__tests__/mock_sync_context.ts
new file mode 100644
index 0000000000000..8c4eb49d5040d
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/layers/__tests__/mock_sync_context.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 sinon from 'sinon';
+import { DataRequestContext } from '../../../actions';
+import { DataMeta, MapFilters } from '../../../../common/descriptor_types';
+
+export class MockSyncContext implements DataRequestContext {
+ dataFilters: MapFilters;
+ isRequestStillActive: (dataId: string, requestToken: symbol) => boolean;
+ onLoadError: (dataId: string, requestToken: symbol, errorMessage: string) => void;
+ registerCancelCallback: (requestToken: symbol, callback: () => void) => void;
+ startLoading: (dataId: string, requestToken: symbol, meta: DataMeta) => void;
+ stopLoading: (dataId: string, requestToken: symbol, data: object, meta: DataMeta) => void;
+ updateSourceData: (newData: unknown) => void;
+
+ constructor({ dataFilters }: { dataFilters: Partial }) {
+ const mapFilters: MapFilters = {
+ filters: [],
+ timeFilters: {
+ from: 'now',
+ to: '15m',
+ mode: 'relative',
+ },
+ zoom: 0,
+ ...dataFilters,
+ };
+
+ this.dataFilters = mapFilters;
+ this.isRequestStillActive = sinon.spy();
+ this.onLoadError = sinon.spy();
+ this.registerCancelCallback = sinon.spy();
+ this.startLoading = sinon.spy();
+ this.stopLoading = sinon.spy();
+ this.updateSourceData = sinon.spy();
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx
index 859d6092dc64d..368dcda6b3a5f 100644
--- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx
@@ -16,7 +16,7 @@ import {
import { getFileUploadComponent } from '../../../kibana_services';
import { GeojsonFileSource } from '../../sources/geojson_file_source';
import { VectorLayer } from '../../layers/vector_layer/vector_layer';
-// @ts-ignore
+// @ts-expect-error
import { createDefaultLayerDescriptor } from '../../sources/es_search_source';
import { RenderWizardArguments } from '../../layers/layer_wizard_registry';
diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js
index f6b9bd6280290..adcc86b9d1546 100644
--- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js
+++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.js
@@ -91,7 +91,7 @@ export class HeatmapLayer extends VectorLayer {
resolution: this.getSource().getGridResolution(),
});
mbMap.setPaintProperty(heatmapLayerId, 'heatmap-opacity', this.getAlpha());
- mbMap.setLayerZoomRange(heatmapLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ mbMap.setLayerZoomRange(heatmapLayerId, this.getMinZoom(), this.getMaxZoom());
}
getLayerTypeIconName() {
diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx
index 2250d5663378c..e122d1cda3ed9 100644
--- a/x-pack/plugins/maps/public/classes/layers/layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx
@@ -325,27 +325,28 @@ export class AbstractLayer implements ILayer {
return this._source.getMinZoom();
}
+ _getMbSourceId() {
+ return this.getId();
+ }
+
_requiresPrevSourceCleanup(mbMap: unknown) {
return false;
}
_removeStaleMbSourcesAndLayers(mbMap: unknown) {
if (this._requiresPrevSourceCleanup(mbMap)) {
- // @ts-ignore
+ // @ts-expect-error
const mbStyle = mbMap.getStyle();
- // @ts-ignore
+ // @ts-expect-error
mbStyle.layers.forEach((mbLayer) => {
- // @ts-ignore
if (this.ownsMbLayerId(mbLayer.id)) {
- // @ts-ignore
+ // @ts-expect-error
mbMap.removeLayer(mbLayer.id);
}
});
- // @ts-ignore
Object.keys(mbStyle.sources).some((mbSourceId) => {
- // @ts-ignore
if (this.ownsMbSourceId(mbSourceId)) {
- // @ts-ignore
+ // @ts-expect-error
mbMap.removeSource(mbSourceId);
}
});
@@ -429,7 +430,7 @@ export class AbstractLayer implements ILayer {
throw new Error('Should implement AbstractLayer#ownsMbLayerId');
}
- ownsMbSourceId(sourceId: string): boolean {
+ ownsMbSourceId(mbSourceId: string): boolean {
throw new Error('Should implement AbstractLayer#ownsMbSourceId');
}
diff --git a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js
index 02df8acbfffad..3e2009c24a2e4 100644
--- a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js
+++ b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js
@@ -74,8 +74,8 @@ export class TileLayer extends AbstractLayer {
return;
}
- const sourceId = this.getId();
- mbMap.addSource(sourceId, {
+ const mbSourceId = this._getMbSourceId();
+ mbMap.addSource(mbSourceId, {
type: 'raster',
tiles: [tmsSourceData.url],
tileSize: 256,
@@ -85,7 +85,7 @@ export class TileLayer extends AbstractLayer {
mbMap.addLayer({
id: mbLayerId,
type: 'raster',
- source: sourceId,
+ source: mbSourceId,
minzoom: this._descriptor.minZoom,
maxzoom: this._descriptor.maxZoom,
});
diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/__snapshots__/tiled_vector_layer.test.tsx.snap b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/__snapshots__/tiled_vector_layer.test.tsx.snap
new file mode 100644
index 0000000000000..f0ae93601ce8a
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/__snapshots__/tiled_vector_layer.test.tsx.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`icon should use vector icon 1`] = `
+
+`;
diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx
new file mode 100644
index 0000000000000..ecd625db34411
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.test.tsx
@@ -0,0 +1,163 @@
+/*
+ * 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 { MockSyncContext } from '../__tests__/mock_sync_context';
+import sinon from 'sinon';
+
+jest.mock('../../../kibana_services', () => {
+ return {
+ getUiSettings() {
+ return {
+ get() {
+ return false;
+ },
+ };
+ },
+ };
+});
+
+import { shallow } from 'enzyme';
+
+import { Feature } from 'geojson';
+import { MVTSingleLayerVectorSource } from '../../sources/mvt_single_layer_vector_source';
+import {
+ DataRequestDescriptor,
+ TiledSingleLayerVectorSourceDescriptor,
+ VectorLayerDescriptor,
+} from '../../../../common/descriptor_types';
+import { SOURCE_TYPES } from '../../../../common/constants';
+import { TiledVectorLayer } from './tiled_vector_layer';
+
+const defaultConfig = {
+ urlTemplate: 'https://example.com/{x}/{y}/{z}.pbf',
+ layerName: 'foobar',
+ minSourceZoom: 4,
+ maxSourceZoom: 14,
+};
+
+function createLayer(
+ layerOptions: Partial = {},
+ sourceOptions: Partial = {}
+): TiledVectorLayer {
+ const sourceDescriptor: TiledSingleLayerVectorSourceDescriptor = {
+ type: SOURCE_TYPES.MVT_SINGLE_LAYER,
+ ...defaultConfig,
+ fields: [],
+ tooltipProperties: [],
+ ...sourceOptions,
+ };
+ const mvtSource = new MVTSingleLayerVectorSource(sourceDescriptor);
+
+ const defaultLayerOptions = {
+ ...layerOptions,
+ sourceDescriptor,
+ };
+ const layerDescriptor = TiledVectorLayer.createDescriptor(defaultLayerOptions);
+ return new TiledVectorLayer({ layerDescriptor, source: mvtSource });
+}
+
+describe('visiblity', () => {
+ it('should get minzoom from source', async () => {
+ const layer: TiledVectorLayer = createLayer({}, {});
+ expect(layer.getMinZoom()).toEqual(4);
+ });
+ it('should get maxzoom from default', async () => {
+ const layer: TiledVectorLayer = createLayer({}, {});
+ expect(layer.getMaxZoom()).toEqual(24);
+ });
+ it('should get maxzoom from layer options', async () => {
+ const layer: TiledVectorLayer = createLayer({ maxZoom: 10 }, {});
+ expect(layer.getMaxZoom()).toEqual(10);
+ });
+});
+
+describe('icon', () => {
+ it('should use vector icon', async () => {
+ const layer: TiledVectorLayer = createLayer({}, {});
+
+ const iconAndTooltipContent = layer.getCustomIconAndTooltipContent();
+ const component = shallow(iconAndTooltipContent.icon);
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('getFeatureById', () => {
+ it('should return null feature', async () => {
+ const layer: TiledVectorLayer = createLayer({}, {});
+ const feature = layer.getFeatureById('foobar') as Feature;
+ expect(feature).toEqual(null);
+ });
+});
+
+describe('syncData', () => {
+ it('Should sync with source-params', async () => {
+ const layer: TiledVectorLayer = createLayer({}, {});
+
+ const syncContext = new MockSyncContext({ dataFilters: {} });
+
+ await layer.syncData(syncContext);
+ // @ts-expect-error
+ sinon.assert.calledOnce(syncContext.startLoading);
+ // @ts-expect-error
+ sinon.assert.calledOnce(syncContext.stopLoading);
+
+ // @ts-expect-error
+ const call = syncContext.stopLoading.getCall(0);
+ expect(call.args[2]).toEqual(defaultConfig);
+ });
+
+ it('Should not resync when no changes to source params', async () => {
+ const layer1: TiledVectorLayer = createLayer({}, {});
+ const syncContext1 = new MockSyncContext({ dataFilters: {} });
+
+ await layer1.syncData(syncContext1);
+
+ const dataRequestDescriptor: DataRequestDescriptor = {
+ data: { ...defaultConfig },
+ dataId: 'source',
+ };
+ const layer2: TiledVectorLayer = createLayer(
+ {
+ __dataRequests: [dataRequestDescriptor],
+ },
+ {}
+ );
+ const syncContext2 = new MockSyncContext({ dataFilters: {} });
+ await layer2.syncData(syncContext2);
+ // @ts-expect-error
+ sinon.assert.notCalled(syncContext2.startLoading);
+ // @ts-expect-error
+ sinon.assert.notCalled(syncContext2.stopLoading);
+ });
+
+ it('Should resync when changes to source params', async () => {
+ const layer1: TiledVectorLayer = createLayer({}, {});
+ const syncContext1 = new MockSyncContext({ dataFilters: {} });
+
+ await layer1.syncData(syncContext1);
+
+ const dataRequestDescriptor: DataRequestDescriptor = {
+ data: defaultConfig,
+ dataId: 'source',
+ };
+ const layer2: TiledVectorLayer = createLayer(
+ {
+ __dataRequests: [dataRequestDescriptor],
+ },
+ { layerName: 'barfoo' }
+ );
+ const syncContext2 = new MockSyncContext({ dataFilters: {} });
+ await layer2.syncData(syncContext2);
+
+ // @ts-expect-error
+ sinon.assert.calledOnce(syncContext2.startLoading);
+ // @ts-expect-error
+ sinon.assert.calledOnce(syncContext2.stopLoading);
+
+ // @ts-expect-error
+ const call = syncContext2.stopLoading.getCall(0);
+ expect(call.args[2]).toEqual({ ...defaultConfig, layerName: 'barfoo' });
+ });
+});
diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx
index a00639aa5fec5..c9ae1c805fa30 100644
--- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx
@@ -6,31 +6,30 @@
import React from 'react';
import { EuiIcon } from '@elastic/eui';
+import { Feature } from 'geojson';
import { VectorStyle } from '../../styles/vector/vector_style';
import { SOURCE_DATA_REQUEST_ID, LAYER_TYPE } from '../../../../common/constants';
import { VectorLayer, VectorLayerArguments } from '../vector_layer/vector_layer';
-import { canSkipSourceUpdate } from '../../util/can_skip_fetch';
import { ITiledSingleLayerVectorSource } from '../../sources/vector_source';
import { DataRequestContext } from '../../../actions';
-import { ISource } from '../../sources/source';
import {
VectorLayerDescriptor,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
-import { MVTSingleLayerVectorSourceConfig } from '../../sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor';
+import { MVTSingleLayerVectorSourceConfig } from '../../sources/mvt_single_layer_vector_source/types';
export class TiledVectorLayer extends VectorLayer {
static type = LAYER_TYPE.TILED_VECTOR;
static createDescriptor(
descriptor: Partial,
- mapColors: string[]
+ mapColors?: string[]
): VectorLayerDescriptor {
const layerDescriptor = super.createDescriptor(descriptor, mapColors);
layerDescriptor.type = TiledVectorLayer.type;
if (!layerDescriptor.style) {
- const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors);
+ const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors ? mapColors : []);
layerDescriptor.style = VectorStyle.createDescriptor(styleProperties);
}
@@ -64,13 +63,16 @@ export class TiledVectorLayer extends VectorLayer {
);
const prevDataRequest = this.getSourceDataRequest();
- const canSkip = await canSkipSourceUpdate({
- source: this._source as ISource,
- prevDataRequest,
- nextMeta: searchFilters,
- });
- if (canSkip) {
- return null;
+ if (prevDataRequest) {
+ const data: MVTSingleLayerVectorSourceConfig = prevDataRequest.getData() as MVTSingleLayerVectorSourceConfig;
+ const canSkipBecauseNoChanges =
+ data.layerName === this._source.getLayerName() &&
+ data.minSourceZoom === this._source.getMinZoom() &&
+ data.maxSourceZoom === this._source.getMaxZoom();
+
+ if (canSkipBecauseNoChanges) {
+ return null;
+ }
}
startLoading(SOURCE_DATA_REQUEST_ID, requestToken, searchFilters);
@@ -89,37 +91,41 @@ export class TiledVectorLayer extends VectorLayer {
}
_syncSourceBindingWithMb(mbMap: unknown) {
- // @ts-ignore
- const mbSource = mbMap.getSource(this.getId());
- if (!mbSource) {
- const sourceDataRequest = this.getSourceDataRequest();
- if (!sourceDataRequest) {
- // this is possible if the layer was invisible at startup.
- // the actions will not perform any data=syncing as an optimization when a layer is invisible
- // when turning the layer back into visible, it's possible the url has not been resovled yet.
- return;
- }
+ // @ts-expect-error
+ const mbSource = mbMap.getSource(this._getMbSourceId());
+ if (mbSource) {
+ return;
+ }
+ const sourceDataRequest = this.getSourceDataRequest();
+ if (!sourceDataRequest) {
+ // this is possible if the layer was invisible at startup.
+ // the actions will not perform any data=syncing as an optimization when a layer is invisible
+ // when turning the layer back into visible, it's possible the url has not been resovled yet.
+ return;
+ }
- const sourceMeta: MVTSingleLayerVectorSourceConfig | null = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig;
- if (!sourceMeta) {
- return;
- }
+ const sourceMeta: MVTSingleLayerVectorSourceConfig | null = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig;
+ if (!sourceMeta) {
+ return;
+ }
- const sourceId = this.getId();
+ const mbSourceId = this._getMbSourceId();
+ // @ts-expect-error
+ mbMap.addSource(mbSourceId, {
+ type: 'vector',
+ tiles: [sourceMeta.urlTemplate],
+ minzoom: sourceMeta.minSourceZoom,
+ maxzoom: sourceMeta.maxSourceZoom,
+ });
+ }
- // @ts-ignore
- mbMap.addSource(sourceId, {
- type: 'vector',
- tiles: [sourceMeta.urlTemplate],
- minzoom: sourceMeta.minSourceZoom,
- maxzoom: sourceMeta.maxSourceZoom,
- });
- }
+ ownsMbSourceId(mbSourceId: string): boolean {
+ return this._getMbSourceId() === mbSourceId;
}
_syncStylePropertiesWithMb(mbMap: unknown) {
// @ts-ignore
- const mbSource = mbMap.getSource(this.getId());
+ const mbSource = mbMap.getSource(this._getMbSourceId());
if (!mbSource) {
return;
}
@@ -129,32 +135,52 @@ export class TiledVectorLayer extends VectorLayer {
return;
}
const sourceMeta: MVTSingleLayerVectorSourceConfig = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig;
+ if (sourceMeta.layerName === '') {
+ return;
+ }
this._setMbPointsProperties(mbMap, sourceMeta.layerName);
this._setMbLinePolygonProperties(mbMap, sourceMeta.layerName);
}
_requiresPrevSourceCleanup(mbMap: unknown): boolean {
- // @ts-ignore
- const mbTileSource = mbMap.getSource(this.getId());
+ // @ts-expect-error
+ const mbTileSource = mbMap.getSource(this._getMbSourceId());
if (!mbTileSource) {
return false;
}
+
const dataRequest = this.getSourceDataRequest();
if (!dataRequest) {
return false;
}
const tiledSourceMeta: MVTSingleLayerVectorSourceConfig | null = dataRequest.getData() as MVTSingleLayerVectorSourceConfig;
- if (
- mbTileSource.tiles[0] === tiledSourceMeta.urlTemplate &&
- mbTileSource.minzoom === tiledSourceMeta.minSourceZoom &&
- mbTileSource.maxzoom === tiledSourceMeta.maxSourceZoom
- ) {
- // TileURL and zoom-range captures all the state. If this does not change, no updates are required.
+
+ if (!tiledSourceMeta) {
return false;
}
- return true;
+ const isSourceDifferent =
+ mbTileSource.tiles[0] !== tiledSourceMeta.urlTemplate ||
+ mbTileSource.minzoom !== tiledSourceMeta.minSourceZoom ||
+ mbTileSource.maxzoom !== tiledSourceMeta.maxSourceZoom;
+
+ if (isSourceDifferent) {
+ return true;
+ }
+
+ const layerIds = this.getMbLayerIds();
+ for (let i = 0; i < layerIds.length; i++) {
+ // @ts-expect-error
+ const mbLayer = mbMap.getLayer(layerIds[i]);
+ if (mbLayer && mbLayer.sourceLayer !== tiledSourceMeta.layerName) {
+ // If the source-pointer of one of the layers is stale, they will all be stale.
+ // In this case, all the mb-layers need to be removed and re-added.
+ return true;
+ }
+ }
+
+ return false;
}
syncLayerWithMB(mbMap: unknown) {
@@ -171,4 +197,8 @@ export class TiledVectorLayer extends VectorLayer {
// higher resolution vector tiles cannot be displayed at lower-res
return Math.max(this._source.getMinZoom(), super.getMinZoom());
}
+
+ getFeatureById(id: string | number): Feature | null {
+ return null;
+ }
}
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts
index e420087628bc8..77daf9c9af570 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts
@@ -5,6 +5,7 @@
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
+import { Feature, GeoJsonProperties } from 'geojson';
import { AbstractLayer } from '../layer';
import { IVectorSource } from '../../sources/vector_source';
import {
@@ -17,6 +18,7 @@ import { IJoin } from '../../joins/join';
import { IVectorStyle } from '../../styles/vector/vector_style';
import { IField } from '../../fields/field';
import { DataRequestContext } from '../../../actions';
+import { ITooltipProperty } from '../../tooltips/tooltip_property';
export type VectorLayerArguments = {
source: IVectorSource;
@@ -31,6 +33,8 @@ export interface IVectorLayer extends ILayer {
getValidJoins(): IJoin[];
getSource(): IVectorSource;
getStyle(): IVectorStyle;
+ getFeatureById(id: string | number): Feature | null;
+ getPropertiesForTooltip(properties: GeoJsonProperties): Promise;
}
export class VectorLayer extends AbstractLayer implements IVectorLayer {
@@ -75,4 +79,6 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer {
_setMbLinePolygonProperties(mbMap: unknown, mvtSourceLayer?: string): void;
getSource(): IVectorSource;
getStyle(): IVectorStyle;
+ getFeatureById(id: string | number): Feature | null;
+ getPropertiesForTooltip(properties: GeoJsonProperties): Promise;
}
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js
index 524ab245c6760..0a4fcfc23060c 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js
@@ -672,10 +672,10 @@ export class VectorLayer extends AbstractLayer {
}
this.syncVisibilityWithMb(mbMap, markerLayerId);
- mbMap.setLayerZoomRange(markerLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ mbMap.setLayerZoomRange(markerLayerId, this.getMinZoom(), this.getMaxZoom());
if (markerLayerId !== textLayerId) {
this.syncVisibilityWithMb(mbMap, textLayerId);
- mbMap.setLayerZoomRange(textLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ mbMap.setLayerZoomRange(textLayerId, this.getMinZoom(), this.getMaxZoom());
}
}
@@ -802,14 +802,14 @@ export class VectorLayer extends AbstractLayer {
});
this.syncVisibilityWithMb(mbMap, fillLayerId);
- mbMap.setLayerZoomRange(fillLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ mbMap.setLayerZoomRange(fillLayerId, this.getMinZoom(), this.getMaxZoom());
const fillFilterExpr = getFillFilterExpression(hasJoins);
if (fillFilterExpr !== mbMap.getFilter(fillLayerId)) {
mbMap.setFilter(fillLayerId, fillFilterExpr);
}
this.syncVisibilityWithMb(mbMap, lineLayerId);
- mbMap.setLayerZoomRange(lineLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ mbMap.setLayerZoomRange(lineLayerId, this.getMinZoom(), this.getMaxZoom());
const lineFilterExpr = getLineFilterExpression(hasJoins);
if (lineFilterExpr !== mbMap.getFilter(lineLayerId)) {
mbMap.setFilter(lineLayerId, lineFilterExpr);
@@ -822,9 +822,9 @@ export class VectorLayer extends AbstractLayer {
}
_syncSourceBindingWithMb(mbMap) {
- const mbSource = mbMap.getSource(this.getId());
+ const mbSource = mbMap.getSource(this._getMbSourceId());
if (!mbSource) {
- mbMap.addSource(this.getId(), {
+ mbMap.addSource(this._getMbSourceId(), {
type: 'geojson',
data: EMPTY_FEATURE_COLLECTION,
});
@@ -891,16 +891,17 @@ export class VectorLayer extends AbstractLayer {
}
async getPropertiesForTooltip(properties) {
- let allTooltips = await this.getSource().filterAndFormatPropertiesToHtml(properties);
- this._addJoinsToSourceTooltips(allTooltips);
+ const vectorSource = this.getSource();
+ let allProperties = await vectorSource.filterAndFormatPropertiesToHtml(properties);
+ this._addJoinsToSourceTooltips(allProperties);
for (let i = 0; i < this.getJoins().length; i++) {
const propsFromJoin = await this.getJoins()[i].filterAndFormatPropertiesForTooltip(
properties
);
- allTooltips = [...allTooltips, ...propsFromJoin];
+ allProperties = [...allProperties, ...propsFromJoin];
}
- return allTooltips;
+ return allProperties;
}
canShowTooltip() {
@@ -912,7 +913,7 @@ export class VectorLayer extends AbstractLayer {
getFeatureById(id) {
const featureCollection = this._getSourceFeatureCollection();
if (!featureCollection) {
- return;
+ return null;
}
return featureCollection.features.find((feature) => {
diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx
index ac69505a9bed5..7021859ee9827 100644
--- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx
@@ -15,7 +15,7 @@ import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/vi
interface Props {
layerId: string;
- onChange: (args: OnSourceChangeArgs) => void;
+ onChange: (...args: OnSourceChangeArgs[]) => void;
source: IEmsFileSource;
tooltipFields: IField[];
}
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_field_config_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_field_config_editor.test.tsx.snap
new file mode 100644
index 0000000000000..f6d0129e85abf
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_field_config_editor.test.tsx.snap
@@ -0,0 +1,491 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render error for dupes 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ string
+
+ ,
+ "value": "String",
+ },
+ Object {
+ "inputDisplay":
+
+
+
+
+ number
+
+ ,
+ "value": "Number",
+ },
+ ]
+ }
+ valueOfSelected="String"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ string
+
+ ,
+ "value": "String",
+ },
+ Object {
+ "inputDisplay":
+
+
+
+
+ number
+
+ ,
+ "value": "Number",
+ },
+ ]
+ }
+ valueOfSelected="Number"
+ />
+
+
+
+
+
+
+
+
+
+
+ Add
+
+
+
+
+`;
+
+exports[`should render error for empty name 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ string
+
+ ,
+ "value": "String",
+ },
+ Object {
+ "inputDisplay":
+
+
+
+
+ number
+
+ ,
+ "value": "Number",
+ },
+ ]
+ }
+ valueOfSelected="String"
+ />
+
+
+
+
+
+
+
+
+
+
+ Add
+
+
+
+
+`;
+
+exports[`should render field editor 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ string
+
+ ,
+ "value": "String",
+ },
+ Object {
+ "inputDisplay":
+
+
+
+
+ number
+
+ ,
+ "value": "Number",
+ },
+ ]
+ }
+ valueOfSelected="String"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ string
+
+ ,
+ "value": "String",
+ },
+ Object {
+ "inputDisplay":
+
+
+
+
+ number
+
+ ,
+ "value": "Number",
+ },
+ ]
+ }
+ valueOfSelected="Number"
+ />
+
+
+
+
+
+
+
+
+
+
+ Add
+
+
+
+
+`;
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap
new file mode 100644
index 0000000000000..699173bd362fa
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_source_settings.test.tsx.snap
@@ -0,0 +1,211 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render fields-editor when there is no layername 1`] = `
+
+
+
+
+
+
+ Available levels
+
+
+
+
+ }
+ max={24}
+ min={0}
+ onChange={[Function]}
+ prepend="Zoom"
+ showInput="inputWithPopover"
+ showLabels={true}
+ value={
+ Array [
+ 4,
+ 14,
+ ]
+ }
+ />
+
+`;
+
+exports[`should render with fields 1`] = `
+
+
+
+
+
+
+ Available levels
+
+
+
+
+ }
+ max={24}
+ min={0}
+ onChange={[Function]}
+ prepend="Zoom"
+ showInput="inputWithPopover"
+ showLabels={true}
+ value={
+ Array [
+ 4,
+ 14,
+ ]
+ }
+ />
+
+ Fields which are available in
+
+
+ foobar
+
+ .
+
+
+ These can be used for tooltips and dynamic styling.
+
+ }
+ delay="regular"
+ position="top"
+ >
+
+ Fields
+
+
+
+
+ }
+ labelType="label"
+ >
+
+
+
+`;
+
+exports[`should render without fields 1`] = `
+
+
+
+
+
+
+ Available levels
+
+
+
+
+ }
+ max={24}
+ min={0}
+ onChange={[Function]}
+ prepend="Zoom"
+ showInput="inputWithPopover"
+ showLabels={true}
+ value={
+ Array [
+ 4,
+ 14,
+ ]
+ }
+ />
+
+`;
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_vector_source_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_vector_source_editor.test.tsx.snap
new file mode 100644
index 0000000000000..ccd0e0064d075
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/mvt_single_layer_vector_source_editor.test.tsx.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render source creation editor (fields should _not_ be included) 1`] = `
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/update_source_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/update_source_editor.test.tsx.snap
new file mode 100644
index 0000000000000..bccf2b17e2b5d
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/__snapshots__/update_source_editor.test.tsx.snap
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render update source editor (fields _should_ be included) 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx
index 067c7f5a47ca3..32fa329be85df 100644
--- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx
@@ -6,23 +6,21 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
-import {
- MVTSingleLayerVectorSourceEditor,
- MVTSingleLayerVectorSourceConfig,
-} from './mvt_single_layer_vector_source_editor';
+import { MVTSingleLayerVectorSourceEditor } from './mvt_single_layer_vector_source_editor';
import { MVTSingleLayerVectorSource, sourceTitle } from './mvt_single_layer_vector_source';
import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry';
import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer';
import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants';
+import { TiledSingleLayerVectorSourceSettings } from '../../../../common/descriptor_types';
export const mvtVectorSourceWizardConfig: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.REFERENCE],
description: i18n.translate('xpack.maps.source.mvtVectorSourceWizard', {
- defaultMessage: 'Vector source wizard',
+ defaultMessage: 'Data service implementing the Mapbox vector tile specification',
}),
icon: 'grid',
renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => {
- const onSourceConfigChange = (sourceConfig: MVTSingleLayerVectorSourceConfig) => {
+ const onSourceConfigChange = (sourceConfig: TiledSingleLayerVectorSourceSettings) => {
const sourceDescriptor = MVTSingleLayerVectorSource.createDescriptor(sourceConfig);
const layerDescriptor = TiledVectorLayer.createDescriptor({ sourceDescriptor }, mapColors);
previewLayers([layerDescriptor]);
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.test.tsx
new file mode 100644
index 0000000000000..0121dc45cb9ee
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.test.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+jest.mock('../../../kibana_services', () => ({}));
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { MVTFieldConfigEditor } from './mvt_field_config_editor';
+import { MVT_FIELD_TYPE } from '../../../../common/constants';
+
+test('should render field editor', async () => {
+ const fields = [
+ {
+ name: 'foo',
+ type: MVT_FIELD_TYPE.STRING,
+ },
+ {
+ name: 'bar',
+ type: MVT_FIELD_TYPE.NUMBER,
+ },
+ ];
+ const component = shallow( {}} />);
+
+ expect(component).toMatchSnapshot();
+});
+
+test('should render error for empty name', async () => {
+ const fields = [
+ {
+ name: '',
+ type: MVT_FIELD_TYPE.STRING,
+ },
+ ];
+ const component = shallow( {}} />);
+
+ expect(component).toMatchSnapshot();
+});
+
+test('should render error for dupes', async () => {
+ const fields = [
+ {
+ name: 'foo',
+ type: MVT_FIELD_TYPE.STRING,
+ },
+ {
+ name: 'foo',
+ type: MVT_FIELD_TYPE.NUMBER,
+ },
+ ];
+ const component = shallow( {}} />);
+
+ expect(component).toMatchSnapshot();
+});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx
new file mode 100644
index 0000000000000..b2a93a4ef88ad
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx
@@ -0,0 +1,210 @@
+/*
+ * 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 @typescript-eslint/consistent-type-definitions */
+
+import React, { ChangeEvent, Component, Fragment } from 'react';
+import {
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiSuperSelect,
+ EuiFieldText,
+ EuiSpacer,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import _ from 'lodash';
+import { MVTFieldDescriptor } from '../../../../common/descriptor_types';
+import { FieldIcon } from '../../../../../../../src/plugins/kibana_react/public';
+import { MVT_FIELD_TYPE } from '../../../../common/constants';
+
+function makeOption({
+ value,
+ icon,
+ message,
+}: {
+ value: MVT_FIELD_TYPE;
+ icon: string;
+ message: string;
+}) {
+ return {
+ value,
+ inputDisplay: (
+
+
+
+
+ {message}
+
+ ),
+ };
+}
+
+const FIELD_TYPE_OPTIONS = [
+ {
+ value: MVT_FIELD_TYPE.STRING,
+ icon: 'string',
+ message: i18n.translate('xpack.maps.mvtSource.stringFieldLabel', {
+ defaultMessage: 'string',
+ }),
+ },
+ {
+ value: MVT_FIELD_TYPE.NUMBER,
+ icon: 'number',
+ message: i18n.translate('xpack.maps.mvtSource.numberFieldLabel', {
+ defaultMessage: 'number',
+ }),
+ },
+].map(makeOption);
+
+interface Props {
+ fields: MVTFieldDescriptor[];
+ onChange: (fields: MVTFieldDescriptor[]) => void;
+}
+
+interface State {
+ currentFields: MVTFieldDescriptor[];
+}
+
+export class MVTFieldConfigEditor extends Component {
+ state: State = {
+ currentFields: _.cloneDeep(this.props.fields),
+ };
+
+ _notifyChange = _.debounce(() => {
+ const invalid = this.state.currentFields.some((field: MVTFieldDescriptor) => {
+ return field.name === '';
+ });
+
+ if (!invalid) {
+ this.props.onChange(this.state.currentFields);
+ }
+ });
+
+ _fieldChange(newFields: MVTFieldDescriptor[]) {
+ this.setState(
+ {
+ currentFields: newFields,
+ },
+ this._notifyChange
+ );
+ }
+
+ _removeField(index: number) {
+ const newFields: MVTFieldDescriptor[] = this.state.currentFields.slice();
+ newFields.splice(index, 1);
+ this._fieldChange(newFields);
+ }
+
+ _addField = () => {
+ const newFields: MVTFieldDescriptor[] = this.state.currentFields.slice();
+ newFields.push({
+ type: MVT_FIELD_TYPE.STRING,
+ name: '',
+ });
+ this._fieldChange(newFields);
+ };
+
+ _renderFieldTypeDropDown(mvtFieldConfig: MVTFieldDescriptor, index: number) {
+ const onChange = (type: MVT_FIELD_TYPE) => {
+ const newFields = this.state.currentFields.slice();
+ newFields[index] = {
+ type,
+ name: newFields[index].name,
+ };
+ this._fieldChange(newFields);
+ };
+
+ return (
+ onChange(value)}
+ compressed
+ />
+ );
+ }
+
+ _renderFieldButtonDelete(index: number) {
+ return (
+ {
+ this._removeField(index);
+ }}
+ title={i18n.translate('xpack.maps.mvtSource.trashButtonTitle', {
+ defaultMessage: 'Remove field',
+ })}
+ aria-label={i18n.translate('xpack.maps.mvtSource.trashButtonAriaLabel', {
+ defaultMessage: 'Remove field',
+ })}
+ />
+ );
+ }
+
+ _renderFieldNameInput(mvtFieldConfig: MVTFieldDescriptor, index: number) {
+ const onChange = (e: ChangeEvent) => {
+ const name = e.target.value;
+ const newFields = this.state.currentFields.slice();
+ newFields[index] = {
+ name,
+ type: newFields[index].type,
+ };
+ this._fieldChange(newFields);
+ };
+
+ const emptyName = mvtFieldConfig.name === '';
+ const hasDupes =
+ this.state.currentFields.filter((field) => field.name === mvtFieldConfig.name).length > 1;
+
+ return (
+
+ );
+ }
+
+ _renderFieldConfig() {
+ return this.state.currentFields.map((mvtFieldConfig: MVTFieldDescriptor, index: number) => {
+ return (
+ <>
+
+ {this._renderFieldNameInput(mvtFieldConfig, index)}
+ {this._renderFieldTypeDropDown(mvtFieldConfig, index)}
+ {this._renderFieldButtonDelete(index)}
+
+
+ >
+ );
+ });
+ }
+
+ render() {
+ return (
+
+ {this._renderFieldConfig()}
+
+
+
+
+ {i18n.translate('xpack.maps.mvtSource.addFieldLabel', {
+ defaultMessage: 'Add',
+ })}
+
+
+
+
+ );
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_source_settings.test.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_source_settings.test.tsx
new file mode 100644
index 0000000000000..b5c75b97e6cb2
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_source_settings.test.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+jest.mock('../../../kibana_services', () => ({}));
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { MVTSingleLayerSourceSettings } from './mvt_single_layer_source_settings';
+
+const defaultSettings = {
+ handleChange: () => {},
+ layerName: 'foobar',
+ fields: [],
+ minSourceZoom: 4,
+ maxSourceZoom: 14,
+ showFields: true,
+};
+
+test('should render with fields', async () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+});
+
+test('should render without fields', async () => {
+ const settings = { ...defaultSettings, showFields: false };
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+});
+
+test('should not render fields-editor when there is no layername', async () => {
+ const settings = { ...defaultSettings, layerName: '' };
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_source_settings.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_source_settings.tsx
new file mode 100644
index 0000000000000..cd3fd97cf66a6
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_source_settings.tsx
@@ -0,0 +1,191 @@
+/*
+ * 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 @typescript-eslint/consistent-type-definitions */
+
+import React, { Fragment, Component, ChangeEvent } from 'react';
+import { EuiFieldText, EuiFormRow, EuiToolTip, EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import _ from 'lodash';
+import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants';
+import { ValidatedDualRange, Value } from '../../../../../../../src/plugins/kibana_react/public';
+import { MVTFieldConfigEditor } from './mvt_field_config_editor';
+import { MVTFieldDescriptor } from '../../../../common/descriptor_types';
+
+export type MVTSettings = {
+ layerName: string;
+ fields: MVTFieldDescriptor[];
+ minSourceZoom: number;
+ maxSourceZoom: number;
+};
+
+interface State {
+ currentLayerName: string;
+ currentMinSourceZoom: number;
+ currentMaxSourceZoom: number;
+ currentFields: MVTFieldDescriptor[];
+}
+
+interface Props {
+ handleChange: (args: MVTSettings) => void;
+ layerName: string;
+ fields: MVTFieldDescriptor[];
+ minSourceZoom: number;
+ maxSourceZoom: number;
+ showFields: boolean;
+}
+
+export class MVTSingleLayerSourceSettings extends Component {
+ // Tracking in state to allow for debounce.
+ // Changes to layer-name and/or min/max zoom require heavy operation at map-level (removing and re-adding all sources/layers)
+ // To preserve snappyness of typing, debounce the dispatches.
+ state = {
+ currentLayerName: this.props.layerName,
+ currentMinSourceZoom: this.props.minSourceZoom,
+ currentMaxSourceZoom: this.props.maxSourceZoom,
+ currentFields: _.cloneDeep(this.props.fields),
+ };
+
+ _handleChange = _.debounce(() => {
+ this.props.handleChange({
+ layerName: this.state.currentLayerName,
+ minSourceZoom: this.state.currentMinSourceZoom,
+ maxSourceZoom: this.state.currentMaxSourceZoom,
+ fields: this.state.currentFields,
+ });
+ }, 200);
+
+ _handleLayerNameInputChange = (e: ChangeEvent) => {
+ this.setState({ currentLayerName: e.target.value }, this._handleChange);
+ };
+
+ _handleFieldChange = (fields: MVTFieldDescriptor[]) => {
+ this.setState({ currentFields: fields }, this._handleChange);
+ };
+
+ _handleZoomRangeChange = (e: Value) => {
+ this.setState(
+ {
+ currentMinSourceZoom: parseInt(e[0] as string, 10),
+ currentMaxSourceZoom: parseInt(e[1] as string, 10),
+ },
+ this._handleChange
+ );
+ };
+
+ render() {
+ const preMessage = i18n.translate(
+ 'xpack.maps.source.MVTSingleLayerVectorSourceEditor.fieldsPreHelpMessage',
+ {
+ defaultMessage: 'Fields which are available in ',
+ }
+ );
+ const message = (
+ <>
+ {this.state.currentLayerName}.{' '}
+ >
+ );
+ const postMessage = i18n.translate(
+ 'xpack.maps.source.MVTSingleLayerVectorSourceEditor.fieldsPostHelpMessage',
+ {
+ defaultMessage: 'These can be used for tooltips and dynamic styling.',
+ }
+ );
+ const fieldEditor =
+ this.props.showFields && this.state.currentLayerName !== '' ? (
+
+ {preMessage}
+ {message}
+ {postMessage}
+ >
+ }
+ >
+
+ {i18n.translate(
+ 'xpack.maps.source.MVTSingleLayerVectorSourceEditor.fieldsMessage',
+ {
+ defaultMessage: 'Fields',
+ }
+ )}{' '}
+
+
+
+ }
+ >
+
+
+ ) : null;
+
+ return (
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.maps.source.MVTSingleLayerVectorSourceEditor.zoomRangeTopMessage',
+ {
+ defaultMessage: 'Available levels',
+ }
+ )}{' '}
+
+
+
+ }
+ formRowDisplay="columnCompressed"
+ value={[this.state.currentMinSourceZoom, this.state.currentMaxSourceZoom]}
+ min={MIN_ZOOM}
+ max={MAX_ZOOM}
+ onChange={this._handleZoomRangeChange}
+ allowEmptyRange={false}
+ showInput="inputWithPopover"
+ compressed
+ showLabels
+ prepend={i18n.translate(
+ 'xpack.maps.source.MVTSingleLayerVectorSourceEditor.dataZoomRangeMessage',
+ {
+ defaultMessage: 'Zoom',
+ }
+ )}
+ />
+ {fieldEditor}
+
+ );
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx
new file mode 100644
index 0000000000000..bc08baad7a842
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.test.tsx
@@ -0,0 +1,91 @@
+/*
+ * 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 { MVTSingleLayerVectorSource } from './mvt_single_layer_vector_source';
+import { MVT_FIELD_TYPE, SOURCE_TYPES } from '../../../../common/constants';
+import { TiledSingleLayerVectorSourceDescriptor } from '../../../../common/descriptor_types';
+
+const descriptor: TiledSingleLayerVectorSourceDescriptor = {
+ type: SOURCE_TYPES.MVT_SINGLE_LAYER,
+ urlTemplate: 'https://example.com/{x}/{y}/{z}.pbf',
+ layerName: 'foobar',
+ minSourceZoom: 4,
+ maxSourceZoom: 14,
+ fields: [],
+ tooltipProperties: [],
+};
+
+describe('getUrlTemplateWithMeta', () => {
+ it('should echo configuration', async () => {
+ const source = new MVTSingleLayerVectorSource(descriptor);
+ const config = await source.getUrlTemplateWithMeta();
+ expect(config.urlTemplate).toEqual(descriptor.urlTemplate);
+ expect(config.layerName).toEqual(descriptor.layerName);
+ expect(config.minSourceZoom).toEqual(descriptor.minSourceZoom);
+ expect(config.maxSourceZoom).toEqual(descriptor.maxSourceZoom);
+ });
+});
+
+describe('canFormatFeatureProperties', () => {
+ it('false if no tooltips', async () => {
+ const source = new MVTSingleLayerVectorSource(descriptor);
+ expect(source.canFormatFeatureProperties()).toEqual(false);
+ });
+ it('true if tooltip', async () => {
+ const descriptorWithTooltips = {
+ ...descriptor,
+ fields: [{ name: 'foobar', type: MVT_FIELD_TYPE.STRING }],
+ tooltipProperties: ['foobar'],
+ };
+ const source = new MVTSingleLayerVectorSource(descriptorWithTooltips);
+ expect(source.canFormatFeatureProperties()).toEqual(true);
+ });
+});
+
+describe('filterAndFormatPropertiesToHtml', () => {
+ const descriptorWithFields = {
+ ...descriptor,
+ fields: [
+ {
+ name: 'foo',
+ type: MVT_FIELD_TYPE.STRING,
+ },
+ {
+ name: 'food',
+ type: MVT_FIELD_TYPE.STRING,
+ },
+ {
+ name: 'fooz',
+ type: MVT_FIELD_TYPE.NUMBER,
+ },
+ ],
+ tooltipProperties: ['foo', 'fooz'],
+ };
+
+ it('should get tooltipproperties', async () => {
+ const source = new MVTSingleLayerVectorSource(descriptorWithFields);
+ const tooltipProperties = await source.filterAndFormatPropertiesToHtml({
+ foo: 'bar',
+ fooz: 123,
+ });
+ expect(tooltipProperties.length).toEqual(2);
+ expect(tooltipProperties[0].getPropertyName()).toEqual('foo');
+ expect(tooltipProperties[0].getHtmlDisplayValue()).toEqual('bar');
+ expect(tooltipProperties[1].getPropertyName()).toEqual('fooz');
+ expect(tooltipProperties[1].getHtmlDisplayValue()).toEqual('123');
+ });
+});
+
+describe('getImmutableSourceProperties', () => {
+ it('should only show immutable props', async () => {
+ const source = new MVTSingleLayerVectorSource(descriptor);
+ const properties = await source.getImmutableProperties();
+ expect(properties).toEqual([
+ { label: 'Data source', value: '.pbf vector tiles' },
+ { label: 'Url', value: 'https://example.com/{x}/{y}/{z}.pbf' },
+ ]);
+ });
+});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts
deleted file mode 100644
index 03b91df22d3ca..0000000000000
--- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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';
-import uuid from 'uuid/v4';
-import { AbstractSource, ImmutableSourceProperty } from '../source';
-import { BoundsFilters, GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source';
-import { MAX_ZOOM, MIN_ZOOM, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
-import { IField } from '../../fields/field';
-import { registerSource } from '../source_registry';
-import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
-import {
- MapExtent,
- TiledSingleLayerVectorSourceDescriptor,
- VectorSourceSyncMeta,
-} from '../../../../common/descriptor_types';
-import { MVTSingleLayerVectorSourceConfig } from './mvt_single_layer_vector_source_editor';
-import { ITooltipProperty } from '../../tooltips/tooltip_property';
-
-export const sourceTitle = i18n.translate(
- 'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle',
- {
- defaultMessage: 'Vector Tile Layer',
- }
-);
-
-export class MVTSingleLayerVectorSource extends AbstractSource
- implements ITiledSingleLayerVectorSource {
- static createDescriptor({
- urlTemplate,
- layerName,
- minSourceZoom,
- maxSourceZoom,
- }: MVTSingleLayerVectorSourceConfig) {
- return {
- type: SOURCE_TYPES.MVT_SINGLE_LAYER,
- id: uuid(),
- urlTemplate,
- layerName,
- minSourceZoom: Math.max(MIN_ZOOM, minSourceZoom),
- maxSourceZoom: Math.min(MAX_ZOOM, maxSourceZoom),
- };
- }
-
- readonly _descriptor: TiledSingleLayerVectorSourceDescriptor;
-
- constructor(
- sourceDescriptor: TiledSingleLayerVectorSourceDescriptor,
- inspectorAdapters?: object
- ) {
- super(sourceDescriptor, inspectorAdapters);
- this._descriptor = sourceDescriptor;
- }
-
- renderSourceSettingsEditor() {
- return null;
- }
-
- getFieldNames(): string[] {
- return [];
- }
-
- getGeoJsonWithMeta(
- layerName: 'string',
- searchFilters: unknown[],
- registerCancelCallback: (callback: () => void) => void
- ): Promise {
- // todo: remove this method
- // This is a consequence of ITiledSingleLayerVectorSource extending IVectorSource.
- throw new Error('Does not implement getGeoJsonWithMeta');
- }
-
- async getFields(): Promise {
- return [];
- }
-
- async getImmutableProperties(): Promise {
- return [
- { label: getDataSourceLabel(), value: sourceTitle },
- { label: getUrlLabel(), value: this._descriptor.urlTemplate },
- {
- label: i18n.translate('xpack.maps.source.MVTSingleLayerVectorSource.layerNameMessage', {
- defaultMessage: 'Layer name',
- }),
- value: this._descriptor.layerName,
- },
- {
- label: i18n.translate('xpack.maps.source.MVTSingleLayerVectorSource.minZoomMessage', {
- defaultMessage: 'Min zoom',
- }),
- value: this._descriptor.minSourceZoom.toString(),
- },
- {
- label: i18n.translate('xpack.maps.source.MVTSingleLayerVectorSource.maxZoomMessage', {
- defaultMessage: 'Max zoom',
- }),
- value: this._descriptor.maxSourceZoom.toString(),
- },
- ];
- }
-
- async getDisplayName(): Promise {
- return this._descriptor.layerName;
- }
-
- async getUrlTemplateWithMeta() {
- return {
- urlTemplate: this._descriptor.urlTemplate,
- layerName: this._descriptor.layerName,
- minSourceZoom: this._descriptor.minSourceZoom,
- maxSourceZoom: this._descriptor.maxSourceZoom,
- };
- }
-
- async getSupportedShapeTypes(): Promise {
- return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
- }
-
- canFormatFeatureProperties() {
- return false;
- }
-
- getMinZoom() {
- return this._descriptor.minSourceZoom;
- }
-
- getMaxZoom() {
- return this._descriptor.maxSourceZoom;
- }
-
- getBoundsForFilters(
- boundsFilters: BoundsFilters,
- registerCancelCallback: (requestToken: symbol, callback: () => void) => void
- ): MapExtent | null {
- return null;
- }
-
- getFieldByName(fieldName: string): IField | null {
- return null;
- }
-
- getSyncMeta(): VectorSourceSyncMeta {
- return null;
- }
-
- getApplyGlobalQuery(): boolean {
- return false;
- }
-
- async filterAndFormatPropertiesToHtml(properties: unknown): Promise {
- return [];
- }
-}
-
-registerSource({
- ConstructorFunction: MVTSingleLayerVectorSource,
- type: SOURCE_TYPES.MVT_SINGLE_LAYER,
-});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx
new file mode 100644
index 0000000000000..ae28828dec5a8
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx
@@ -0,0 +1,222 @@
+/*
+ * 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';
+import uuid from 'uuid/v4';
+import React from 'react';
+import { GeoJsonProperties } from 'geojson';
+import { AbstractSource, ImmutableSourceProperty, SourceEditorArgs } from '../source';
+import { BoundsFilters, GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source';
+import {
+ FIELD_ORIGIN,
+ MAX_ZOOM,
+ MIN_ZOOM,
+ SOURCE_TYPES,
+ VECTOR_SHAPE_TYPE,
+} from '../../../../common/constants';
+import { registerSource } from '../source_registry';
+import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
+import {
+ MapExtent,
+ MVTFieldDescriptor,
+ TiledSingleLayerVectorSourceDescriptor,
+ VectorSourceSyncMeta,
+} from '../../../../common/descriptor_types';
+import { MVTField } from '../../fields/mvt_field';
+import { UpdateSourceEditor } from './update_source_editor';
+import { ITooltipProperty, TooltipProperty } from '../../tooltips/tooltip_property';
+
+export const sourceTitle = i18n.translate(
+ 'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle',
+ {
+ defaultMessage: '.pbf vector tiles',
+ }
+);
+
+export class MVTSingleLayerVectorSource extends AbstractSource
+ implements ITiledSingleLayerVectorSource {
+ static createDescriptor({
+ urlTemplate,
+ layerName,
+ minSourceZoom,
+ maxSourceZoom,
+ fields,
+ tooltipProperties,
+ }: Partial) {
+ return {
+ type: SOURCE_TYPES.MVT_SINGLE_LAYER,
+ id: uuid(),
+ urlTemplate: urlTemplate ? urlTemplate : '',
+ layerName: layerName ? layerName : '',
+ minSourceZoom:
+ typeof minSourceZoom === 'number' ? Math.max(MIN_ZOOM, minSourceZoom) : MIN_ZOOM,
+ maxSourceZoom:
+ typeof maxSourceZoom === 'number' ? Math.min(MAX_ZOOM, maxSourceZoom) : MAX_ZOOM,
+ fields: fields ? fields : [],
+ tooltipProperties: tooltipProperties ? tooltipProperties : [],
+ };
+ }
+
+ readonly _descriptor: TiledSingleLayerVectorSourceDescriptor;
+ readonly _tooltipFields: MVTField[];
+
+ constructor(
+ sourceDescriptor: TiledSingleLayerVectorSourceDescriptor,
+ inspectorAdapters?: object
+ ) {
+ super(sourceDescriptor, inspectorAdapters);
+ this._descriptor = MVTSingleLayerVectorSource.createDescriptor(sourceDescriptor);
+
+ this._tooltipFields = this._descriptor.tooltipProperties
+ .map((fieldName) => {
+ return this.getFieldByName(fieldName);
+ })
+ .filter((f) => f !== null) as MVTField[];
+ }
+
+ async supportsFitToBounds() {
+ return false;
+ }
+
+ renderSourceSettingsEditor({ onChange }: SourceEditorArgs) {
+ return (
+
+ );
+ }
+
+ getFieldNames(): string[] {
+ return this._descriptor.fields.map((field: MVTFieldDescriptor) => {
+ return field.name;
+ });
+ }
+
+ getMVTFields(): MVTField[] {
+ return this._descriptor.fields.map((field: MVTFieldDescriptor) => {
+ return new MVTField({
+ fieldName: field.name,
+ type: field.type,
+ source: this,
+ origin: FIELD_ORIGIN.SOURCE,
+ });
+ });
+ }
+
+ getFieldByName(fieldName: string): MVTField | null {
+ try {
+ return this.createField({ fieldName });
+ } catch (e) {
+ return null;
+ }
+ }
+
+ createField({ fieldName }: { fieldName: string }): MVTField {
+ const field = this._descriptor.fields.find((f: MVTFieldDescriptor) => {
+ return f.name === fieldName;
+ });
+ if (!field) {
+ throw new Error(`Cannot create field for fieldName ${fieldName}`);
+ }
+ return new MVTField({
+ fieldName: field.name,
+ type: field.type,
+ source: this,
+ origin: FIELD_ORIGIN.SOURCE,
+ });
+ }
+
+ getGeoJsonWithMeta(
+ layerName: 'string',
+ searchFilters: unknown[],
+ registerCancelCallback: (callback: () => void) => void
+ ): Promise {
+ // Having this method here is a consequence of ITiledSingleLayerVectorSource extending IVectorSource.
+ throw new Error('Does not implement getGeoJsonWithMeta');
+ }
+
+ async getFields(): Promise {
+ return this.getMVTFields();
+ }
+
+ getLayerName(): string {
+ return this._descriptor.layerName;
+ }
+
+ async getImmutableProperties(): Promise {
+ return [
+ { label: getDataSourceLabel(), value: sourceTitle },
+ { label: getUrlLabel(), value: this._descriptor.urlTemplate },
+ ];
+ }
+
+ async getDisplayName(): Promise {
+ return this.getLayerName();
+ }
+
+ async getUrlTemplateWithMeta() {
+ return {
+ urlTemplate: this._descriptor.urlTemplate,
+ layerName: this._descriptor.layerName,
+ minSourceZoom: this._descriptor.minSourceZoom,
+ maxSourceZoom: this._descriptor.maxSourceZoom,
+ };
+ }
+
+ async getSupportedShapeTypes(): Promise {
+ return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
+ }
+
+ canFormatFeatureProperties() {
+ return !!this._tooltipFields.length;
+ }
+
+ getMinZoom() {
+ return this._descriptor.minSourceZoom;
+ }
+
+ getMaxZoom() {
+ return this._descriptor.maxSourceZoom;
+ }
+
+ getBoundsForFilters(
+ boundsFilters: BoundsFilters,
+ registerCancelCallback: (requestToken: symbol, callback: () => void) => void
+ ): MapExtent | null {
+ return null;
+ }
+
+ getSyncMeta(): VectorSourceSyncMeta {
+ return null;
+ }
+
+ getApplyGlobalQuery(): boolean {
+ return false;
+ }
+
+ async filterAndFormatPropertiesToHtml(
+ properties: GeoJsonProperties,
+ featureId?: string | number
+ ): Promise {
+ const tooltips = [];
+ for (const key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ for (let i = 0; i < this._tooltipFields.length; i++) {
+ const mvtField = this._tooltipFields[i];
+ if (mvtField.getName() === key) {
+ const tooltip = new TooltipProperty(key, key, properties[key]);
+ tooltips.push(tooltip);
+ break;
+ }
+ }
+ }
+ }
+ return tooltips;
+ }
+}
+
+registerSource({
+ ConstructorFunction: MVTSingleLayerVectorSource,
+ type: SOURCE_TYPES.MVT_SINGLE_LAYER,
+});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.test.tsx
new file mode 100644
index 0000000000000..986756f840014
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.test.tsx
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+jest.mock('../../../kibana_services', () => ({}));
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { MVTSingleLayerVectorSourceEditor } from './mvt_single_layer_vector_source_editor';
+
+test('should render source creation editor (fields should _not_ be included)', async () => {
+ const component = shallow( {}} />);
+
+ expect(component).toMatchSnapshot();
+});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx
index 760b8c676cb37..49487e96a4544 100644
--- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx
@@ -10,17 +10,14 @@ import _ from 'lodash';
import { EuiFieldText, EuiFormRow, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants';
-import { ValidatedDualRange, Value } from '../../../../../../../src/plugins/kibana_react/public';
+import {
+ MVTFieldDescriptor,
+ TiledSingleLayerVectorSourceSettings,
+} from '../../../../common/descriptor_types';
+import { MVTSingleLayerSourceSettings } from './mvt_single_layer_source_settings';
-export type MVTSingleLayerVectorSourceConfig = {
- urlTemplate: string;
- layerName: string;
- minSourceZoom: number;
- maxSourceZoom: number;
-};
-
-export interface Props {
- onSourceConfigChange: (sourceConfig: MVTSingleLayerVectorSourceConfig) => void;
+interface Props {
+ onSourceConfigChange: (sourceConfig: TiledSingleLayerVectorSourceSettings) => void;
}
interface State {
@@ -28,6 +25,7 @@ interface State {
layerName: string;
minSourceZoom: number;
maxSourceZoom: number;
+ fields?: MVTFieldDescriptor[];
}
export class MVTSingleLayerVectorSourceEditor extends Component {
@@ -36,6 +34,7 @@ export class MVTSingleLayerVectorSourceEditor extends Component {
layerName: '',
minSourceZoom: MIN_ZOOM,
maxSourceZoom: MAX_ZOOM,
+ fields: [],
};
_sourceConfigChange = _.debounce(() => {
@@ -50,6 +49,7 @@ export class MVTSingleLayerVectorSourceEditor extends Component {
layerName: this.state.layerName,
minSourceZoom: this.state.minSourceZoom,
maxSourceZoom: this.state.maxSourceZoom,
+ fields: this.state.fields,
});
}
}, 200);
@@ -64,23 +64,13 @@ export class MVTSingleLayerVectorSourceEditor extends Component {
);
};
- _handleLayerNameInputChange = (e: ChangeEvent) => {
- const layerName = e.target.value;
- this.setState(
- {
- layerName,
- },
- () => this._sourceConfigChange()
- );
- };
-
- _handleZoomRangeChange = (e: Value) => {
- const minSourceZoom = parseInt(e[0] as string, 10);
- const maxSourceZoom = parseInt(e[1] as string, 10);
-
- if (this.state.minSourceZoom !== minSourceZoom || this.state.maxSourceZoom !== maxSourceZoom) {
- this.setState({ minSourceZoom, maxSourceZoom }, () => this._sourceConfigChange());
- }
+ _handleChange = (state: {
+ layerName: string;
+ fields: MVTFieldDescriptor[];
+ minSourceZoom: number;
+ maxSourceZoom: number;
+ }) => {
+ this.setState(state, () => this._sourceConfigChange());
};
render() {
@@ -90,37 +80,30 @@ export class MVTSingleLayerVectorSourceEditor extends Component {
label={i18n.translate('xpack.maps.source.MVTSingleLayerVectorSourceEditor.urlMessage', {
defaultMessage: 'Url',
})}
- >
-
-
-
-
+
-
);
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/types.ts b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/types.ts
new file mode 100644
index 0000000000000..599eaea73c9a0
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/types.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { MVTFieldDescriptor } from '../../../../common/descriptor_types';
+
+export interface MVTSingleLayerVectorSourceConfig {
+ urlTemplate: string;
+ layerName: string;
+ minSourceZoom: number;
+ maxSourceZoom: number;
+ fields?: MVTFieldDescriptor[];
+ tooltipProperties?: string[];
+}
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.test.tsx
new file mode 100644
index 0000000000000..fd19379058e3b
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.test.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+jest.mock('../../../kibana_services', () => ({}));
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { UpdateSourceEditor } from './update_source_editor';
+import { MVTSingleLayerVectorSource } from './mvt_single_layer_vector_source';
+import { TiledSingleLayerVectorSourceDescriptor } from '../../../../common/descriptor_types';
+import { SOURCE_TYPES } from '../../../../common/constants';
+
+const descriptor: TiledSingleLayerVectorSourceDescriptor = {
+ type: SOURCE_TYPES.MVT_SINGLE_LAYER,
+ urlTemplate: 'https://example.com/{x}/{y}/{z}.pbf',
+ layerName: 'foobar',
+ minSourceZoom: 4,
+ maxSourceZoom: 14,
+ fields: [],
+ tooltipProperties: [],
+};
+
+test('should render update source editor (fields _should_ be included)', async () => {
+ const source = new MVTSingleLayerVectorSource(descriptor);
+
+ const component = shallow(
+ {}} />
+ );
+
+ expect(component).toMatchSnapshot();
+});
diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx
new file mode 100644
index 0000000000000..a959912718197
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/update_source_editor.tsx
@@ -0,0 +1,136 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { TooltipSelector } from '../../../components/tooltip_selector';
+import { MVTField } from '../../fields/mvt_field';
+import { MVTSingleLayerVectorSource } from './mvt_single_layer_vector_source';
+import { MVTSettings, MVTSingleLayerSourceSettings } from './mvt_single_layer_source_settings';
+import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view';
+import { MVTFieldDescriptor } from '../../../../common/descriptor_types';
+
+interface Props {
+ tooltipFields: MVTField[];
+ onChange: (...args: OnSourceChangeArgs[]) => void;
+ source: MVTSingleLayerVectorSource;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+interface State {}
+
+export class UpdateSourceEditor extends Component {
+ _onTooltipPropertiesSelect = (propertyNames: string[]) => {
+ this.props.onChange({ propName: 'tooltipProperties', value: propertyNames });
+ };
+
+ _handleChange = (settings: MVTSettings) => {
+ const changes: OnSourceChangeArgs[] = [];
+ if (settings.layerName !== this.props.source.getLayerName()) {
+ changes.push({ propName: 'layerName', value: settings.layerName });
+ }
+ if (settings.minSourceZoom !== this.props.source.getMinZoom()) {
+ changes.push({ propName: 'minSourceZoom', value: settings.minSourceZoom });
+ }
+ if (settings.maxSourceZoom !== this.props.source.getMaxZoom()) {
+ changes.push({ propName: 'maxSourceZoom', value: settings.maxSourceZoom });
+ }
+ if (!_.isEqual(settings.fields, this._getFieldDescriptors())) {
+ changes.push({ propName: 'fields', value: settings.fields });
+
+ // Remove dangling tooltips.
+ // This behaves similar to how stale styling properties are removed (e.g. on metric-change in agg sources)
+ const sanitizedTooltips = [];
+ for (let i = 0; i < this.props.tooltipFields.length; i++) {
+ const tooltipName = this.props.tooltipFields[i].getName();
+ for (let j = 0; j < settings.fields.length; j++) {
+ if (settings.fields[j].name === tooltipName) {
+ sanitizedTooltips.push(tooltipName);
+ break;
+ }
+ }
+ }
+
+ if (!_.isEqual(sanitizedTooltips, this.props.tooltipFields)) {
+ changes.push({ propName: 'tooltipProperties', value: sanitizedTooltips });
+ }
+ }
+ this.props.onChange(...changes);
+ };
+
+ _getFieldDescriptors(): MVTFieldDescriptor[] {
+ return this.props.source.getMVTFields().map((field: MVTField) => {
+ return field.getMVTFieldDescriptor();
+ });
+ }
+
+ _renderSourceSettingsCard() {
+ const fieldDescriptors: MVTFieldDescriptor[] = this._getFieldDescriptors();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ _renderTooltipSelectionCard() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ render() {
+ return (
+
+ {this._renderSourceSettingsCard()}
+ {this._renderTooltipSelectionCard()}
+
+ );
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts
index f937eac336532..c68e22ada8b0c 100644
--- a/x-pack/plugins/maps/public/classes/sources/source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/source.ts
@@ -17,7 +17,7 @@ import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view';
export type SourceEditorArgs = {
- onChange: (args: OnSourceChangeArgs) => void;
+ onChange: (...args: OnSourceChangeArgs[]) => void;
};
export type ImmutableSourceProperty = {
diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts
index 99a7478cd8362..42993bf36f618 100644
--- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts
+++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts
@@ -5,7 +5,7 @@
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
-import { FeatureCollection } from 'geojson';
+import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
import { Filter, TimeRange } from 'src/plugins/data/public';
import { AbstractSource, ISource } from '../source';
import { IField } from '../../fields/field';
@@ -35,7 +35,7 @@ export type BoundsFilters = {
};
export interface IVectorSource extends ISource {
- filterAndFormatPropertiesToHtml(properties: unknown): Promise;
+ filterAndFormatPropertiesToHtml(properties: GeoJsonProperties): Promise;
getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (requestToken: symbol, callback: () => void) => void
@@ -51,10 +51,12 @@ export interface IVectorSource extends ISource {
getSyncMeta(): VectorSourceSyncMeta;
getFieldNames(): string[];
getApplyGlobalQuery(): boolean;
+ createField({ fieldName }: { fieldName: string }): IField;
+ canFormatFeatureProperties(): boolean;
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
- filterAndFormatPropertiesToHtml(properties: unknown): Promise;
+ filterAndFormatPropertiesToHtml(properties: GeoJsonProperties): Promise;
getBoundsForFilters(
boundsFilters: BoundsFilters,
registerCancelCallback: (requestToken: symbol, callback: () => void) => void
@@ -72,6 +74,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc
canFormatFeatureProperties(): boolean;
getApplyGlobalQuery(): boolean;
getFieldNames(): string[];
+ createField({ fieldName }: { fieldName: string }): IField;
}
export interface ITiledSingleLayerVectorSource extends IVectorSource {
@@ -83,4 +86,5 @@ export interface ITiledSingleLayerVectorSource extends IVectorSource {
}>;
getMinZoom(): number;
getMaxZoom(): number;
+ getLayerName(): string;
}
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js
index b7a80562f10ca..fe2f302504a15 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js
@@ -89,7 +89,7 @@ export class ColorMapSelect extends Component {
};
_renderColorStopsInput() {
- if (!this.props.useCustomColorMap) {
+ if (!this.props.isCustomOnly && !this.props.useCustomColorMap) {
return null;
}
@@ -102,7 +102,7 @@ export class ColorMapSelect extends Component {
swatches={this.props.swatches}
/>
);
- } else
+ } else {
colorStopEditor = (
);
+ }
return (
@@ -121,6 +122,10 @@ export class ColorMapSelect extends Component {
}
_renderColorMapSelections() {
+ if (this.props.isCustomOnly) {
+ return null;
+ }
+
const colorMapOptionsWithCustom = [
{
value: CUSTOM_COLOR_MAP,
@@ -146,19 +151,22 @@ export class ColorMapSelect extends Component {
) : null;
return (
-
- {toggle}
-
-
-
-
+
+
+ {toggle}
+
+
+
+
+
+
);
}
@@ -166,7 +174,6 @@ export class ColorMapSelect extends Component {
return (
{this._renderColorMapSelections()}
-
{this._renderColorStopsInput()}
);
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js
index fa13e1cf66664..90070343a1b48 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js
@@ -90,6 +90,7 @@ export function DynamicColorForm({
if (styleProperty.isOrdinal()) {
return (
{
+ const field = fields.find((field) => {
return field.name === selectedFieldName;
});
+ //Do not spread in all the other unused values (e.g. type, supportsAutoDomain etc...)
+ if (field) {
+ selectedOption = {
+ value: field.value,
+ label: field.label,
+ };
+ }
}
return (
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/style_map_select.js b/x-pack/plugins/maps/public/classes/styles/vector/components/style_map_select.js
index e285d91dcd7a4..e4dc9d1b4d8f6 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/style_map_select.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/style_map_select.js
@@ -46,19 +46,16 @@ export class StyleMapSelect extends Component {
};
_renderCustomStopsInput() {
- if (!this.props.useCustomMap) {
+ return !this.props.isCustomOnly && !this.props.useCustomMap
+ ? null
+ : this.props.renderCustomStopsInput(this._onCustomMapChange);
+ }
+
+ _renderMapSelect() {
+ if (this.props.isCustomOnly) {
return null;
}
- return (
-
-
- {this.props.renderCustomStopsInput(this._onCustomMapChange)}
-
- );
- }
-
- render() {
const mapOptionsWithCustom = [
{
value: CUSTOM_MAP,
@@ -87,6 +84,15 @@ export class StyleMapSelect extends Component {
hasDividers={true}
compressed
/>
+
+
+ );
+ }
+
+ render() {
+ return (
+
+ {this._renderMapSelect()}
{this._renderCustomStopsInput()}
);
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/dynamic_icon_form.js
index f9f8a67846470..e3724d42a783b 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/dynamic_icon_form.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/dynamic_icon_form.js
@@ -36,17 +36,20 @@ export function DynamicIconForm({
};
function renderIconMapSelect() {
- if (!styleOptions.field || !styleOptions.field.name) {
+ const field = styleProperty.getField();
+ if (!field) {
return null;
}
return (
);
}
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.js b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.js
index 08f5dfe4f4ba0..6cfe656d65a1e 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.js
@@ -8,8 +8,8 @@ import React from 'react';
import { StyleMapSelect } from '../style_map_select';
import { i18n } from '@kbn/i18n';
-import { getIconPaletteOptions } from '../../symbol_utils';
import { IconStops } from './icon_stops';
+import { getIconPaletteOptions } from '../../symbol_utils';
export function IconMapSelect({
customIconStops,
@@ -19,6 +19,7 @@ export function IconMapSelect({
styleProperty,
symbolOptions,
useCustomIconMap,
+ isCustomOnly,
}) {
function onMapSelectChange({ customMapStops, selectedMapId, useCustomMap }) {
onChange({
@@ -52,6 +53,7 @@ export function IconMapSelect({
useCustomMap={useCustomIconMap}
selectedMapId={iconPaletteId}
renderCustomStopsInput={renderCustomIconStopsInput}
+ isCustomOnly={isCustomOnly}
/>
);
}
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js
index 7856a4ddaff39..6528648eff552 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js
@@ -62,6 +62,7 @@ export class VectorStyleEditor extends Component {
name: field.getName(),
origin: field.getOrigin(),
type: await field.getDataType(),
+ supportsAutoDomain: field.supportsAutoDomain(),
};
};
@@ -109,7 +110,9 @@ export class VectorStyleEditor extends Component {
}
_getOrdinalFields() {
- return [...this.state.dateFields, ...this.state.numberFields];
+ return [...this.state.dateFields, ...this.state.numberFields].filter((field) => {
+ return field.supportsAutoDomain;
+ });
}
_handleSelectedFeatureChange = (selectedFeature) => {
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js
index ae4d935e2457b..763eb81ad0f98 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js
@@ -10,11 +10,10 @@ import { VECTOR_STYLES } from '../../../../../common/constants';
export class DynamicOrientationProperty extends DynamicStyleProperty {
syncIconRotationWithMb(symbolLayerId, mbMap) {
- if (this._options.field && this._options.field.name) {
- const targetName = getComputedFieldName(
- VECTOR_STYLES.ICON_ORIENTATION,
- this._options.field.name
- );
+ if (this._field && this._field.isValid()) {
+ const targetName = this._field.supportsAutoDomain()
+ ? getComputedFieldName(VECTOR_STYLES.ICON_ORIENTATION, this.getFieldName())
+ : this._field.getName();
// Using property state instead of feature-state because layout properties do not support feature-state
mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', ['coalesce', ['get', targetName], 0]);
} else {
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.js
index de868f3f92650..a7a3130875a95 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.js
@@ -10,7 +10,11 @@ import { getComputedFieldName } from '../style_util';
export class DynamicTextProperty extends DynamicStyleProperty {
syncTextFieldWithMb(mbLayerId, mbMap) {
if (this._field && this._field.isValid()) {
- const targetName = getComputedFieldName(this._styleName, this._options.field.name);
+ // Fields that support auto-domain are normalized with a field-formatter and stored into a computed-field
+ // Otherwise, the raw value is just carried over and no computed field is created.
+ const targetName = this._field.supportsAutoDomain()
+ ? getComputedFieldName(this._styleName, this.getFieldName())
+ : this._field.getName();
mbMap.setLayoutProperty(mbLayerId, 'text-field', ['coalesce', ['get', targetName], '']);
} else {
mbMap.setLayoutProperty(mbLayerId, 'text-field', null);
diff --git a/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts
index 7149fe29f90ec..7bb79d8d341d3 100644
--- a/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts
+++ b/x-pack/plugins/maps/public/classes/tooltips/tooltip_property.ts
@@ -19,7 +19,7 @@ export interface ITooltipProperty {
export interface LoadFeatureProps {
layerId: string;
- featureId: number;
+ featureId?: number | string;
}
export interface FeatureGeometry {
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/view.js b/x-pack/plugins/maps/public/connected_components/layer_panel/view.js
index 14252dcfc067d..557fe5fd5f705 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/view.js
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/view.js
@@ -43,16 +43,16 @@ export class LayerPanel extends React.Component {
componentDidMount() {
this._isMounted = true;
- this.loadDisplayName();
- this.loadImmutableSourceProperties();
- this.loadLeftJoinFields();
+ this._loadDisplayName();
+ this._loadImmutableSourceProperties();
+ this._loadLeftJoinFields();
}
componentWillUnmount() {
this._isMounted = false;
}
- loadDisplayName = async () => {
+ _loadDisplayName = async () => {
if (!this.props.selectedLayer) {
return;
}
@@ -63,7 +63,7 @@ export class LayerPanel extends React.Component {
}
};
- loadImmutableSourceProperties = async () => {
+ _loadImmutableSourceProperties = async () => {
if (!this.props.selectedLayer) {
return;
}
@@ -74,7 +74,7 @@ export class LayerPanel extends React.Component {
}
};
- async loadLeftJoinFields() {
+ async _loadLeftJoinFields() {
if (!this.props.selectedLayer || !this.props.selectedLayer.isJoinable()) {
return;
}
@@ -97,8 +97,11 @@ export class LayerPanel extends React.Component {
}
}
- _onSourceChange = ({ propName, value, newLayerType }) => {
- this.props.updateSourceProp(this.props.selectedLayer.getId(), propName, value, newLayerType);
+ _onSourceChange = (...args) => {
+ for (let i = 0; i < args.length; i++) {
+ const { propName, value, newLayerType } = args[i];
+ this.props.updateSourceProp(this.props.selectedLayer.getId(), propName, value, newLayerType);
+ }
};
_renderFilterSection() {
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
index 362186a8f5549..5e2a153b2ccbf 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
@@ -31,14 +31,15 @@ export class FeatureProperties extends React.Component {
this._isMounted = false;
}
- _loadProperties = () => {
+ _loadProperties = async () => {
this._fetchProperties({
nextFeatureId: this.props.featureId,
nextLayerId: this.props.layerId,
+ mbProperties: this.props.mbProperties,
});
};
- _fetchProperties = async ({ nextLayerId, nextFeatureId }) => {
+ _fetchProperties = async ({ nextLayerId, nextFeatureId, mbProperties }) => {
if (this.prevLayerId === nextLayerId && this.prevFeatureId === nextFeatureId) {
// do not reload same feature properties
return;
@@ -64,6 +65,7 @@ export class FeatureProperties extends React.Component {
properties = await this.props.loadFeatureProperties({
layerId: nextLayerId,
featureId: nextFeatureId,
+ mbProperties: mbProperties,
});
} catch (error) {
if (this._isMounted) {
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
index e5b97947602b0..d91bc8e803ab9 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
@@ -132,6 +132,7 @@ export class FeaturesTooltip extends React.Component {
{
sinon.assert.notCalled(closeOnClickTooltipStub);
sinon.assert.calledWith(openOnClickTooltipStub, {
- features: [{ id: 1, layerId: 'tfi3f' }],
+ features: [{ id: 1, layerId: 'tfi3f', mbProperties: { __kbn__feature_id__: 1 } }],
location: [100, 30],
});
});
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
index 03c2aeb2edd0a..6c42057680408 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
@@ -58,7 +58,7 @@ export class TooltipPopover extends Component {
// Mapbox feature geometry is from vector tile and is not the same as the original geometry.
_loadFeatureGeometry = ({ layerId, featureId }) => {
const tooltipLayer = this._findLayerById(layerId);
- if (!tooltipLayer) {
+ if (!tooltipLayer || typeof featureId === 'undefined') {
return null;
}
@@ -70,22 +70,24 @@ export class TooltipPopover extends Component {
return targetFeature.geometry;
};
- _loadFeatureProperties = async ({ layerId, featureId }) => {
+ _loadFeatureProperties = async ({ layerId, featureId, mbProperties }) => {
const tooltipLayer = this._findLayerById(layerId);
if (!tooltipLayer) {
return [];
}
- const targetFeature = tooltipLayer.getFeatureById(featureId);
- if (!targetFeature) {
- return [];
+ let targetFeature;
+ if (typeof featureId !== 'undefined') {
+ targetFeature = tooltipLayer.getFeatureById(featureId);
}
- return await tooltipLayer.getPropertiesForTooltip(targetFeature.properties);
+
+ const properties = targetFeature ? targetFeature.properties : mbProperties;
+ return await tooltipLayer.getPropertiesForTooltip(properties);
};
_loadPreIndexedShape = async ({ layerId, featureId }) => {
const tooltipLayer = this._findLayerById(layerId);
- if (!tooltipLayer) {
+ if (!tooltipLayer || typeof featureId === 'undefined') {
return null;
}
diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx
index 9539d530bab04..9d5125532e5b8 100644
--- a/x-pack/plugins/ml/public/application/app.tsx
+++ b/x-pack/plugins/ml/public/application/app.tsx
@@ -7,7 +7,7 @@
import React, { FC } from 'react';
import ReactDOM from 'react-dom';
-import { AppMountParameters, CoreStart } from 'kibana/public';
+import { AppMountParameters, CoreStart, HttpStart } from 'kibana/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
@@ -17,6 +17,8 @@ import { setLicenseCache } from './license';
import { MlSetupDependencies, MlStartDependencies } from '../plugin';
import { MlRouter } from './routing';
+import { mlApiServicesProvider } from './services/ml_api_service';
+import { HttpService } from './services/http_service';
type MlDependencies = MlSetupDependencies & MlStartDependencies;
@@ -27,6 +29,23 @@ interface AppProps {
const localStorage = new Storage(window.localStorage);
+/**
+ * Provides global services available across the entire ML app.
+ */
+export function getMlGlobalServices(httpStart: HttpStart) {
+ const httpService = new HttpService(httpStart);
+ return {
+ httpService,
+ mlApiServices: mlApiServicesProvider(httpService),
+ };
+}
+
+export interface MlServicesContext {
+ mlServices: MlGlobalServices;
+}
+
+export type MlGlobalServices = ReturnType;
+
const App: FC = ({ coreStart, deps }) => {
const pageDeps = {
indexPatterns: deps.data.indexPatterns,
@@ -47,7 +66,9 @@ const App: FC = ({ coreStart, deps }) => {
const I18nContext = coreStart.i18n.Context;
return (
-
+
diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx
index 3fb654f35be4d..803281bcd0ce9 100644
--- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx
+++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx
@@ -27,7 +27,6 @@ import {
normalizeTimes,
} from './job_select_service_utils';
import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
-import { ml } from '../../services/ml_api_service';
import { useMlKibana } from '../../contexts/kibana';
import { JobSelectionMaps } from './job_selector';
@@ -66,7 +65,10 @@ export const JobSelectorFlyout: FC = ({
withTimeRangeSelector = true,
}) => {
const {
- services: { notifications },
+ services: {
+ notifications,
+ mlServices: { mlApiServices },
+ },
} = useMlKibana();
const [newSelection, setNewSelection] = useState(selectedIds);
@@ -151,7 +153,7 @@ export const JobSelectorFlyout: FC = ({
async function fetchJobs() {
try {
- const resp = await ml.jobs.jobsWithTimerange(dateFormatTz);
+ const resp = await mlApiServices.jobs.jobsWithTimerange(dateFormatTz);
const normalizedJobs = normalizeTimes(resp.jobs, dateFormatTz, DEFAULT_GANTT_BAR_WIDTH);
const { groups: groupsWithTimerange, groupsMap } = getGroupsFromJobs(normalizedJobs);
setJobs(normalizedJobs);
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx
index 27f8c822d68e3..beafae1ecd2f6 100644
--- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx
+++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx
@@ -9,10 +9,7 @@ import { Subscription } from 'rxjs';
import { EuiSuperDatePicker, OnRefreshProps } from '@elastic/eui';
import { TimeRange, TimeHistoryContract } from 'src/plugins/data/public';
-import {
- mlTimefilterRefresh$,
- mlTimefilterTimeChange$,
-} from '../../../services/timefilter_refresh_service';
+import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service';
import { useUrlState } from '../../../util/url_state';
import { useMlKibana } from '../../../contexts/kibana';
@@ -108,7 +105,6 @@ export const DatePickerWrapper: FC = () => {
timefilter.setTime(newTime);
setTime(newTime);
setRecentlyUsedRanges(getRecentlyUsedRanges());
- mlTimefilterTimeChange$.next({ lastRefresh: Date.now(), timeRange: { start, end } });
}
function updateInterval({
diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts
index 2a156b5716ad4..3bc3b8c2c6dfd 100644
--- a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts
+++ b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts
@@ -13,6 +13,7 @@ import {
import { SecurityPluginSetup } from '../../../../../security/public';
import { LicenseManagementUIPluginSetup } from '../../../../../license_management/public';
import { SharePluginStart } from '../../../../../../../src/plugins/share/public';
+import { MlServicesContext } from '../../app';
interface StartPlugins {
data: DataPublicPluginStart;
@@ -20,7 +21,8 @@ interface StartPlugins {
licenseManagement?: LicenseManagementUIPluginSetup;
share: SharePluginStart;
}
-export type StartServices = CoreStart & StartPlugins & { kibanaVersion: string };
+export type StartServices = CoreStart &
+ StartPlugins & { kibanaVersion: string } & MlServicesContext;
// eslint-disable-next-line react-hooks/rules-of-hooks
export const useMlKibana = () => useKibana();
export type MlKibanaReactContextValue = KibanaReactContextValue;
diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts
index 07d5a153664b7..95ef5e5b2938c 100644
--- a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts
+++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts
@@ -7,6 +7,7 @@
import React from 'react';
import { IndexPattern, IndexPatternsContract } from '../../../../../../../src/plugins/data/public';
import { SavedSearchSavedObject } from '../../../../common/types/kibana';
+import { MlServicesContext } from '../../app';
export interface MlContextValue {
combinedQuery: any;
@@ -34,4 +35,4 @@ export type SavedSearchQuery = object;
// Multiple custom hooks can be created to access subsets of
// the overall context value if necessary too,
// see useCurrentIndexPattern() for example.
-export const MlContext = React.createContext>({});
+export const MlContext = React.createContext>({});
diff --git a/x-pack/plugins/ml/public/application/explorer/_explorer.scss b/x-pack/plugins/ml/public/application/explorer/_explorer.scss
index 7e5f354bbb402..63c471e66c49a 100644
--- a/x-pack/plugins/ml/public/application/explorer/_explorer.scss
+++ b/x-pack/plugins/ml/public/application/explorer/_explorer.scss
@@ -1,3 +1,5 @@
+$borderRadius: $euiBorderRadius / 2;
+
.ml-swimlane-selector {
visibility: hidden;
}
@@ -104,10 +106,9 @@
// SASSTODO: This entire selector needs to be rewritten.
// It looks extremely brittle with very specific sizing units
- .ml-explorer-swimlane {
+ .mlExplorerSwimlane {
user-select: none;
padding: 0;
- margin-bottom: $euiSizeS;
line.gridLine {
stroke: $euiBorderColor;
@@ -218,17 +219,20 @@
div.lane {
height: 30px;
border-bottom: 0px;
- border-radius: 2px;
- margin-top: -1px;
+ border-radius: $borderRadius;
white-space: nowrap;
+ &:not(:first-child) {
+ margin-top: -1px;
+ }
+
div.lane-label {
display: inline-block;
- font-size: 13px;
+ font-size: $euiFontSizeXS;
height: 30px;
text-align: right;
vertical-align: middle;
- border-radius: 2px;
+ border-radius: $borderRadius;
padding-right: 5px;
margin-right: 5px;
border: 1px solid transparent;
@@ -261,7 +265,7 @@
.sl-cell-inner-dragselect {
height: 26px;
margin: 1px;
- border-radius: 2px;
+ border-radius: $borderRadius;
text-align: center;
}
@@ -293,7 +297,7 @@
.sl-cell-inner,
.sl-cell-inner-dragselect {
border: 2px solid $euiColorDarkShade;
- border-radius: 2px;
+ border-radius: $borderRadius;
opacity: 1;
}
}
diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts
index 590a69283a819..095b42ffac5b7 100644
--- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts
+++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts
@@ -11,6 +11,7 @@ import useObservable from 'react-use/lib/useObservable';
import { forkJoin, of, Observable, Subject } from 'rxjs';
import { mergeMap, switchMap, tap } from 'rxjs/operators';
+import { useCallback, useMemo } from 'react';
import { anomalyDataChange } from '../explorer_charts/explorer_charts_container_service';
import { explorerService } from '../explorer_dashboard_service';
import {
@@ -22,15 +23,17 @@ import {
loadAnomaliesTableData,
loadDataForCharts,
loadFilteredTopInfluencers,
- loadOverallData,
loadTopInfluencers,
- loadViewBySwimlane,
- loadViewByTopFieldValuesForSelectedTime,
AppStateSelectedCells,
ExplorerJob,
TimeRangeBounds,
} from '../explorer_utils';
import { ExplorerState } from '../reducers';
+import { useMlKibana, useTimefilter } from '../../contexts/kibana';
+import { AnomalyTimelineService } from '../../services/anomaly_timeline_service';
+import { mlResultsServiceProvider } from '../../services/results_service';
+import { isViewBySwimLaneData } from '../swimlane_container';
+import { ANOMALY_SWIM_LANE_HARD_LIMIT } from '../explorer_constants';
// Memoize the data fetching methods.
// wrapWithLastRefreshArg() wraps any given function and preprends a `lastRefresh` argument
@@ -39,13 +42,13 @@ import { ExplorerState } from '../reducers';
// about this parameter. The generic type T retains and returns the type information of
// the original function.
const memoizeIsEqual = (newArgs: any[], lastArgs: any[]) => isEqual(newArgs, lastArgs);
-const wrapWithLastRefreshArg = any>(func: T) => {
+const wrapWithLastRefreshArg = any>(func: T, context: any = null) => {
return function (lastRefresh: number, ...args: Parameters): ReturnType {
- return func.apply(null, args);
+ return func.apply(context, args);
};
};
-const memoize = any>(func: T) => {
- return memoizeOne(wrapWithLastRefreshArg(func), memoizeIsEqual);
+const memoize = any>(func: T, context?: any) => {
+ return memoizeOne(wrapWithLastRefreshArg(func, context), memoizeIsEqual);
};
const memoizedAnomalyDataChange = memoize(anomalyDataChange);
@@ -56,9 +59,7 @@ const memoizedLoadDataForCharts = memoize(loadDataForC
const memoizedLoadFilteredTopInfluencers = memoize(
loadFilteredTopInfluencers
);
-const memoizedLoadOverallData = memoize(loadOverallData);
const memoizedLoadTopInfluencers = memoize(loadTopInfluencers);
-const memoizedLoadViewBySwimlane = memoize(loadViewBySwimlane);
const memoizedLoadAnomaliesTableData = memoize(loadAnomaliesTableData);
export interface LoadExplorerDataConfig {
@@ -73,6 +74,9 @@ export interface LoadExplorerDataConfig {
tableInterval: string;
tableSeverity: number;
viewBySwimlaneFieldName: string;
+ viewByFromPage: number;
+ viewByPerPage: number;
+ swimlaneContainerWidth: number;
}
export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfig => {
@@ -87,183 +91,213 @@ export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfi
/**
* Fetches the data necessary for the Anomaly Explorer using observables.
- *
- * @param config LoadExplorerDataConfig
- *
- * @return Partial
*/
-function loadExplorerData(config: LoadExplorerDataConfig): Observable> {
- if (!isLoadExplorerDataConfig(config)) {
- return of({});
- }
-
- const {
- bounds,
- lastRefresh,
- influencersFilterQuery,
- noInfluencersConfigured,
- selectedCells,
- selectedJobs,
- swimlaneBucketInterval,
- swimlaneLimit,
- tableInterval,
- tableSeverity,
- viewBySwimlaneFieldName,
- } = config;
-
- const selectionInfluencers = getSelectionInfluencers(selectedCells, viewBySwimlaneFieldName);
- const jobIds = getSelectionJobIds(selectedCells, selectedJobs);
- const timerange = getSelectionTimeRange(
- selectedCells,
- swimlaneBucketInterval.asSeconds(),
- bounds
+const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService) => {
+ const memoizedLoadOverallData = memoize(
+ anomalyTimelineService.loadOverallData,
+ anomalyTimelineService
);
+ const memoizedLoadViewBySwimlane = memoize(
+ anomalyTimelineService.loadViewBySwimlane,
+ anomalyTimelineService
+ );
+ return (config: LoadExplorerDataConfig): Observable> => {
+ if (!isLoadExplorerDataConfig(config)) {
+ return of({});
+ }
- const dateFormatTz = getDateFormatTz();
-
- // First get the data where we have all necessary args at hand using forkJoin:
- // annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues
- return forkJoin({
- annotationsData: memoizedLoadAnnotationsTableData(
+ const {
+ bounds,
lastRefresh,
+ influencersFilterQuery,
+ noInfluencersConfigured,
selectedCells,
selectedJobs,
+ swimlaneBucketInterval,
+ swimlaneLimit,
+ tableInterval,
+ tableSeverity,
+ viewBySwimlaneFieldName,
+ swimlaneContainerWidth,
+ viewByFromPage,
+ viewByPerPage,
+ } = config;
+
+ const selectionInfluencers = getSelectionInfluencers(selectedCells, viewBySwimlaneFieldName);
+ const jobIds = getSelectionJobIds(selectedCells, selectedJobs);
+ const timerange = getSelectionTimeRange(
+ selectedCells,
swimlaneBucketInterval.asSeconds(),
bounds
- ),
- anomalyChartRecords: memoizedLoadDataForCharts(
- lastRefresh,
- jobIds,
- timerange.earliestMs,
- timerange.latestMs,
- selectionInfluencers,
- selectedCells,
- influencersFilterQuery
- ),
- influencers:
- selectionInfluencers.length === 0
- ? memoizedLoadTopInfluencers(
+ );
+
+ const dateFormatTz = getDateFormatTz();
+
+ // First get the data where we have all necessary args at hand using forkJoin:
+ // annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues
+ return forkJoin({
+ annotationsData: memoizedLoadAnnotationsTableData(
+ lastRefresh,
+ selectedCells,
+ selectedJobs,
+ swimlaneBucketInterval.asSeconds(),
+ bounds
+ ),
+ anomalyChartRecords: memoizedLoadDataForCharts(
+ lastRefresh,
+ jobIds,
+ timerange.earliestMs,
+ timerange.latestMs,
+ selectionInfluencers,
+ selectedCells,
+ influencersFilterQuery
+ ),
+ influencers:
+ selectionInfluencers.length === 0
+ ? memoizedLoadTopInfluencers(
+ lastRefresh,
+ jobIds,
+ timerange.earliestMs,
+ timerange.latestMs,
+ [],
+ noInfluencersConfigured,
+ influencersFilterQuery
+ )
+ : Promise.resolve({}),
+ overallState: memoizedLoadOverallData(lastRefresh, selectedJobs, swimlaneContainerWidth),
+ tableData: memoizedLoadAnomaliesTableData(
+ lastRefresh,
+ selectedCells,
+ selectedJobs,
+ dateFormatTz,
+ swimlaneBucketInterval.asSeconds(),
+ bounds,
+ viewBySwimlaneFieldName,
+ tableInterval,
+ tableSeverity,
+ influencersFilterQuery
+ ),
+ topFieldValues:
+ selectedCells !== undefined && selectedCells.showTopFieldValues === true
+ ? anomalyTimelineService.loadViewByTopFieldValuesForSelectedTime(
+ timerange.earliestMs,
+ timerange.latestMs,
+ selectedJobs,
+ viewBySwimlaneFieldName,
+ swimlaneLimit,
+ viewByPerPage,
+ viewByFromPage,
+ swimlaneContainerWidth
+ )
+ : Promise.resolve([]),
+ }).pipe(
+ // Trigger a side-effect action to reset view-by swimlane,
+ // show the view-by loading indicator
+ // and pass on the data we already fetched.
+ tap(explorerService.setViewBySwimlaneLoading),
+ // Trigger a side-effect to update the charts.
+ tap(({ anomalyChartRecords }) => {
+ if (selectedCells !== undefined && Array.isArray(anomalyChartRecords)) {
+ memoizedAnomalyDataChange(
lastRefresh,
- jobIds,
+ anomalyChartRecords,
timerange.earliestMs,
timerange.latestMs,
+ tableSeverity
+ );
+ } else {
+ memoizedAnomalyDataChange(
+ lastRefresh,
[],
- noInfluencersConfigured,
- influencersFilterQuery
- )
- : Promise.resolve({}),
- overallState: memoizedLoadOverallData(
- lastRefresh,
- selectedJobs,
- swimlaneBucketInterval,
- bounds
- ),
- tableData: memoizedLoadAnomaliesTableData(
- lastRefresh,
- selectedCells,
- selectedJobs,
- dateFormatTz,
- swimlaneBucketInterval.asSeconds(),
- bounds,
- viewBySwimlaneFieldName,
- tableInterval,
- tableSeverity,
- influencersFilterQuery
- ),
- topFieldValues:
- selectedCells !== undefined && selectedCells.showTopFieldValues === true
- ? loadViewByTopFieldValuesForSelectedTime(
timerange.earliestMs,
timerange.latestMs,
- selectedJobs,
- viewBySwimlaneFieldName,
- swimlaneLimit,
- noInfluencersConfigured
- )
- : Promise.resolve([]),
- }).pipe(
- // Trigger a side-effect action to reset view-by swimlane,
- // show the view-by loading indicator
- // and pass on the data we already fetched.
- tap(explorerService.setViewBySwimlaneLoading),
- // Trigger a side-effect to update the charts.
- tap(({ anomalyChartRecords }) => {
- if (selectedCells !== undefined && Array.isArray(anomalyChartRecords)) {
- memoizedAnomalyDataChange(
- lastRefresh,
- anomalyChartRecords,
- timerange.earliestMs,
- timerange.latestMs,
- tableSeverity
- );
- } else {
- memoizedAnomalyDataChange(
- lastRefresh,
- [],
- timerange.earliestMs,
- timerange.latestMs,
- tableSeverity
- );
- }
- }),
- // Load view-by swimlane data and filtered top influencers.
- // mergeMap is used to have access to the already fetched data and act on it in arg #1.
- // In arg #2 of mergeMap we combine the data and pass it on in the action format
- // which can be consumed by explorerReducer() later on.
- mergeMap(
- ({ anomalyChartRecords, influencers, overallState, topFieldValues }) =>
- forkJoin({
- influencers:
- (selectionInfluencers.length > 0 || influencersFilterQuery !== undefined) &&
- anomalyChartRecords !== undefined &&
- anomalyChartRecords.length > 0
- ? memoizedLoadFilteredTopInfluencers(
- lastRefresh,
- jobIds,
- timerange.earliestMs,
- timerange.latestMs,
- anomalyChartRecords,
- selectionInfluencers,
- noInfluencersConfigured,
- influencersFilterQuery
- )
- : Promise.resolve(influencers),
- viewBySwimlaneState: memoizedLoadViewBySwimlane(
- lastRefresh,
- topFieldValues,
- {
- earliest: overallState.overallSwimlaneData.earliest,
- latest: overallState.overallSwimlaneData.latest,
- },
- selectedJobs,
- viewBySwimlaneFieldName,
- swimlaneLimit,
- influencersFilterQuery,
- noInfluencersConfigured
- ),
- }),
- (
- { annotationsData, overallState, tableData },
- { influencers, viewBySwimlaneState }
- ): Partial => {
- return {
- annotationsData,
- influencers,
- ...overallState,
- ...viewBySwimlaneState,
- tableData,
- };
- }
- )
- );
-}
-
-const loadExplorerData$ = new Subject();
-const explorerData$ = loadExplorerData$.pipe(
- switchMap((config: LoadExplorerDataConfig) => loadExplorerData(config))
-);
-
+ tableSeverity
+ );
+ }
+ }),
+ // Load view-by swimlane data and filtered top influencers.
+ // mergeMap is used to have access to the already fetched data and act on it in arg #1.
+ // In arg #2 of mergeMap we combine the data and pass it on in the action format
+ // which can be consumed by explorerReducer() later on.
+ mergeMap(
+ ({ anomalyChartRecords, influencers, overallState, topFieldValues }) =>
+ forkJoin({
+ influencers:
+ (selectionInfluencers.length > 0 || influencersFilterQuery !== undefined) &&
+ anomalyChartRecords !== undefined &&
+ anomalyChartRecords.length > 0
+ ? memoizedLoadFilteredTopInfluencers(
+ lastRefresh,
+ jobIds,
+ timerange.earliestMs,
+ timerange.latestMs,
+ anomalyChartRecords,
+ selectionInfluencers,
+ noInfluencersConfigured,
+ influencersFilterQuery
+ )
+ : Promise.resolve(influencers),
+ viewBySwimlaneState: memoizedLoadViewBySwimlane(
+ lastRefresh,
+ topFieldValues,
+ {
+ earliest: overallState.earliest,
+ latest: overallState.latest,
+ },
+ selectedJobs,
+ viewBySwimlaneFieldName,
+ ANOMALY_SWIM_LANE_HARD_LIMIT,
+ viewByPerPage,
+ viewByFromPage,
+ swimlaneContainerWidth,
+ influencersFilterQuery
+ ),
+ }),
+ (
+ { annotationsData, overallState, tableData },
+ { influencers, viewBySwimlaneState }
+ ): Partial => {
+ return {
+ annotationsData,
+ influencers,
+ loading: false,
+ viewBySwimlaneDataLoading: false,
+ overallSwimlaneData: overallState,
+ viewBySwimlaneData: viewBySwimlaneState,
+ tableData,
+ swimlaneLimit: isViewBySwimLaneData(viewBySwimlaneState)
+ ? viewBySwimlaneState.cardinality
+ : undefined,
+ };
+ }
+ )
+ );
+ };
+};
export const useExplorerData = (): [Partial | undefined, (d: any) => void] => {
+ const timefilter = useTimefilter();
+
+ const {
+ services: {
+ mlServices: { mlApiServices },
+ uiSettings,
+ },
+ } = useMlKibana();
+ const loadExplorerData = useMemo(() => {
+ const service = new AnomalyTimelineService(
+ timefilter,
+ uiSettings,
+ mlResultsServiceProvider(mlApiServices)
+ );
+ return loadExplorerDataProvider(service);
+ }, []);
+ const loadExplorerData$ = useMemo(() => new Subject(), []);
+ const explorerData$ = useMemo(() => loadExplorerData$.pipe(switchMap(loadExplorerData)), []);
const explorerData = useObservable(explorerData$);
- return [explorerData, (c) => loadExplorerData$.next(c)];
+
+ const update = useCallback((c) => {
+ loadExplorerData$.next(c);
+ }, []);
+
+ return [explorerData, update];
};
diff --git a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx
index 16e2fb47a209d..3ad749c9d0631 100644
--- a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx
@@ -52,7 +52,6 @@ function getDefaultEmbeddablepaPanelConfig(jobIds: JobId[]) {
interface AddToDashboardControlProps {
jobIds: JobId[];
viewBy: string;
- limit: number;
onClose: (callback?: () => Promise) => void;
}
@@ -63,7 +62,6 @@ export const AddToDashboardControl: FC = ({
onClose,
jobIds,
viewBy,
- limit,
}) => {
const {
notifications: { toasts },
@@ -141,7 +139,6 @@ export const AddToDashboardControl: FC = ({
jobIds,
swimlaneType,
viewBy,
- limit,
},
};
}
@@ -206,8 +203,8 @@ export const AddToDashboardControl: FC = ({
{
id: SWIMLANE_TYPE.VIEW_BY,
label: i18n.translate('xpack.ml.explorer.viewByFieldLabel', {
- defaultMessage: 'View by {viewByField}, up to {limit} rows',
- values: { viewByField: viewBy, limit },
+ defaultMessage: 'View by {viewByField}',
+ values: { viewByField: viewBy },
}),
},
];
diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx
index b4d32e2af64b8..e00e2e1e1e2eb 100644
--- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx
@@ -22,12 +22,11 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { DRAG_SELECT_ACTION, VIEW_BY_JOB_LABEL } from './explorer_constants';
+import { DRAG_SELECT_ACTION, SWIMLANE_TYPE, VIEW_BY_JOB_LABEL } from './explorer_constants';
import { AddToDashboardControl } from './add_to_dashboard_control';
import { useMlKibana } from '../contexts/kibana';
import { TimeBuckets } from '../util/time_buckets';
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
-import { SelectLimit } from './select_limit';
import {
ALLOW_CELL_RANGE_SELECTION,
dragSelect$,
@@ -36,9 +35,9 @@ import {
import { ExplorerState } from './reducers/explorer_reducer';
import { hasMatchingPoints } from './has_matching_points';
import { ExplorerNoInfluencersFound } from './components/explorer_no_influencers_found/explorer_no_influencers_found';
-import { LoadingIndicator } from '../components/loading_indicator';
import { SwimlaneContainer } from './swimlane_container';
-import { OverallSwimlaneData } from './explorer_utils';
+import { OverallSwimlaneData, ViewBySwimLaneData } from './explorer_utils';
+import { NoOverallData } from './components/no_overall_data';
function mapSwimlaneOptionsToEuiOptions(options: string[]) {
return options.map((option) => ({
@@ -132,8 +131,11 @@ export const AnomalyTimeline: FC = React.memo(
viewBySwimlaneDataLoading,
viewBySwimlaneFieldName,
viewBySwimlaneOptions,
- swimlaneLimit,
selectedJobs,
+ viewByFromPage,
+ viewByPerPage,
+ swimlaneLimit,
+ loading,
} = explorerState;
const setSwimlaneSelectActive = useCallback((active: boolean) => {
@@ -159,25 +161,18 @@ export const AnomalyTimeline: FC = React.memo(
}, []);
// Listener for click events in the swimlane to load corresponding anomaly data.
- const swimlaneCellClick = useCallback((selectedCellsUpdate: any) => {
- // If selectedCells is an empty object we clear any existing selection,
- // otherwise we save the new selection in AppState and update the Explorer.
- if (Object.keys(selectedCellsUpdate).length === 0) {
- setSelectedCells();
- } else {
- setSelectedCells(selectedCellsUpdate);
- }
- }, []);
-
- const showOverallSwimlane =
- overallSwimlaneData !== null &&
- overallSwimlaneData.laneLabels &&
- overallSwimlaneData.laneLabels.length > 0;
-
- const showViewBySwimlane =
- viewBySwimlaneData !== null &&
- viewBySwimlaneData.laneLabels &&
- viewBySwimlaneData.laneLabels.length > 0;
+ const swimlaneCellClick = useCallback(
+ (selectedCellsUpdate: any) => {
+ // If selectedCells is an empty object we clear any existing selection,
+ // otherwise we save the new selection in AppState and update the Explorer.
+ if (Object.keys(selectedCellsUpdate).length === 0) {
+ setSelectedCells();
+ } else {
+ setSelectedCells(selectedCellsUpdate);
+ }
+ },
+ [setSelectedCells]
+ );
const menuItems = useMemo(() => {
const items = [];
@@ -235,21 +230,6 @@ export const AnomalyTimeline: FC = React.memo(
/>
-
-
-
-
- }
- display={'columnCompressed'}
- >
-
-
-
{viewByLoadedForTimeFormatted && (
@@ -305,68 +285,84 @@ export const AnomalyTimeline: FC
= React.memo(
- {showOverallSwimlane && (
- explorerService.setSwimlaneContainerWidth(width)}
- />
- )}
+ explorerService.setSwimlaneContainerWidth(width)}
+ isLoading={loading}
+ noDataWarning={}
+ />
+
+
{viewBySwimlaneOptions.length > 0 && (
<>
- {showViewBySwimlane && (
- <>
-
-
-
+
+ explorerService.setSwimlaneContainerWidth(width)}
+ fromPage={viewByFromPage}
+ perPage={viewByPerPage}
+ swimlaneLimit={swimlaneLimit}
+ onPaginationChange={({ perPage: perPageUpdate, fromPage: fromPageUpdate }) => {
+ if (perPageUpdate) {
+ explorerService.setViewByPerPage(perPageUpdate);
}
- timeBuckets={timeBuckets}
- swimlaneCellClick={swimlaneCellClick}
- swimlaneData={viewBySwimlaneData as OverallSwimlaneData}
- swimlaneType={'viewBy'}
- selection={selectedCells}
- swimlaneRenderDoneListener={swimlaneRenderDoneListener}
- onResize={(width) => explorerService.setSwimlaneContainerWidth(width)}
- />
-
- >
- )}
-
- {viewBySwimlaneDataLoading && }
-
- {!showViewBySwimlane &&
- !viewBySwimlaneDataLoading &&
- typeof viewBySwimlaneFieldName === 'string' && (
-
+ ) : (
+
+ )
+ ) : null
+ }
/>
- )}
+
+ >
>
)}
@@ -380,7 +376,6 @@ export const AnomalyTimeline: FC = React.memo(
}}
jobIds={selectedJobs.map(({ id }) => id)}
viewBy={viewBySwimlaneFieldName!}
- limit={swimlaneLimit}
/>
)}
>
diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/__snapshots__/explorer_no_influencers_found.test.js.snap b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/__snapshots__/explorer_no_influencers_found.test.js.snap
index 3ba4ebb2acdea..d3190d2ac1dad 100644
--- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/__snapshots__/explorer_no_influencers_found.test.js.snap
+++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/__snapshots__/explorer_no_influencers_found.test.js.snap
@@ -1,20 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ExplorerNoInfluencersFound snapshot 1`] = `
-
-
-
+
`;
diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx
index 639c0f7b78504..24def01108584 100644
--- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx
+++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx
@@ -7,7 +7,6 @@
import React, { FC } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiEmptyPrompt } from '@elastic/eui';
/*
* React component for rendering EuiEmptyPrompt when no influencers were found.
@@ -15,26 +14,17 @@ import { EuiEmptyPrompt } from '@elastic/eui';
export const ExplorerNoInfluencersFound: FC<{
viewBySwimlaneFieldName: string;
showFilterMessage?: boolean;
-}> = ({ viewBySwimlaneFieldName, showFilterMessage = false }) => (
-
- {showFilterMessage === false && (
-
- )}
- {showFilterMessage === true && (
-
- )}
-
- }
- />
-);
+}> = ({ viewBySwimlaneFieldName, showFilterMessage = false }) =>
+ showFilterMessage === false ? (
+
+ ) : (
+
+ );
diff --git a/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx b/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx
new file mode 100644
index 0000000000000..e73aac66a0d9f
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx
@@ -0,0 +1,17 @@
+/*
+ * 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 React, { FC } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+export const NoOverallData: FC = () => {
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js
index 71c96840d1b57..df4cea0c07987 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer.js
@@ -12,8 +12,6 @@ import PropTypes from 'prop-types';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
import {
EuiFlexGroup,
@@ -27,6 +25,7 @@ import {
EuiPageHeaderSection,
EuiSpacer,
EuiTitle,
+ EuiLoadingContent,
} from '@elastic/eui';
import { AnnotationFlyout } from '../components/annotations/annotation_flyout';
@@ -36,12 +35,10 @@ import { DatePickerWrapper } from '../components/navigation_menu/date_picker_wra
import { InfluencersList } from '../components/influencers_list';
import { explorerService } from './explorer_dashboard_service';
import { AnomalyResultsViewSelector } from '../components/anomaly_results_view_selector';
-import { LoadingIndicator } from '../components/loading_indicator/loading_indicator';
import { NavigationMenu } from '../components/navigation_menu';
import { CheckboxShowCharts } from '../components/controls/checkbox_showcharts';
import { JobSelector } from '../components/job_selector';
import { SelectInterval } from '../components/controls/select_interval/select_interval';
-import { limit$ } from './select_limit/select_limit';
import { SelectSeverity } from '../components/controls/select_severity/select_severity';
import {
ExplorerQueryBar,
@@ -142,19 +139,6 @@ export class Explorer extends React.Component {
state = { filterIconTriggeredQuery: undefined, language: DEFAULT_QUERY_LANG };
- _unsubscribeAll = new Subject();
-
- componentDidMount() {
- limit$.pipe(takeUntil(this._unsubscribeAll)).subscribe(explorerService.setSwimlaneLimit);
- }
-
- componentWillUnmount() {
- this._unsubscribeAll.next();
- this._unsubscribeAll.complete();
- }
-
- viewByChangeHandler = (e) => explorerService.setViewBySwimlaneFieldName(e.target.value);
-
// Escape regular parens from fieldName as that portion of the query is not wrapped in double quotes
// and will cause a syntax error when called with getKqlQueryValues
applyFilter = (fieldName, fieldValue, action) => {
@@ -240,29 +224,7 @@ export class Explorer extends React.Component {
const noJobsFound = selectedJobs === null || selectedJobs.length === 0;
const hasResults = overallSwimlaneData.points && overallSwimlaneData.points.length > 0;
- if (loading === true) {
- return (
-
-
-
- );
- }
-
- if (noJobsFound) {
+ if (noJobsFound && !loading) {
return (
@@ -270,7 +232,7 @@ export class Explorer extends React.Component {
);
}
- if (noJobsFound && hasResults === false) {
+ if (noJobsFound && hasResults === false && !loading) {
return (
@@ -320,7 +282,11 @@ export class Explorer extends React.Component {
/>
-
+ {loading ? (
+
+ ) : (
+
+ )}
)}
@@ -352,59 +318,59 @@ export class Explorer extends React.Component {
>
)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
- {chartsData.seriesToPlot.length > 0 && selectedCells !== undefined && (
-
-
-
-
-
- )}
-
-
-
-
-
- {showCharts && }
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {chartsData.seriesToPlot.length > 0 && selectedCells !== undefined && (
+
+
+
+
+
+ )}
+
+
+
+ {showCharts && }
+
+
+ >
+ )}
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts
index d1adf8c7ad744..21e13cb029d69 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts
@@ -27,9 +27,10 @@ export const EXPLORER_ACTION = {
SET_INFLUENCER_FILTER_SETTINGS: 'setInfluencerFilterSettings',
SET_SELECTED_CELLS: 'setSelectedCells',
SET_SWIMLANE_CONTAINER_WIDTH: 'setSwimlaneContainerWidth',
- SET_SWIMLANE_LIMIT: 'setSwimlaneLimit',
SET_VIEW_BY_SWIMLANE_FIELD_NAME: 'setViewBySwimlaneFieldName',
SET_VIEW_BY_SWIMLANE_LOADING: 'setViewBySwimlaneLoading',
+ SET_VIEW_BY_PER_PAGE: 'setViewByPerPage',
+ SET_VIEW_BY_FROM_PAGE: 'setViewByFromPage',
};
export const FILTER_ACTION = {
@@ -51,9 +52,23 @@ export const CHART_TYPE = {
};
export const MAX_CATEGORY_EXAMPLES = 10;
+
+/**
+ * Maximum amount of top influencer to fetch.
+ */
export const MAX_INFLUENCER_FIELD_VALUES = 10;
export const MAX_INFLUENCER_FIELD_NAMES = 50;
export const VIEW_BY_JOB_LABEL = i18n.translate('xpack.ml.explorer.jobIdLabel', {
defaultMessage: 'job ID',
});
+/**
+ * Hard limitation for the size of terms
+ * aggregations on influencers values.
+ */
+export const ANOMALY_SWIM_LANE_HARD_LIMIT = 1000;
+
+/**
+ * Default page size fot the anomaly swim lane.
+ */
+export const SWIM_LANE_DEFAULT_PAGE_SIZE = 10;
diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts
index 30ab918983a77..1429bf0858361 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts
@@ -12,7 +12,7 @@
import { isEqual } from 'lodash';
import { from, isObservable, Observable, Subject } from 'rxjs';
-import { distinctUntilChanged, flatMap, map, scan } from 'rxjs/operators';
+import { distinctUntilChanged, flatMap, map, scan, shareReplay } from 'rxjs/operators';
import { DeepPartial } from '../../../common/types/common';
@@ -49,7 +49,9 @@ const explorerFilteredAction$ = explorerAction$.pipe(
// applies action and returns state
const explorerState$: Observable