[Lens] Add feature to ignore global filters at layer level (#159248)

## Summary

Fixes #143493

* Add the switch control in Layer Settings
  * [x] Make sure it does not duplicate on Annotation Layer Settings 
  * [x] Data Layers
  * [x] Reference line layers
  * [x] Extended dataView picker to support multiple icons
    * [x] Added unit tests 
  * [x] Functional tests

<img width="351" alt="Screenshot 2023-06-07 at 15 28 19"
src="00dc5523-0bec-4e9c-b1d0-4d36804b29f9">
<img width="340" alt="Screenshot 2023-06-07 at 15 31 31"
src="d36ca147-5d8c-4123-9be3-2932844cbd15">
<img width="331" alt="Screenshot 2023-06-07 at 15 28 25"
src="c7d4f166-b8ab-4439-a83c-debf82b913ad">
<img width="324" alt="Screenshot 2023-06-07 at 15 27 59"
src="3738a7e0-6e49-4e22-b857-965a953b4b84">
<img width="323" alt="Screenshot 2023-06-07 at 15 27 53"
src="5965bf1c-0e25-4c0e-b54f-fa315157fd44">


*  Create `IgnoreGlobalFilter` shared component folder
   * [x] Layer setting control component
   * [x] Info badge component
   
 * Extends `esaggs_fn` to support the flag
   * [x] Avoid to pass the filter to the handler if set
   * [x] Add unit tests

* Notification badges
* [x] Extends the badge component in Embeddable to support grouped
messages
    * [x] Added unit tests
  
<img width="750" alt="Screenshot 2023-06-07 at 15 31 39"
src="01bf8203-9133-4429-9b79-17ec67613c7e">
<img width="828" alt="Screenshot 2023-06-07 at 15 30 57"
src="9acb78f2-d061-4225-a4af-b3a66e7454fc">
<img width="756" alt="Screenshot 2023-06-07 at 15 27 43"
src="b9f79aed-7c02-4060-9c0f-61f438dc031d">


* Add support for Open in Lens
  * [x] Add unit tests for each converter
  * [x] Functional tests


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
This commit is contained in:
Marco Liberati 2023-06-09 17:55:47 +02:00 committed by GitHub
parent b6a2611361
commit e325d4102b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 1266 additions and 263 deletions

View file

@ -34,6 +34,7 @@ interface Arguments {
timeFields?: string[];
probability?: number;
samplerSeed?: number;
ignoreGlobalFilters?: boolean;
}
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<
@ -111,6 +112,12 @@ export const getEsaggsMeta: () => Omit<EsaggsExpressionFunctionDefinition, 'fn'>
'The seed to generate the random sampling of documents. Uses random sampler.',
}),
},
ignoreGlobalFilters: {
types: ['boolean'],
help: i18n.translate('data.search.functions.esaggs.ignoreGlobalFilters.help', {
defaultMessage: 'Whether to ignore or use global query and filters',
}),
},
},
});

View file

@ -153,4 +153,28 @@ describe('esaggs expression function - public', () => {
})
);
});
test('does not forward filters and query if ignoreGlobalFilters is enabled', async () => {
const input = {
type: 'kibana_context' as 'kibana_context',
filters: [{ $state: {}, meta: {}, query: {} }],
query: {
query: 'hiya',
language: 'painless',
},
timeRange: { from: 'a', to: 'b' },
} as KibanaContext;
await definition()
.fn(input, { ...args, ignoreGlobalFilters: true }, mockHandlers)
.toPromise();
expect(handleEsaggsRequest).toHaveBeenCalledWith(
expect.objectContaining({
filters: undefined,
query: undefined,
timeRange: input.timeRange,
})
);
});
});

View file

@ -66,10 +66,10 @@ export function getFunctionDefinition({
return handleEsaggsRequest({
abortSignal,
aggs: aggConfigs,
filters: get(input, 'filters', undefined),
filters: args.ignoreGlobalFilters ? undefined : get(input, 'filters', undefined),
indexPattern,
inspectorAdapters,
query: get(input, 'query', undefined) as any,
query: args.ignoreGlobalFilters ? undefined : (get(input, 'query', undefined) as any),
searchSessionId: getSearchSessionId(),
searchSourceService: searchSource,
timeFields: args.timeFields,

View file

@ -98,6 +98,7 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim
layerId,
columns: columns.map(excludeMetaFromColumn),
columnOrder: [],
ignoreGlobalFilters: false,
},
],
configuration: getConfiguration(

View file

@ -95,6 +95,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time
layerId,
columns: columns.map(excludeMetaFromColumn),
columnOrder: [],
ignoreGlobalFilters: false,
},
],
configuration: getConfiguration(

View file

@ -89,6 +89,7 @@ export const convertToLens: ConvertHeatmapToLensVisualization = async (vis, time
layerId,
columns: layerConfig.columns.map(excludeMetaFromColumn),
columnOrder: [],
ignoreGlobalFilters: false,
},
],
configuration,

View file

@ -85,6 +85,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti
layerId,
columns: layerConfig.columns.map(excludeMetaFromColumn),
columnOrder: [],
ignoreGlobalFilters: false,
},
],
configuration: getConfiguration(

View file

@ -73,6 +73,7 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt
layerId,
columns: layerConfig.columns.map(excludeMetaFromColumn),
columnOrder: [],
ignoreGlobalFilters: false,
},
],
configuration: getConfiguration(layerId, vis, layerConfig),

View file

@ -99,6 +99,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi
layerId,
columns: layerConfig.columns.map(excludeMetaFromColumn),
columnOrder: [],
ignoreGlobalFilters: false,
},
],
configuration: getConfiguration(layerId, vis.params, layerConfig),

View file

@ -23,7 +23,7 @@ export function YesNo({
name,
value,
disabled,
'data-test-subj': dataTestSubj,
'data-test-subj': dataTestSubj = name,
onChange,
}: YesNoProps) {
const handleChange = useCallback(

View file

@ -204,4 +204,60 @@ describe('convertToLens', () => {
expect(result).toBeDefined();
expect(result?.type).toBe('lnsMetric');
});
test('should carry the ignoreGlobalFilters flag if set on the panel', async () => {
mockGetMetricsColumns.mockReturnValue([metricColumn]);
mockGetSeriesAgg.mockReturnValue({ metrics: [metric] });
mockGetConfigurationForGauge.mockReturnValue({});
const result = await convertToLens(
{
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
],
ignore_global_filter: 1,
}),
} as Vis<Panel>,
undefined,
true
);
expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true);
});
test('should carry the ignoreGlobalFilters flag if set on the series', async () => {
mockGetMetricsColumns.mockReturnValue([metricColumn]);
mockGetSeriesAgg.mockReturnValue({ metrics: [metric] });
mockGetConfigurationForGauge.mockReturnValue({});
const result = await convertToLens(
{
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
],
ignore_global_filter: 0,
}),
} as Vis<Panel>,
undefined,
true
);
expect(result?.layers[0].ignoreGlobalFilters).toBe(true);
});
});

View file

@ -101,6 +101,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (
const extendedLayer: ExtendedLayer = {
indexPatternId,
ignoreGlobalFilters: Boolean(model.ignore_global_filter || series.ignore_global_filter),
layerId: uuidv4(),
columns: [...metricsColumns, ...(bucket ? [bucket] : [])],
columnOrder: [],

View file

@ -32,6 +32,7 @@ export const createSeries = (partialSeries?: Partial<Series>): Series => ({
series_index_pattern: { id: 'test' },
series_max_bars: 0,
steps: 0,
ignore_global_filter: 0,
...partialSeries,
});
@ -57,5 +58,6 @@ export const createPanel = (parialPanel?: Partial<Panel>): Panel => ({
show_grid: 0,
show_legend: 0,
type: PANEL_TYPES.TIMESERIES,
ignore_global_filter: 0,
...parialPanel,
});

View file

@ -18,6 +18,17 @@ jest.mock('../palette', () => ({
getPalette: jest.fn(() => mockGetPalette()),
}));
function createEmptyLensLayer(partialLayer: Partial<Layer>): Layer {
return {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
ignoreGlobalFilters: false,
layerId: '0',
...partialLayer,
};
}
describe('getConfigurationForMetric', () => {
beforeEach(() => {
jest.clearAllMocks();
@ -26,15 +37,11 @@ describe('getConfigurationForMetric', () => {
const metricId = 'some-id';
const metric = { id: metricId, type: METRIC_TYPES.COUNT };
test('should return null if no series was provided', () => {
const layerId = 'layer-id-1';
const model = createPanel({ series: [] });
const layer: Layer = {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
const layer = createEmptyLensLayer({ layerId });
const config = getConfigurationForMetric(model, layer);
expect(config).toBeNull();
@ -47,12 +54,7 @@ describe('getConfigurationForMetric', () => {
const model = createPanel({
series: [createSeries({ metrics: [metric1] })],
});
const layer: Layer = {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
const layer = createEmptyLensLayer({ layerId });
const config = getConfigurationForMetric(model, layer);
expect(config).toBeNull();
@ -69,12 +71,7 @@ describe('getConfigurationForMetric', () => {
createSeries({ metrics: [metric, metric2] }),
],
});
const layer: Layer = {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
const layer = createEmptyLensLayer({ layerId });
const config = getConfigurationForMetric(model, layer);
expect(config).toBeNull();
@ -94,7 +91,9 @@ describe('getConfigurationForMetric', () => {
const model = createPanel({
series: [createSeries({ metrics: [metric, metric1] }), createSeries({ metrics: [metric2] })],
});
const layer: Layer = {
const layer = createEmptyLensLayer({
layerId,
columns: [
{
columnId: columnId1,
@ -105,10 +104,7 @@ describe('getConfigurationForMetric', () => {
meta: { metricId: metricId2 },
},
] as Column[],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
});
const config = getConfigurationForMetric(model, layer);
expect(config).toEqual({
@ -131,7 +127,7 @@ describe('getConfigurationForMetric', () => {
series: [createSeries({ metrics: [metric] })],
});
const bucket = { columnId: bucketColumnId } as Column;
const layer: Layer = {
const layer = createEmptyLensLayer({
columns: [
{
columnId,
@ -144,10 +140,8 @@ describe('getConfigurationForMetric', () => {
meta: { metricId },
},
],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
});
const config = getConfigurationForMetric(model, layer, bucket);
expect(config).toEqual({
@ -169,7 +163,7 @@ describe('getConfigurationForMetric', () => {
const model = createPanel({
series: [createSeries({ metrics: [metric] })],
});
const layer: Layer = {
const layer = createEmptyLensLayer({
columns: [
{
columnId,
@ -182,10 +176,8 @@ describe('getConfigurationForMetric', () => {
meta: { metricId },
},
],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
});
const config = getConfigurationForMetric(model, layer);
expect(config).toBeNull();
expect(mockGetPalette).toBeCalledTimes(1);
@ -215,12 +207,7 @@ describe('getConfigurationForGauge', () => {
test('should return null if no series was provided', () => {
const layerId = 'layer-id-1';
const model = createPanel({ series: [] });
const layer: Layer = {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
const layer = createEmptyLensLayer({ layerId });
const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn);
expect(config).toBeNull();
@ -233,12 +220,7 @@ describe('getConfigurationForGauge', () => {
const model = createPanel({
series: [createSeries({ metrics: [metric1] })],
});
const layer: Layer = {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
const layer = createEmptyLensLayer({ layerId });
const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn);
expect(config).toBeNull();
@ -252,7 +234,7 @@ describe('getConfigurationForGauge', () => {
const model = createPanel({
series: [createSeries({ metrics: [metric] })],
});
const layer: Layer = {
const layer = createEmptyLensLayer({
columns: [
{
columnId,
@ -265,10 +247,8 @@ describe('getConfigurationForGauge', () => {
meta: { metricId },
},
],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
});
const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn);
expect(config).toBeNull();
expect(mockGetPalette).toBeCalledTimes(1);
@ -279,12 +259,7 @@ describe('getConfigurationForGauge', () => {
const metric1 = { id: 'metric-id-1', type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'sum' };
const color = '#fff';
const model = createPanel({ series: [createSeries({ metrics: [metric, metric1], color })] });
const layer: Layer = {
columns: [],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
const layer = createEmptyLensLayer({ layerId });
const config = getConfigurationForGauge(model, layer, undefined, gaugeMaxColumn);
expect(config).toEqual({
@ -312,7 +287,7 @@ describe('getConfigurationForGauge', () => {
const model = createPanel({ series: [createSeries({ metrics: [metric, metric1], color })] });
const bucketColumnId = 'bucket-column-id-1';
const bucket = { columnId: bucketColumnId } as Column;
const layer: Layer = {
const layer = createEmptyLensLayer({
columns: [
{
columnId: columnId1,
@ -325,10 +300,8 @@ describe('getConfigurationForGauge', () => {
meta: { metricId },
},
],
columnOrder: [],
indexPatternId: 'some-index-pattern',
layerId,
};
});
const config = getConfigurationForGauge(model, layer, bucket, gaugeMaxColumn);
expect(config).toEqual({

View file

@ -68,6 +68,7 @@ describe('getLayers', () => {
{
indexPatternId: 'test',
layerId: 'test-layer-1',
ignoreGlobalFilters: false,
columns: [
{
operationType: 'count',
@ -111,6 +112,7 @@ describe('getLayers', () => {
{
indexPatternId: 'test',
layerId: 'test-layer-1',
ignoreGlobalFilters: false,
columns: [
{
operationType: 'static_value',
@ -131,6 +133,7 @@ describe('getLayers', () => {
{
indexPatternId: 'test',
layerId: 'test-layer-1',
ignoreGlobalFilters: false,
columns: [
{
operationType: 'percentile',
@ -164,6 +167,7 @@ describe('getLayers', () => {
{
indexPatternId: 'test',
layerId: 'test-layer-1',
ignoreGlobalFilters: false,
columns: [
{
operationType: 'percentile_rank',

View file

@ -283,4 +283,73 @@ describe('convertToLens', () => {
expect(result?.type).toBe('lnsMetric');
expect(mockGetConfigurationForMetric).toBeCalledTimes(1);
});
test('should set the ignoreGlobalFilters if set on the panel', async () => {
mockGetBucketsColumns.mockReturnValueOnce([bucket]);
mockGetBucketsColumns.mockReturnValueOnce([bucket]);
mockGetMetricsColumns.mockReturnValueOnce([metric]);
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
],
ignore_global_filter: 1,
}),
} as Vis<Panel>);
expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true);
});
test('should set the ignoreGlobalFilters if set on the series', async () => {
mockGetBucketsColumns.mockReturnValueOnce([bucket]);
mockGetBucketsColumns.mockReturnValueOnce([bucket]);
mockGetMetricsColumns.mockReturnValueOnce([metric]);
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
],
}),
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(true);
});
test('should ignore the ignoreGlobalFilters if set on hidden series', async () => {
mockGetBucketsColumns.mockReturnValueOnce([bucket]);
mockGetBucketsColumns.mockReturnValueOnce([bucket]);
mockGetMetricsColumns.mockReturnValueOnce([metric]);
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: true,
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
],
}),
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(false);
});
});

View file

@ -117,6 +117,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (
const [bucket] = uniqueBuckets;
const extendedLayer: ExtendedLayer = {
ignoreGlobalFilters: Boolean(
model.ignore_global_filter ||
// eslint-disable-next-line @typescript-eslint/naming-convention
visibleSeries.some(({ ignore_global_filter }) => ignore_global_filter)
),
indexPatternId: indexPatternId as string,
layerId: uuidv4(),
columns: [...metrics, ...(bucket ? [bucket] : [])],

View file

@ -232,4 +232,65 @@ describe('convertToLens', () => {
expect(result?.type).toBe('lnsDatatable');
expect(mockIsValidMetrics).toBeCalledTimes(1);
});
test('should set the ignoreGlobalFilters if set on the panel', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
],
ignore_global_filter: 1,
}),
uiState: {
get: () => ({}),
},
} as Vis<Panel>);
expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true);
});
test('should set the ignoreGlobalFilters if set on the series', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
],
}),
uiState: {
get: () => ({}),
},
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(true);
});
test('should ignore the ignoreGlobalFilters if set on hidden series', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: true,
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
],
}),
uiState: {
get: () => ({}),
},
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(false);
});
});

View file

@ -38,7 +38,8 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (
const dataViews = getDataViewsStart();
try {
const seriesNum = model.series.filter((series) => !series.hidden).length;
const visibleSeries = model.series.filter((series) => !series.hidden);
const seriesNum = visibleSeries.length;
const sortConfig = uiState.get('table')?.sort ?? {};
const datasourceInfo = await extractOrGenerateDatasourceInfo(
@ -165,6 +166,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (
}
const extendedLayer: ExtendedLayer = {
ignoreGlobalFilters: Boolean(
model.ignore_global_filter ||
// eslint-disable-next-line @typescript-eslint/naming-convention
visibleSeries.some(({ ignore_global_filter }) => ignore_global_filter)
),
indexPatternId: indexPatternId as string,
layerId: uuidv4(),
columns: [...metrics, ...commonBucketsColumns, ...bucketsColumns],

View file

@ -162,4 +162,51 @@ describe('convertToLens', () => {
expect(result?.type).toBe('lnsXY');
expect(mockIsValidMetrics).toBeCalledTimes(0);
});
test('should set the ignoreGlobalFilters if set on the panel', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
],
ignore_global_filter: 1,
}),
} as Vis<Panel>);
expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true);
});
test('should set the ignoreGlobalFilters if set on the series', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
ignore_global_filter: 1,
}),
],
}),
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(true);
});
test('should ignore the ignoreGlobalFilters if set on hidden series', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: true,
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: false,
}),
],
}),
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(false);
});
});

View file

@ -111,6 +111,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async ({ params: mo
const layerId = uuidv4();
extendedLayers[layerIdx] = {
ignoreGlobalFilters: Boolean(model.ignore_global_filter || series.ignore_global_filter),
indexPatternId,
layerId,
columns: isReferenceLine

View file

@ -133,4 +133,51 @@ describe('convertToLens', () => {
expect(result?.type).toBe('lnsXY');
expect(mockIsValidMetrics).toBeCalledTimes(0);
});
test('should set the ignoreGlobalFilters if set on the panel', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
],
ignore_global_filter: 1,
}),
} as Vis<Panel>);
expect(result?.layers.every((l) => l.ignoreGlobalFilters)).toBe(true);
});
test('should set the ignoreGlobalFilters if set on the series', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
ignore_global_filter: 1,
}),
],
}),
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(true);
});
test('should ignore the ignoreGlobalFilters if set on hidden series', async () => {
const result = await convertToLens({
params: createPanel({
series: [
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
hidden: true,
ignore_global_filter: 1,
}),
createSeries({
metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }],
}),
],
}),
} as Vis<Panel>);
expect(result?.layers[0].ignoreGlobalFilters).toBe(false);
});
});

View file

@ -37,7 +37,8 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (
const dataViews = getDataViewsStart();
try {
const extendedLayers: Record<number, ExtendedLayer> = {};
const seriesNum = model.series.filter((series) => !series.hidden).length;
const visibleSeries = model.series.filter((series) => !series.hidden);
const seriesNum = visibleSeries.length;
// handle multiple layers/series
for (const [layerIdx, series] of model.series.entries()) {
@ -85,6 +86,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (
const layerId = uuidv4();
extendedLayers[layerIdx] = {
ignoreGlobalFilters: Boolean(
model.ignore_global_filter ||
// eslint-disable-next-line @typescript-eslint/naming-convention
visibleSeries.some(({ ignore_global_filter }) => ignore_global_filter)
),
indexPatternId,
layerId,
columns: [...metricsColumns, ...bucketsColumns],

View file

@ -28,6 +28,7 @@ describe('getConfiguration', () => {
seriesIdsMap: { 1: '1' },
collapseFn: 'max',
isReferenceLineLayer: false,
ignoreGlobalFilters: false,
},
{
indexPatternId: '',
@ -41,6 +42,7 @@ describe('getConfiguration', () => {
seriesIdsMap: { 4: '2' },
collapseFn: undefined,
isReferenceLineLayer: false,
ignoreGlobalFilters: false,
},
{
indexPatternId: '',
@ -51,6 +53,7 @@ describe('getConfiguration', () => {
seriesIdsMap: {},
collapseFn: undefined,
isReferenceLineLayer: true,
ignoreGlobalFilters: false,
},
];
const series = [

View file

@ -26,6 +26,7 @@ export interface Layer {
seriesIdsMap: Record<string, string>;
isReferenceLineLayer: boolean;
collapseFn?: CollapseFunction;
ignoreGlobalFilters: boolean;
}
const SIBBLING_PIPELINE_AGGS: string[] = [
@ -190,6 +191,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte
seriesIdsMap,
collapseFn,
isReferenceLineLayer: false,
ignoreGlobalFilters: false,
};
});
@ -202,6 +204,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte
columnOrder: [],
metrics: [staticValueColumn.columnId],
isReferenceLineLayer: true,
ignoreGlobalFilters: false,
collapseFn: undefined,
seriesIdsMap: {},
});

View file

@ -14,6 +14,7 @@ export interface Layer {
layerId: string;
columns: Column[];
columnOrder: string[];
ignoreGlobalFilters: boolean;
}
export interface NavigateToLensContext<T extends Configuration = Configuration> {

View file

@ -66,6 +66,12 @@ export class VisualBuilderPageObject extends FtrService {
}
}
private async toggleYesNoSwitch(testSubj: string, value: boolean) {
const option = await this.testSubjects.find(`${testSubj}-${value ? 'yes' : 'no'}`);
(await option.findByCssSelector('label')).click();
await this.header.waitUntilLoadingHasFinished();
}
public async checkTabIsSelected(chartType: string) {
const chartTypeBtn = await this.testSubjects.find(`${chartType}TsvbTypeBtn`);
const isSelected = await chartTypeBtn.getAttribute('aria-selected');
@ -588,17 +594,11 @@ export class VisualBuilderPageObject extends FtrService {
}
public async setDropLastBucket(value: boolean) {
const option = await this.testSubjects.find(`metricsDropLastBucket-${value ? 'yes' : 'no'}`);
(await option.findByCssSelector('label')).click();
await this.header.waitUntilLoadingHasFinished();
await this.toggleYesNoSwitch('metricsDropLastBucket', value);
}
public async setOverrideIndexPattern(value: boolean) {
const option = await this.testSubjects.find(
`seriesOverrideIndexPattern-${value ? 'yes' : 'no'}`
);
(await option.findByCssSelector('label')).click();
await this.header.waitUntilLoadingHasFinished();
await this.toggleYesNoSwitch('seriesOverrideIndexPattern', value);
}
public async waitForIndexPatternTimeFieldOptionsLoaded() {
@ -912,6 +912,10 @@ export class VisualBuilderPageObject extends FtrService {
await this.header.waitUntilLoadingHasFinished();
}
public async setIgnoreFilters(value: boolean) {
await this.toggleYesNoSwitch('ignore_global_filter', value);
}
public async setMetricsDataTimerangeMode(value: string) {
const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode');
return await this.comboBox.setElement(dataTimeRangeMode, value);

View file

@ -542,6 +542,9 @@ describe('IndexPattern Data Source', () => {
"type": "expression",
},
],
"ignoreGlobalFilters": Array [
false,
],
"index": Array [
Object {
"chain": Array [
@ -1831,6 +1834,7 @@ describe('IndexPattern Data Source', () => {
columns: {},
sampling: 1,
linkToLayers: ['link-to-id'],
ignoreGlobalFilters: false,
},
},
});
@ -1921,6 +1925,7 @@ describe('IndexPattern Data Source', () => {
indexPatternId: '1',
columnOrder: [],
columns: {},
ignoreGlobalFilters: false,
linkToLayers: ['some-layer'],
sampling: 1,
},
@ -1951,7 +1956,12 @@ describe('IndexPattern Data Source', () => {
newState: {
...state,
layers: {
first: { ...state.layers.first, linkToLayers: undefined, sampling: 1 },
first: {
...state.layers.first,
linkToLayers: undefined,
sampling: 1,
ignoreGlobalFilters: false,
},
},
},
});

View file

@ -997,6 +997,7 @@ function blankLayer(indexPatternId: string, linkToLayers?: string[]): FormBasedL
columns: {},
columnOrder: [],
sampling: 1,
ignoreGlobalFilters: false,
};
}

View file

@ -1577,6 +1577,7 @@ describe('IndexPattern Data Source suggestions', () => {
},
],
indexPatternId: '1',
ignoreGlobalFilters: false,
},
] as Layer[];
function stateWithoutLayer() {
@ -2084,6 +2085,27 @@ describe('IndexPattern Data Source suggestions', () => {
})
);
});
it('should repserve the ignoreGlobalFilter flag if set', () => {
const updatedContext = [{ ...context[0], ignoreGlobalFilters: true }];
const suggestions = getDatasourceSuggestionsForVisualizeCharts(
stateWithoutLayer(),
updatedContext,
expectedIndexPatterns
);
expect(suggestions).toContainEqual(
expect.objectContaining({
state: expect.objectContaining({
layers: {
test: expect.objectContaining({
ignoreGlobalFilters: true,
}),
},
}),
})
);
});
});
describe('#getDatasourceSuggestionsForVisualizeField', () => {

View file

@ -312,6 +312,7 @@ function createNewLayerWithMetricAggregationFromVizEditor(
) {
const columns = convertToColumnChange(layer.columns, indexPattern);
let newLayer: FormBasedLayer = {
ignoreGlobalFilters: layer.ignoreGlobalFilters,
indexPatternId: indexPattern.id,
columns: {},
columnOrder: [],

View file

@ -14,6 +14,7 @@ import { RandomSamplingSlider } from '@kbn/random-sampling';
import type { DatasourceLayerSettingsProps } from '../../types';
import type { FormBasedPrivateState } from './types';
import { isSamplingValueEnabled } from './utils';
import { IgnoreGlobalFilterRowControl } from '../../shared_components/ignore_global_filter';
const samplingValues = [0.00001, 0.0001, 0.001, 0.01, 0.1, 1];
@ -28,81 +29,95 @@ export function LayerSettingsPanel({
: state.layers[layerId].sampling;
return (
<EuiFormRow
display="rowCompressed"
data-test-subj="lns-indexPattern-random-sampling-row"
fullWidth
helpText={
<>
<EuiSpacer size="s" />
<p>
<FormattedMessage
id="xpack.lens.indexPattern.randomSampling.help"
defaultMessage="Lower sampling percentages increases the performance, but lowers the accuracy. Lower sampling percentages are best for large datasets. {link}"
values={{
link: (
<EuiLink
href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html"
target="_blank"
external
>
<FormattedMessage
id="xpack.lens.indexPattern.randomSampling.learnMore"
defaultMessage="View documentation"
/>
</EuiLink>
),
}}
/>
</p>
</>
}
label={
<>
{i18n.translate('xpack.lens.indexPattern.randomSampling.label', {
defaultMessage: 'Sampling',
})}{' '}
<EuiToolTip
content={i18n.translate('xpack.lens.indexPattern.randomSampling.experimentalLabel', {
defaultMessage: 'Technical preview',
})}
>
<EuiBetaBadge
css={css`
vertical-align: middle;
`}
iconType="beaker"
label={i18n.translate('xpack.lens.indexPattern.randomSampling.experimentalLabel', {
<>
<EuiFormRow
display="rowCompressed"
data-test-subj="lns-indexPattern-random-sampling-row"
fullWidth
helpText={
<>
<EuiSpacer size="s" />
<p>
<FormattedMessage
id="xpack.lens.indexPattern.randomSampling.help"
defaultMessage="Lower sampling percentages increases the performance, but lowers the accuracy. Lower sampling percentages are best for large datasets. {link}"
values={{
link: (
<EuiLink
href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html"
target="_blank"
external
>
<FormattedMessage
id="xpack.lens.indexPattern.randomSampling.learnMore"
defaultMessage="View documentation"
/>
</EuiLink>
),
}}
/>
</p>
</>
}
label={
<>
{i18n.translate('xpack.lens.indexPattern.randomSampling.label', {
defaultMessage: 'Sampling',
})}{' '}
<EuiToolTip
content={i18n.translate('xpack.lens.indexPattern.randomSampling.experimentalLabel', {
defaultMessage: 'Technical preview',
})}
size="s"
/>
</EuiToolTip>
</>
}
>
<RandomSamplingSlider
disabled={isSamplingValueDisabled}
disabledReason={i18n.translate('xpack.lens.indexPattern.randomSampling.disabledMessage', {
defaultMessage:
'In order to select a reduced sampling percentage, you must remove any maximum or minimum functions applied on this layer.',
})}
values={samplingValues}
currentValue={currentValue}
data-test-subj="lns-indexPattern-random-sampling-slider"
onChange={(newSamplingValue) => {
setState({
...state,
layers: {
...state.layers,
[layerId]: {
...state.layers[layerId],
sampling: newSamplingValue,
>
<EuiBetaBadge
css={css`
vertical-align: middle;
`}
iconType="beaker"
label={i18n.translate('xpack.lens.indexPattern.randomSampling.experimentalLabel', {
defaultMessage: 'Technical preview',
})}
size="s"
/>
</EuiToolTip>
</>
}
>
<RandomSamplingSlider
disabled={isSamplingValueDisabled}
disabledReason={i18n.translate('xpack.lens.indexPattern.randomSampling.disabledMessage', {
defaultMessage:
'In order to select a reduced sampling percentage, you must remove any maximum or minimum functions applied on this layer.',
})}
values={samplingValues}
currentValue={currentValue}
data-test-subj="lns-indexPattern-random-sampling-slider"
onChange={(newSamplingValue) => {
setState({
...state,
layers: {
...state.layers,
[layerId]: {
...state.layers[layerId],
sampling: newSamplingValue,
},
},
},
});
});
}}
/>
</EuiFormRow>
<IgnoreGlobalFilterRowControl
checked={!state.layers[layerId].ignoreGlobalFilters}
onChange={() => {
const newLayer = {
...state.layers[layerId],
ignoreGlobalFilters: !state.layers[layerId].ignoreGlobalFilters,
};
const newLayers = { ...state.layers };
newLayers[layerId] = newLayer;
setState({ ...state, layers: newLayers });
}}
/>
</EuiFormRow>
</>
);
}

View file

@ -13,6 +13,7 @@ import type { DatasourceLayerPanelProps } from '../../types';
import type { FormBasedPrivateState } from './types';
import { ChangeIndexPattern } from '../../shared_components/dataview_picker/dataview_picker';
import { getSamplingValue } from './utils';
import { getIgnoreGlobalFilterIcon } from '../../shared_components/ignore_global_filter';
export interface FormBasedLayerPanelProps extends DatasourceLayerPanelProps<FormBasedPrivateState> {
state: FormBasedPrivateState;
@ -41,24 +42,28 @@ export function LayerPanel({
});
const samplingValue = getSamplingValue(layer);
const extraIconLabelProps =
samplingValue !== 1
? {
icon: {
component: (
<RandomSamplingIcon color={euiTheme.colors.disabledText} fill="currentColor" />
),
value: `${samplingValue * 100}%`,
tooltipValue: i18n.translate('xpack.lens.indexPattern.randomSamplingInfo', {
defaultMessage: '{value}% sampling',
values: {
value: samplingValue * 100,
},
}),
'data-test-subj': 'lnsChangeIndexPatternSamplingInfo',
},
}
: {};
const extraIcons = [];
if (layer.ignoreGlobalFilters) {
extraIcons.push(
getIgnoreGlobalFilterIcon({
color: euiTheme.colors.disabledText,
dataTestSubj: 'lnsChangeIndexPatternIgnoringFilters',
})
);
}
if (samplingValue !== 1) {
extraIcons.push({
component: <RandomSamplingIcon color={euiTheme.colors.disabledText} fill="currentColor" />,
value: `${samplingValue * 100}%`,
tooltipValue: i18n.translate('xpack.lens.indexPattern.randomSamplingInfo', {
defaultMessage: '{value}% sampling',
values: {
value: samplingValue * 100,
},
}),
'data-test-subj': 'lnsChangeIndexPatternSamplingInfo',
});
}
return (
<ChangeIndexPattern
@ -69,7 +74,7 @@ export function LayerPanel({
'data-test-subj': 'lns_layerIndexPatternLabel',
size: 's',
fontWeight: 'normal',
...extraIconLabelProps,
extraIcons,
}}
indexPatternId={layer.indexPatternId}
indexPatternRefs={indexPatternRefs}

View file

@ -426,6 +426,7 @@ function getExpressionForLayer(
timeFields: allDateHistogramFields,
probability: getSamplingValue(layer),
samplerSeed: seedrandom(searchSessionId).int32(),
ignoreGlobalFilters: Boolean(layer.ignoreGlobalFilters),
}).toAst(),
{
type: 'function',

View file

@ -55,6 +55,7 @@ export interface FormBasedLayer {
// Partial columns represent the temporary invalid states
incompleteColumns?: Record<string, IncompleteColumn>;
sampling?: number;
ignoreGlobalFilters?: boolean;
}
export interface FormBasedPersistedState {

View file

@ -60,6 +60,7 @@ import { supportsRarityRanking } from './operations/definitions/terms';
import { DEFAULT_MAX_DOC_COUNT } from './operations/definitions/terms/constants';
import { getOriginalId } from '../../../common/expressions/datatable/transpose_helpers';
import { ReducedSamplingSectionEntries } from './info_badges';
import { IgnoredGlobalFiltersEntries } from '../../shared_components/ignore_global_filter';
function isMinOrMaxColumn(
column?: GenericIndexPatternColumn
@ -508,14 +509,13 @@ export function getNotifiableFeatures(
if (!visualizationInfo) {
return [];
}
const layersWithCustomSamplingValues = Object.entries(state.layers).filter(
const features: UserMessage[] = [];
const layers = Object.entries(state.layers);
const layersWithCustomSamplingValues = layers.filter(
([, layer]) => getSamplingValue(layer) !== 1
);
if (!layersWithCustomSamplingValues.length) {
return [];
}
return [
{
if (layersWithCustomSamplingValues.length) {
features.push({
uniqueId: 'random_sampling_info',
severity: 'info',
fixableInEditor: false,
@ -530,8 +530,32 @@ export function getNotifiableFeatures(
/>
),
displayLocations: [{ id: 'embeddableBadge' }],
},
];
});
}
const layersWithIgnoreGlobalFilters = layers.filter(([, layer]) => layer.ignoreGlobalFilters);
if (layersWithIgnoreGlobalFilters.length) {
features.push({
uniqueId: 'ignoring-global-filters-layers',
severity: 'info',
fixableInEditor: false,
shortMessage: i18n.translate('xpack.lens.xyChart.layerAnnotationsIgnoreTitle', {
defaultMessage: 'Layers ignoring global filters',
}),
longMessage: (
<IgnoredGlobalFiltersEntries
layers={layersWithIgnoreGlobalFilters.map(([layerId, { indexPatternId }]) => ({
layerId,
indexPatternId,
}))}
visualizationInfo={visualizationInfo}
dataViews={frame.dataViews}
/>
),
displayLocations: [{ id: 'embeddableBadge' }],
});
}
return features;
}
/**

View file

@ -0,0 +1,234 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event';
import { EmbeddableFeatureBadge } from './embeddable_info_badges';
import { UserMessage } from '../types';
describe('EmbeddableFeatureBadge', () => {
function renderPopup(messages: UserMessage[], count: number = messages.length) {
render(<EmbeddableFeatureBadge messages={messages} />);
userEvent.click(screen.getByText(`${count}`));
}
it('should render no badge', () => {
render(<EmbeddableFeatureBadge messages={[]} />);
expect(screen.queryByText('0')).not.toBeInTheDocument();
});
it('should render the message in the popover', async () => {
render(
<EmbeddableFeatureBadge
messages={[
{
shortMessage: 'Short message',
longMessage: 'Long text',
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
]}
/>
);
expect(screen.getByText('1')).toBeInTheDocument();
userEvent.click(screen.getByText('1'));
expect(await screen.findByText('Long text')).toBeInTheDocument();
});
it('should render a description of the badge in a tooltip on hover', async () => {
renderPopup([
{
shortMessage: 'Short message',
longMessage: 'Long text',
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
]);
expect(await screen.findByText('1 visualization modifier')).toBeInTheDocument();
});
it('should render a separate section for each unique-id', async () => {
renderPopup([
{
uniqueId: '1',
shortMessage: 'Section1',
longMessage: 'Long text',
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
{
uniqueId: '2',
shortMessage: 'Section2',
longMessage: 'Long text 2',
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
]);
expect(await screen.findByText('Section1')).toBeInTheDocument();
expect(await screen.findByText('Section2')).toBeInTheDocument();
});
it('should group multiple messages with same id', async () => {
renderPopup(
[
{
uniqueId: '1',
shortMessage: 'Section1',
longMessage: <div>LongText1</div>,
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
{
uniqueId: '1',
shortMessage: 'Section1',
longMessage: <div>LongText2</div>,
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
],
1 // messages are grouped
);
expect(await screen.findByText('Section1')).toBeInTheDocument();
expect(await screen.findAllByText('Section1')).toHaveLength(1);
expect(await screen.findAllByText('LongText', { exact: false })).toHaveLength(2);
});
it('should render messages without id first, then grouped messages', async () => {
renderPopup(
[
{
shortMessage: 'Section2',
longMessage: <div>AnotherText</div>,
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
{
uniqueId: '1',
shortMessage: 'Section1',
longMessage: <div>LongText1</div>,
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
{
uniqueId: '1',
shortMessage: 'Section1',
longMessage: <div>LongText2</div>,
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
],
2 // last two messages are grouped
);
expect(await screen.findAllByText('Section', { exact: false })).toHaveLength(2);
// now check the order
const longMessages = await screen.findAllByText('Text', { exact: false });
expect(longMessages[0]).toHaveTextContent('AnotherText');
expect(longMessages[1]).toHaveTextContent('LongText1');
});
describe('Horizontal rules', () => {
it('should render no rule for single message', async () => {
renderPopup([
{
shortMessage: `Section1`,
longMessage: <div>hello</div>,
severity: 'info',
fixableInEditor: false,
displayLocations: [],
},
]);
expect(
await screen.queryByTestId('lns-feature-badges-horizontal-rule')
).not.toBeInTheDocument();
});
it('should apply an horizontal if there are multiple messages without id', async () => {
const messages = [1, 2, 3].map((id) => ({
shortMessage: `Section${id}`,
longMessage: <div>{id}</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
}));
renderPopup(messages);
expect(await screen.getAllByTestId('lns-feature-badges-horizontal-rule')).toHaveLength(
messages.length - 1
);
});
it('should apply a rule between messages without id and grouped ones', async () => {
const messages = [
{
uniqueId: 'myId',
shortMessage: `Section1`,
longMessage: <div>Grouped</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
},
{
shortMessage: `Section2`,
longMessage: <div>NoId</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
},
];
renderPopup(messages);
expect(await screen.getAllByTestId('lns-feature-badges-horizontal-rule')).toHaveLength(1);
});
it('should apply rules taking into account grouped messages', async () => {
const messages = [
{
shortMessage: `Section2`,
longMessage: <div>NoId</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
},
// #1 rule here
{
uniqueId: 'myId',
shortMessage: `Section1`,
longMessage: <div>Grouped</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
},
{
uniqueId: 'myId',
shortMessage: `Section1`,
longMessage: <div>Grouped 2</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
},
// #2 rule here
{
uniqueId: 'myOtherId',
shortMessage: `Section2`,
longMessage: <div>Grouped3</div>,
severity: 'info' as const,
fixableInEditor: false,
displayLocations: [],
},
];
renderPopup(messages, 3);
expect(await screen.getAllByTestId('lns-feature-badges-horizontal-rule')).toHaveLength(2);
});
});
});

View file

@ -16,7 +16,7 @@ import {
} from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { Fragment } from 'react';
import { useState } from 'react';
import type { UserMessage } from '../types';
import './embeddable_info_badges.scss';
@ -36,6 +36,20 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }
count: messages.length,
},
});
const messagesWithoutUniqueId = messages.filter(({ uniqueId }) => !uniqueId);
// compact messages be grouping longMessage together on matching unique-id
const messagesGroupedByUniqueId: Record<string, UserMessage[]> = {};
for (const message of messages) {
if (message.uniqueId) {
if (!messagesGroupedByUniqueId[message.uniqueId]) {
messagesGroupedByUniqueId[message.uniqueId] = [];
}
messagesGroupedByUniqueId[message.uniqueId].push(message);
}
}
const messageCount =
messagesWithoutUniqueId.length + Object.keys(messagesGroupedByUniqueId).length;
return (
<EuiPopover
panelPaddingSize="none"
@ -61,7 +75,7 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }
`}
iconType="wrench"
>
{messages.length}
{messageCount}
</EuiButtonEmpty>
</EuiToolTip>
}
@ -72,13 +86,18 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }
css={css`
max-width: 280px;
`}
data-test-subj="lns-feature-badges-panel"
>
{messages.map(({ shortMessage, longMessage }, index) => {
{messagesWithoutUniqueId.map(({ shortMessage, longMessage }, index) => {
return (
<>
{index ? <EuiHorizontalRule margin="none" /> : null}
<Fragment key={`${shortMessage}-${index}`}>
{index ? (
<EuiHorizontalRule
margin="none"
data-test-subj="lns-feature-badges-horizontal-rule"
/>
) : null}
<aside
key={`${shortMessage}-${index}`}
css={css`
padding: ${euiTheme.size.base};
`}
@ -88,7 +107,35 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }
</EuiTitle>
<ul className="lnsEmbeddablePanelFeatureList">{longMessage}</ul>
</aside>
</>
</Fragment>
);
})}
{Object.entries(messagesGroupedByUniqueId).map(([uniqueId, messagesByUniqueId], index) => {
const hasHorizontalRule = messagesWithoutUniqueId.length || index;
const [{ shortMessage }] = messagesByUniqueId;
return (
<Fragment key={uniqueId}>
{hasHorizontalRule ? (
<EuiHorizontalRule
margin="none"
data-test-subj="lns-feature-badges-horizontal-rule"
/>
) : null}
<aside
css={css`
padding: ${euiTheme.size.base};
`}
>
<EuiTitle size="xxs" css={css`color=${euiTheme.colors.title}`}>
<h3>{shortMessage}</h3>
</EuiTitle>
<ul className="lnsEmbeddablePanelFeatureList">
{messagesByUniqueId.map(({ longMessage }, i) => (
<Fragment key={`${uniqueId}-${i}`}>{longMessage}</Fragment>
))}
</ul>
</aside>
</Fragment>
);
})}
</div>

View file

@ -0,0 +1,125 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { TriggerButton } from './trigger';
import { EuiIcon } from '@elastic/eui';
describe('TriggerButton', () => {
describe('base version (no icons)', () => {
it('should render the basic button', () => {
render(
<TriggerButton togglePopover={jest.fn()} label={'Trigger label'} dataTestSubj="test-id" />
);
expect(screen.getByText('Trigger label')).toBeInTheDocument();
});
it('should render the title if provided', () => {
render(
<TriggerButton
togglePopover={jest.fn()}
label={'Trigger'}
dataTestSubj="test-id"
title="My title"
/>
);
expect(screen.getByTitle('My title')).toBeInTheDocument();
});
it('should call the toggle callback on click', () => {
const toggleFn = jest.fn();
render(
<TriggerButton
togglePopover={toggleFn}
label={'Trigger'}
dataTestSubj="test-id"
title="My title"
/>
);
userEvent.click(screen.getByText('Trigger'));
expect(toggleFn).toHaveBeenCalled();
});
it('should render the main label as red if missing', () => {
render(
<TriggerButton
togglePopover={jest.fn()}
label={'Trigger'}
dataTestSubj="test-id"
title="My title"
isMissingCurrent
/>
);
// EUI danger red: rgb(171, 35, 28)
expect(screen.getByTestId('test-id')).toHaveStyle({ color: 'rgb(171, 35, 28)' });
});
});
describe('with icons', () => {
it('should render one icon', () => {
render(
<TriggerButton
togglePopover={jest.fn()}
label={'Trigger label'}
dataTestSubj="test-id"
extraIcons={[
{
component: <EuiIcon type={'filterIgnore'} color={'red'} />,
tooltipValue: 'Ignore global filters',
'data-test-subj': 'ignore-global-filters',
},
]}
/>
);
expect(screen.getByTestId('ignore-global-filters')).toBeInTheDocument();
});
it('should render multiple icons', () => {
const indexes = [1, 2, 3];
render(
<TriggerButton
togglePopover={jest.fn()}
label={'Trigger label'}
dataTestSubj="test-id"
extraIcons={indexes.map((index) => ({
component: <EuiIcon type={'filterIgnore'} color={'red'} />,
tooltipValue: 'Ignore global filters',
'data-test-subj': `ignore-global-filters-${index}`,
}))}
/>
);
for (const index of indexes) {
expect(screen.getByTestId(`ignore-global-filters-${index}`)).toBeInTheDocument();
}
});
it('should render the value together with the provided component', () => {
render(
<TriggerButton
togglePopover={jest.fn()}
label={'Trigger label'}
dataTestSubj="test-id"
extraIcons={[
{
component: <EuiIcon type={'filterIgnore'} color={'red'} />,
tooltipValue: 'Ignore global filters',
'data-test-subj': 'ignore-global-filters',
value: 'Ignore filters',
},
]}
/>
);
expect(screen.getByText('Ignore filters')).toBeInTheDocument();
});
});
});

View file

@ -12,12 +12,12 @@ import { ToolbarButton, ToolbarButtonProps } from './toolbar_button';
interface TriggerLabelProps {
label: string;
icon?: {
extraIcons?: Array<{
component: React.ReactElement;
value?: string;
tooltipValue?: string;
'data-test-subj': string;
};
}>;
}
export type ChangeIndexPatternTriggerProps = ToolbarButtonProps &
@ -27,10 +27,10 @@ export type ChangeIndexPatternTriggerProps = ToolbarButtonProps &
isDisabled?: boolean;
};
function TriggerLabel({ label, icon }: TriggerLabelProps) {
function TriggerLabel({ label, extraIcons }: TriggerLabelProps) {
const { euiTheme } = useEuiTheme();
if (!icon) {
if (!extraIcons?.length) {
return <>{label}</>;
}
return (
@ -44,28 +44,35 @@ function TriggerLabel({ label, icon }: TriggerLabelProps) {
>
{label}
</EuiFlexItem>
<EuiFlexItem
grow={false}
data-test-subj={icon['data-test-subj']}
css={css`
display: block;
*:hover &,
*:focus & {
text-decoration: none !important;
}
`}
>
<EuiToolTip content={icon.tooltipValue} position="top">
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>{icon.component}</EuiFlexItem>
{icon.value ? (
<EuiFlexItem grow={false}>
<EuiTextColor color={euiTheme.colors.disabledText}>{icon.value}</EuiTextColor>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiToolTip>
</EuiFlexItem>
{extraIcons.map((icon) => (
<EuiFlexItem
grow={false}
data-test-subj={icon['data-test-subj']}
css={css`
display: block;
*:hover &,
*:focus & {
text-decoration: none !important;
}
`}
key={icon['data-test-subj']}
>
<EuiToolTip
content={icon.tooltipValue}
position="top"
data-test-subj={`${icon['data-test-subj']}-tooltip`}
>
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>{icon.component}</EuiFlexItem>
{icon.value ? (
<EuiFlexItem grow={false}>
<EuiTextColor color={euiTheme.colors.disabledText}>{icon.value}</EuiTextColor>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiToolTip>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
}
@ -75,7 +82,7 @@ export function TriggerButton({
title,
togglePopover,
isMissingCurrent,
icon,
extraIcons,
...rest
}: ChangeIndexPatternTriggerProps & {
togglePopover: () => void;
@ -96,7 +103,7 @@ export function TriggerButton({
{...rest}
textProps={{ style: { width: '100%' } }}
>
<TriggerLabel label={label} icon={icon} />
<TriggerLabel label={label} extraIcons={extraIcons} />
</ToolbarButton>
);
}

View file

@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiIcon } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import React from 'react';
/**
* Retrieve the IgnoreGlobalfilter shared icon to be put into the dataViewPicker within a Layer panel
* @param dataTestSubj test id to be applied
* @returns
*/
export const getIgnoreGlobalFilterIcon = ({
color,
dataTestSubj,
}: {
color: string;
dataTestSubj: string;
}) => {
return {
component: (
<EuiIcon
type={'filterIgnore'}
color={color}
css={css`
margin-top: 15px;
`}
/>
),
tooltipValue: i18n.translate('xpack.lens.layerPanel.ignoreGlobalFilters', {
defaultMessage: 'Ignore global filters',
}),
'data-test-subj': dataTestSubj,
};
};

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { getIgnoreGlobalFilterIcon } from './data_view_picker_icon';
export { IgnoredGlobalFiltersEntries } from './info_badge';
export { IgnoreGlobalFilterRowControl } from './settings_control';

View file

@ -7,16 +7,15 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { InfoBadge } from '../../shared_components/info_badges/info_badge';
import { InfoBadge } from '../info_badges/info_badge';
import { FramePublicAPI, VisualizationInfo } from '../../types';
import { XYAnnotationLayerConfig } from './types';
export function IgnoredGlobalFiltersEntries({
layers,
visualizationInfo,
dataViews,
}: {
layers: XYAnnotationLayerConfig[];
layers: Array<{ layerId: string; indexPatternId: string }>;
visualizationInfo: VisualizationInfo;
dataViews: FramePublicAPI['dataViews'];
}) {
@ -27,8 +26,8 @@ export function IgnoredGlobalFiltersEntries({
const layerInfo = visualizationInfo.layers.find(({ layerId }) => layerId === layer.layerId);
const layerTitle =
layerInfo?.label ||
i18n.translate('xpack.lens.xyChart.layerAnnotationsLabel', {
defaultMessage: 'Annotations',
i18n.translate('xpack.lens.layerTitle.fallbackLabel', {
defaultMessage: 'Layer',
});
const layerPalette = layerInfo?.palette;
return (

View file

@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFormRow, EuiSwitch } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
export function IgnoreGlobalFilterRowControl({
checked,
onChange,
}: {
checked: boolean;
onChange: (newValue: boolean) => void;
}) {
return (
<EuiFormRow
display="columnCompressedSwitch"
label={i18n.translate('xpack.lens.layerSettings.ignoreGlobalFilters', {
defaultMessage: 'Use global filters',
})}
>
<EuiSwitch
label={i18n.translate('xpack.lens.layerSettings.ignoreGlobalFilters', {
defaultMessage: 'Use global filters',
})}
showLabel={false}
checked={checked}
data-test-subj="lns-layerSettings-ignoreGlobalFilters"
onChange={() => onChange(!checked)}
compressed
/>
</EuiFormRow>
);
}

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import { EuiFormRow, EuiSwitch } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { IgnoreGlobalFilterRowControl } from '../../shared_components/ignore_global_filter';
import type { VisualizationLayerSettingsProps } from '../../types';
import type { XYState } from './types';
import { isAnnotationsLayer } from './visualization_helpers';
@ -26,28 +25,15 @@ export function LayerSettings({
return null;
}
return (
<EuiFormRow
display="columnCompressedSwitch"
label={i18n.translate('xpack.lens.xyChart.ignoreGlobalFilters', {
defaultMessage: 'Use global filters',
})}
>
<EuiSwitch
label={i18n.translate('xpack.lens.xyChart.ignoreGlobalFilters', {
defaultMessage: 'Use global filters',
})}
showLabel={false}
checked={!layer.ignoreGlobalFilters}
data-test-subj="lnsXY-layerSettings-ignoreGlobalFilters"
onChange={() => {
const layerIndex = state.layers.findIndex((l) => l === layer);
const newLayer = { ...layer, ignoreGlobalFilters: !layer.ignoreGlobalFilters };
const newLayers = [...state.layers];
newLayers[layerIndex] = newLayer;
setState({ ...state, layers: newLayers });
}}
compressed
/>
</EuiFormRow>
<IgnoreGlobalFilterRowControl
checked={!layer.ignoreGlobalFilters}
onChange={() => {
const layerIndex = state.layers.findIndex((l) => l === layer);
const newLayer = { ...layer, ignoreGlobalFilters: !layer.ignoreGlobalFilters };
const newLayers = [...state.layers];
newLayers[layerIndex] = newLayer;
setState({ ...state, layers: newLayers });
}}
/>
);
}

View file

@ -110,8 +110,8 @@ import { defaultAnnotationLabel } from './annotations/helpers';
import { onDropForVisualization } from '../../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils';
import { createAnnotationActions } from './annotations/actions';
import { AddLayerButton } from './add_layer';
import { IgnoredGlobalFiltersEntries } from './info_badges';
import { LayerSettings } from './layer_settings';
import { IgnoredGlobalFiltersEntries } from '../../shared_components/ignore_global_filter';
const XY_ID = 'lnsXY';
@ -1187,7 +1187,10 @@ function getNotifiableFeatures(
}),
longMessage: (
<IgnoredGlobalFiltersEntries
layers={annotationsWithIgnoreFlag}
layers={annotationsWithIgnoreFlag.map(({ layerId, indexPatternId }) => ({
layerId,
indexPatternId,
}))}
visualizationInfo={visualizationInfo}
dataViews={frame.dataViews}
/>

View file

@ -18,8 +18,8 @@ import {
} from '@elastic/eui';
import { ToolbarButton } from '@kbn/kibana-react-plugin/public';
import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons';
import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';
import { getIgnoreGlobalFilterIcon } from '../../../shared_components/ignore_global_filter/data_view_picker_icon';
import type {
VisualizationLayerHeaderContentProps,
VisualizationLayerWidgetProps,
@ -123,25 +123,6 @@ function AnnotationLayerHeaderContent({
const layer = state.layers[layerIndex] as XYAnnotationLayerConfig;
const currentIndexPattern = frame.dataViews.indexPatterns[layer.indexPatternId];
const extraIconLabelProps = !layer.ignoreGlobalFilters
? {}
: {
icon: {
component: (
<EuiIcon
type={'filterIgnore'}
color={euiTheme.colors.disabledText}
css={css`
margin-top: 15px;
`}
/>
),
tooltipValue: i18n.translate('xpack.lens.layerPanel.ignoreGlobalFilters', {
defaultMessage: 'Ignore global filters',
}),
'data-test-subj': 'lnsChangeIndexPatternIgnoringFilters',
},
};
return (
<ChangeIndexPattern
data-test-subj="indexPattern-switcher"
@ -151,7 +132,14 @@ function AnnotationLayerHeaderContent({
'data-test-subj': 'lns_layerIndexPatternLabel',
size: 's',
fontWeight: 'normal',
...extraIconLabelProps,
extraIcons: layer.ignoreGlobalFilters
? [
getIgnoreGlobalFilterIcon({
color: euiTheme.colors.disabledText,
dataTestSubj: 'lnsChangeIndexPatternIgnoringFilters',
}),
]
: undefined,
}}
indexPatternId={layer.indexPatternId}
indexPatternRefs={frame.dataViews.indexPatternRefs}

View file

@ -21219,7 +21219,6 @@
"xpack.lens.xyChart.iconSelect.starLabel": "Étoile",
"xpack.lens.xyChart.iconSelect.tagIconLabel": "Balise",
"xpack.lens.xyChart.iconSelect.triangleIconLabel": "Triangle",
"xpack.lens.xyChart.ignoreGlobalFilters": "Utiliser les filtres globaux",
"xpack.lens.xyChart.layerAnnotation": "Annotation",
"xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "Calques ignorant les filtres globaux",
"xpack.lens.xyChart.layerAnnotationsLabel": "Annotations",

View file

@ -21219,7 +21219,6 @@
"xpack.lens.xyChart.iconSelect.starLabel": "星",
"xpack.lens.xyChart.iconSelect.tagIconLabel": "タグ",
"xpack.lens.xyChart.iconSelect.triangleIconLabel": "三角形",
"xpack.lens.xyChart.ignoreGlobalFilters": "グローバルフィルターを使用",
"xpack.lens.xyChart.layerAnnotation": "注釈",
"xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "グローバルフィルターを無視するレイヤー",
"xpack.lens.xyChart.layerAnnotationsLabel": "注釈",

View file

@ -21219,7 +21219,6 @@
"xpack.lens.xyChart.iconSelect.starLabel": "五角星",
"xpack.lens.xyChart.iconSelect.tagIconLabel": "标签",
"xpack.lens.xyChart.iconSelect.triangleIconLabel": "三角形",
"xpack.lens.xyChart.ignoreGlobalFilters": "使用全局筛选",
"xpack.lens.xyChart.layerAnnotation": "标注",
"xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "忽略全局筛选的图层",
"xpack.lens.xyChart.layerAnnotationsLabel": "标注",

View file

@ -50,6 +50,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await testSubjects.getVisibleText('lnsChangeIndexPatternSamplingInfo')).to.be('1%');
});
it('should expose the ignore global filters control for a data layer', async () => {
await PageObjects.lens.openLayerContextMenu();
expect(
await testSubjects.exists('lns-layerPanel-0 > lnsChangeIndexPatternIgnoringFilters')
).to.be(false);
// click on open layer settings
await testSubjects.click('lnsLayerSettings');
// annotations settings have only ignore filters
await testSubjects.click('lns-layerSettings-ignoreGlobalFilters');
expect(
await testSubjects.exists('lns-layerPanel-0 > lnsChangeIndexPatternIgnoringFilters')
).to.be(true);
await testSubjects.click('lns-indexPattern-dimensionContainerBack');
});
it('should add an annotation layer and settings shoud be available with ignore filters', async () => {
// configure a date histogram
await PageObjects.lens.configureDimension({
@ -66,15 +81,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// add annotation layer
await PageObjects.lens.createLayer('annotations');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
expect(
await testSubjects.exists('lns-layerPanel-1 > lnsChangeIndexPatternIgnoringFilters')
).to.be(true);
await PageObjects.lens.openLayerContextMenu(1);
await testSubjects.click('lnsLayerSettings');
// annotations settings have only ignore filters
await testSubjects.click('lnsXY-layerSettings-ignoreGlobalFilters');
await testSubjects.click('lns-layerSettings-ignoreGlobalFilters');
// now close the panel and check the dataView picker has no icon
await testSubjects.click('lns-indexPattern-dimensionContainerBack');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(false);
expect(
await testSubjects.exists('lns-layerPanel-1 > lnsChangeIndexPatternIgnoringFilters')
).to.be(false);
});
it('should add a new visualization layer and disable the sampling if max operation is chosen', async () => {
@ -118,6 +137,29 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.click('lns-indexPattern-dimensionContainerBack');
});
it('should expose sampling and ignore filters settings for reference lines layer', async () => {
await PageObjects.lens.createLayer('referenceLine');
await PageObjects.lens.openLayerContextMenu(3);
// click on open layer settings
await testSubjects.click('lnsLayerSettings');
// random sampling available
await testSubjects.existOrFail('lns-indexPattern-random-sampling-row');
// tweak the value
await PageObjects.lens.dragRangeInput('lns-indexPattern-random-sampling-slider', 2, 'left');
// annotations settings have only ignore filters
await testSubjects.click('lns-layerSettings-ignoreGlobalFilters');
await testSubjects.click('lns-indexPattern-dimensionContainerBack');
// Check both sampling and ignore filters are now present
await testSubjects.existOrFail('lnsChangeIndexPatternSamplingInfo');
expect(
await testSubjects.getVisibleText('lns-layerPanel-3 > lnsChangeIndexPatternSamplingInfo')
).to.be('1%');
expect(
await testSubjects.exists('lns-layerPanel-3 > lnsChangeIndexPatternIgnoringFilters')
).to.be(true);
});
it('should switch to pie chart and have layer settings available', async () => {
await PageObjects.lens.switchToVisualization('pie');
await PageObjects.lens.openLayerContextMenu();

View file

@ -126,5 +126,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
]);
});
});
it('should bring the ignore global filters configured at series level over', async () => {
await visualBuilder.clickSeriesOption();
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('mtrVis');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
it('should bring the ignore global filters configured at panel level over', async () => {
await visualBuilder.clickPanelOptions('gauge');
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('mtrVis');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
});
}

View file

@ -127,5 +127,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
]);
});
});
it('should bring the ignore global filters configured at series level over', async () => {
await visualBuilder.clickSeriesOption();
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('mtrVis');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
it('should bring the ignore global filters configured at panel level over', async () => {
await visualBuilder.clickPanelOptions('metric');
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('mtrVis');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
});
}

View file

@ -217,5 +217,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
]);
});
});
it('should bring the ignore global filters configured at panel level over', async () => {
await visualBuilder.clickPanelOptions('table');
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('lnsDataTable');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
});
}

View file

@ -187,5 +187,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(await dimensions[2].getVisibleText()).to.eql('Top 10 values of extension.raw');
});
});
it('should bring the ignore global filters configured at series level over', async () => {
await visualBuilder.clickSeriesOption();
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('xyVisChart');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
it('should bring the ignore global filters configured at panel level over', async () => {
await visualBuilder.clickPanelOptions('timeSeries');
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('xyVisChart');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
});
}

View file

@ -179,5 +179,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(await queryBar.getQueryString()).to.equal('machine.os : ios');
});
it('should bring the ignore global filters configured at series level over', async () => {
await visualBuilder.clickSeriesOption();
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('xyVisChart');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
it('should bring the ignore global filters configured at panel level over', async () => {
await visualBuilder.clickPanelOptions('topN');
await visualBuilder.setIgnoreFilters(true);
await header.waitUntilLoadingHasFinished();
await visualize.navigateToLensFromAnotherVisulization();
await lens.waitForVisualization('xyVisChart');
expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true);
});
});
}