-
+
-
+
-
+
= ({
<>
-
+
= (props)
<>
-
+
;
+export type TLSActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.tls'>;
+export type DurationAnomalyActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.durationAnomaly'>;
+
+export const MONITOR_STATUS: MonitorStatusActionGroup = {
+ id: 'xpack.uptime.alerts.actionGroups.monitorStatus',
+ name: 'Uptime Down Monitor',
+};
+
+export const TLS: TLSActionGroup = {
+ id: 'xpack.uptime.alerts.actionGroups.tls',
+ name: 'Uptime TLS Alert',
+};
+
+export const DURATION_ANOMALY: DurationAnomalyActionGroup = {
+ id: 'xpack.uptime.alerts.actionGroups.durationAnomaly',
+ name: 'Uptime Duration Anomaly',
+};
+
export const ACTION_GROUP_DEFINITIONS: {
- MONITOR_STATUS: ActionGroup<'xpack.uptime.alerts.actionGroups.monitorStatus'>;
- TLS: ActionGroup<'xpack.uptime.alerts.actionGroups.tls'>;
- DURATION_ANOMALY: ActionGroup<'xpack.uptime.alerts.actionGroups.durationAnomaly'>;
+ MONITOR_STATUS: MonitorStatusActionGroup;
+ TLS: TLSActionGroup;
+ DURATION_ANOMALY: DurationAnomalyActionGroup;
} = {
- MONITOR_STATUS: {
- id: 'xpack.uptime.alerts.actionGroups.monitorStatus',
- name: 'Uptime Down Monitor',
- },
- TLS: {
- id: 'xpack.uptime.alerts.actionGroups.tls',
- name: 'Uptime TLS Alert',
- },
- DURATION_ANOMALY: {
- id: 'xpack.uptime.alerts.actionGroups.durationAnomaly',
- name: 'Uptime Duration Anomaly',
- },
+ MONITOR_STATUS,
+ TLS,
+ DURATION_ANOMALY,
};
export const CLIENT_ALERT_TYPES = {
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts
index 487daf0332a98..a02116877f49a 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts
@@ -5,10 +5,143 @@
* 2.0.
*/
-import { colourPalette, getSeriesAndDomain } from './data_formatting';
+import { colourPalette, getSeriesAndDomain, getSidebarItems } from './data_formatting';
import { NetworkItems, MimeType } from './types';
import { WaterfallDataEntry } from '../../waterfall/types';
+const networkItems: NetworkItems = [
+ {
+ timestamp: '2021-01-05T19:22:28.928Z',
+ method: 'GET',
+ url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css',
+ status: 200,
+ mimeType: 'text/css',
+ requestSentTime: 18098833.175,
+ requestStartTime: 18098835.439,
+ loadEndTime: 18098957.145,
+ timings: {
+ connect: 81.10800000213203,
+ wait: 34.577999998873565,
+ receive: 0.5520000013348181,
+ send: 0.3600000018195715,
+ total: 123.97000000055414,
+ proxy: -1,
+ blocked: 0.8540000017092098,
+ queueing: 2.263999998831423,
+ ssl: 55.38700000033714,
+ dns: 3.559999997378327,
+ },
+ },
+ {
+ timestamp: '2021-01-05T19:22:28.928Z',
+ method: 'GET',
+ url: 'https://unpkg.com/director@1.2.8/build/director.js',
+ status: 200,
+ mimeType: 'application/javascript',
+ requestSentTime: 18098833.537,
+ requestStartTime: 18098837.233999997,
+ loadEndTime: 18098977.648000002,
+ timings: {
+ blocked: 84.54599999822676,
+ receive: 3.068000001803739,
+ queueing: 3.69700000010198,
+ proxy: -1,
+ total: 144.1110000014305,
+ wait: 52.56100000042352,
+ connect: -1,
+ send: 0.2390000008745119,
+ ssl: -1,
+ dns: -1,
+ },
+ },
+];
+
+const networkItemsWithoutFullTimings: NetworkItems = [
+ networkItems[0],
+ {
+ timestamp: '2021-01-05T19:22:28.928Z',
+ method: 'GET',
+ url: 'file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js',
+ status: 0,
+ mimeType: 'text/javascript',
+ requestSentTime: 18098834.097,
+ loadEndTime: 18098836.889999997,
+ timings: {
+ total: 2.7929999996558763,
+ blocked: -1,
+ ssl: -1,
+ wait: -1,
+ connect: -1,
+ dns: -1,
+ queueing: -1,
+ send: -1,
+ proxy: -1,
+ receive: -1,
+ },
+ },
+];
+
+const networkItemsWithoutAnyTimings: NetworkItems = [
+ {
+ timestamp: '2021-01-05T19:22:28.928Z',
+ method: 'GET',
+ url: 'file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js',
+ status: 0,
+ mimeType: 'text/javascript',
+ requestSentTime: 18098834.097,
+ loadEndTime: 18098836.889999997,
+ timings: {
+ total: -1,
+ blocked: -1,
+ ssl: -1,
+ wait: -1,
+ connect: -1,
+ dns: -1,
+ queueing: -1,
+ send: -1,
+ proxy: -1,
+ receive: -1,
+ },
+ },
+];
+
+const networkItemsWithoutTimingsObject: NetworkItems = [
+ {
+ timestamp: '2021-01-05T19:22:28.928Z',
+ method: 'GET',
+ url: 'file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js',
+ status: 0,
+ mimeType: 'text/javascript',
+ requestSentTime: 18098834.097,
+ loadEndTime: 18098836.889999997,
+ },
+];
+
+const networkItemsWithUncommonMimeType: NetworkItems = [
+ {
+ timestamp: '2021-01-05T19:22:28.928Z',
+ method: 'GET',
+ url: 'https://unpkg.com/director@1.2.8/build/director.js',
+ status: 200,
+ mimeType: 'application/x-javascript',
+ requestSentTime: 18098833.537,
+ requestStartTime: 18098837.233999997,
+ loadEndTime: 18098977.648000002,
+ timings: {
+ blocked: 84.54599999822676,
+ receive: 3.068000001803739,
+ queueing: 3.69700000010198,
+ proxy: -1,
+ total: 144.1110000014305,
+ wait: 52.56100000042352,
+ connect: -1,
+ send: 0.2390000008745119,
+ ssl: -1,
+ dns: -1,
+ },
+ },
+];
+
describe('Palettes', () => {
it('A colour palette comprising timing and mime type colours is correctly generated', () => {
expect(colourPalette).toEqual({
@@ -30,139 +163,6 @@ describe('Palettes', () => {
});
describe('getSeriesAndDomain', () => {
- const networkItems: NetworkItems = [
- {
- timestamp: '2021-01-05T19:22:28.928Z',
- method: 'GET',
- url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css',
- status: 200,
- mimeType: 'text/css',
- requestSentTime: 18098833.175,
- requestStartTime: 18098835.439,
- loadEndTime: 18098957.145,
- timings: {
- connect: 81.10800000213203,
- wait: 34.577999998873565,
- receive: 0.5520000013348181,
- send: 0.3600000018195715,
- total: 123.97000000055414,
- proxy: -1,
- blocked: 0.8540000017092098,
- queueing: 2.263999998831423,
- ssl: 55.38700000033714,
- dns: 3.559999997378327,
- },
- },
- {
- timestamp: '2021-01-05T19:22:28.928Z',
- method: 'GET',
- url: 'https://unpkg.com/director@1.2.8/build/director.js',
- status: 200,
- mimeType: 'application/javascript',
- requestSentTime: 18098833.537,
- requestStartTime: 18098837.233999997,
- loadEndTime: 18098977.648000002,
- timings: {
- blocked: 84.54599999822676,
- receive: 3.068000001803739,
- queueing: 3.69700000010198,
- proxy: -1,
- total: 144.1110000014305,
- wait: 52.56100000042352,
- connect: -1,
- send: 0.2390000008745119,
- ssl: -1,
- dns: -1,
- },
- },
- ];
-
- const networkItemsWithoutFullTimings: NetworkItems = [
- networkItems[0],
- {
- timestamp: '2021-01-05T19:22:28.928Z',
- method: 'GET',
- url: 'file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js',
- status: 0,
- mimeType: 'text/javascript',
- requestSentTime: 18098834.097,
- loadEndTime: 18098836.889999997,
- timings: {
- total: 2.7929999996558763,
- blocked: -1,
- ssl: -1,
- wait: -1,
- connect: -1,
- dns: -1,
- queueing: -1,
- send: -1,
- proxy: -1,
- receive: -1,
- },
- },
- ];
-
- const networkItemsWithoutAnyTimings: NetworkItems = [
- {
- timestamp: '2021-01-05T19:22:28.928Z',
- method: 'GET',
- url: 'file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js',
- status: 0,
- mimeType: 'text/javascript',
- requestSentTime: 18098834.097,
- loadEndTime: 18098836.889999997,
- timings: {
- total: -1,
- blocked: -1,
- ssl: -1,
- wait: -1,
- connect: -1,
- dns: -1,
- queueing: -1,
- send: -1,
- proxy: -1,
- receive: -1,
- },
- },
- ];
-
- const networkItemsWithoutTimingsObject: NetworkItems = [
- {
- timestamp: '2021-01-05T19:22:28.928Z',
- method: 'GET',
- url: 'file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js',
- status: 0,
- mimeType: 'text/javascript',
- requestSentTime: 18098834.097,
- loadEndTime: 18098836.889999997,
- },
- ];
-
- const networkItemsWithUncommonMimeType: NetworkItems = [
- {
- timestamp: '2021-01-05T19:22:28.928Z',
- method: 'GET',
- url: 'https://unpkg.com/director@1.2.8/build/director.js',
- status: 200,
- mimeType: 'application/x-javascript',
- requestSentTime: 18098833.537,
- requestStartTime: 18098837.233999997,
- loadEndTime: 18098977.648000002,
- timings: {
- blocked: 84.54599999822676,
- receive: 3.068000001803739,
- queueing: 3.69700000010198,
- proxy: -1,
- total: 144.1110000014305,
- wait: 52.56100000042352,
- connect: -1,
- send: 0.2390000008745119,
- ssl: -1,
- dns: -1,
- },
- },
- ];
-
it('formats timings', () => {
const actual = getSeriesAndDomain(networkItems);
expect(actual).toMatchInlineSnapshot(`
@@ -175,6 +175,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#dcd4c4",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#dcd4c4",
@@ -188,6 +189,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#54b399",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#54b399",
@@ -201,6 +203,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#da8b45",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#da8b45",
@@ -214,6 +217,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#edc5a2",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#edc5a2",
@@ -227,6 +231,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#d36086",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#d36086",
@@ -240,6 +245,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#b0c9e0",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#b0c9e0",
@@ -253,6 +259,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#ca8eae",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#ca8eae",
@@ -266,6 +273,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#dcd4c4",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#dcd4c4",
@@ -279,6 +287,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#d36086",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#d36086",
@@ -292,6 +301,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#b0c9e0",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#b0c9e0",
@@ -305,6 +315,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#9170b8",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#9170b8",
@@ -316,6 +327,7 @@ describe('getSeriesAndDomain', () => {
"y0": 137.70799999925657,
},
],
+ "totalHighlightedRequests": 2,
}
`);
});
@@ -332,6 +344,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#dcd4c4",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#dcd4c4",
@@ -345,6 +358,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#54b399",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#54b399",
@@ -358,6 +372,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#da8b45",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#da8b45",
@@ -371,6 +386,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#edc5a2",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#edc5a2",
@@ -384,6 +400,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#d36086",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#d36086",
@@ -397,6 +414,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#b0c9e0",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#b0c9e0",
@@ -410,6 +428,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#ca8eae",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#ca8eae",
@@ -423,6 +442,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "#9170b8",
+ "isHighlighted": true,
"showTooltip": true,
"tooltipProps": Object {
"colour": "#9170b8",
@@ -434,6 +454,7 @@ describe('getSeriesAndDomain', () => {
"y0": 0.9219999983906746,
},
],
+ "totalHighlightedRequests": 2,
}
`);
});
@@ -450,6 +471,7 @@ describe('getSeriesAndDomain', () => {
Object {
"config": Object {
"colour": "",
+ "isHighlighted": true,
"showTooltip": false,
"tooltipProps": undefined,
},
@@ -458,6 +480,7 @@ describe('getSeriesAndDomain', () => {
"y0": 0,
},
],
+ "totalHighlightedRequests": 1,
}
`);
});
@@ -473,6 +496,7 @@ describe('getSeriesAndDomain', () => {
"series": Array [
Object {
"config": Object {
+ "isHighlighted": true,
"showTooltip": false,
},
"x": 0,
@@ -480,6 +504,7 @@ describe('getSeriesAndDomain', () => {
"y0": 0,
},
],
+ "totalHighlightedRequests": 1,
}
`);
});
@@ -501,4 +526,41 @@ describe('getSeriesAndDomain', () => {
});
expect(contentDownloadedingConfigItem).toBeDefined();
});
+
+ it('counts the total number of highlighted items', () => {
+ // only one CSS file in this array of network Items
+ const actual = getSeriesAndDomain(networkItems, false, '', ['stylesheet']);
+ expect(actual.totalHighlightedRequests).toBe(1);
+ });
+
+ it('adds isHighlighted to waterfall entry when filter matches', () => {
+ // only one CSS file in this array of network Items
+ const { series } = getSeriesAndDomain(networkItems, false, '', ['stylesheet']);
+ series.forEach((item) => {
+ if (item.x === 0) {
+ expect(item.config.isHighlighted).toBe(true);
+ } else {
+ expect(item.config.isHighlighted).toBe(false);
+ }
+ });
+ });
+
+ it('adds isHighlighted to waterfall entry when query matches', () => {
+ // only the second item matches this query
+ const { series } = getSeriesAndDomain(networkItems, false, 'director', []);
+ series.forEach((item) => {
+ if (item.x === 1) {
+ expect(item.config.isHighlighted).toBe(true);
+ } else {
+ expect(item.config.isHighlighted).toBe(false);
+ }
+ });
+ });
+});
+
+describe('getSidebarItems', () => {
+ it('passes the item index offset by 1 to offsetIndex for visual display', () => {
+ const actual = getSidebarItems(networkItems, false, '', []);
+ expect(actual[0].offsetIndex).toBe(1);
+ });
});
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts
index 0ac93794594c0..46f0d23d0a6b9 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts
@@ -55,8 +55,28 @@ const getFriendlyTooltipValue = ({
}
return `${label}: ${formatValueForDisplay(value)}ms`;
};
+export const isHighlightedItem = (
+ item: NetworkItem,
+ query?: string,
+ activeFilters: string[] = []
+) => {
+ if (!query && activeFilters?.length === 0) {
+ return true;
+ }
+
+ const matchQuery = query ? item.url?.includes(query) : true;
+ const matchFilters =
+ activeFilters.length > 0 ? activeFilters.includes(MimeTypesMap[item.mimeType!]) : true;
+
+ return !!(matchQuery && matchFilters);
+};
-export const getSeriesAndDomain = (items: NetworkItems) => {
+export const getSeriesAndDomain = (
+ items: NetworkItems,
+ onlyHighlighted = false,
+ query?: string,
+ activeFilters?: string[]
+) => {
const getValueForOffset = (item: NetworkItem) => {
return item.requestSentTime;
};
@@ -78,13 +98,21 @@ export const getSeriesAndDomain = (items: NetworkItems) => {
}
};
+ let totalHighlightedRequests = 0;
+
const series = items.reduce((acc, item, index) => {
+ const isHighlighted = isHighlightedItem(item, query, activeFilters);
+ if (isHighlighted) {
+ totalHighlightedRequests++;
+ }
+
if (!item.timings) {
acc.push({
x: index,
y0: 0,
y: 0,
config: {
+ isHighlighted,
showTooltip: false,
},
});
@@ -96,10 +124,13 @@ export const getSeriesAndDomain = (items: NetworkItems) => {
let currentOffset = offsetValue - zeroOffset;
+ let timingValueFound = false;
+
TIMING_ORDER.forEach((timing) => {
const value = getValue(item.timings, timing);
- const colour = timing === Timings.Receive ? mimeTypeColour : colourPalette[timing];
if (value && value >= 0) {
+ timingValueFound = true;
+ const colour = timing === Timings.Receive ? mimeTypeColour : colourPalette[timing];
const y = currentOffset + value;
acc.push({
@@ -108,6 +139,7 @@ export const getSeriesAndDomain = (items: NetworkItems) => {
y,
config: {
colour,
+ isHighlighted,
showTooltip: true,
tooltipProps: {
value: getFriendlyTooltipValue({
@@ -126,7 +158,7 @@ export const getSeriesAndDomain = (items: NetworkItems) => {
/* if no specific timing values are found, use the total time
* if total time is not available use 0, set showTooltip to false,
* and omit tooltip props */
- if (!acc.find((entry) => entry.x === index)) {
+ if (!timingValueFound) {
const total = item.timings.total;
const hasTotal = total !== -1;
acc.push({
@@ -134,6 +166,7 @@ export const getSeriesAndDomain = (items: NetworkItems) => {
y0: hasTotal ? currentOffset : 0,
y: hasTotal ? currentOffset + item.timings.total : 0,
config: {
+ isHighlighted,
colour: hasTotal ? mimeTypeColour : '',
showTooltip: hasTotal,
tooltipProps: hasTotal
@@ -154,14 +187,31 @@ export const getSeriesAndDomain = (items: NetworkItems) => {
const yValues = series.map((serie) => serie.y);
const domain = { min: 0, max: Math.max(...yValues) };
- return { series, domain };
+
+ let filteredSeries = series;
+ if (onlyHighlighted) {
+ filteredSeries = series.filter((item) => item.config.isHighlighted);
+ }
+
+ return { series: filteredSeries, domain, totalHighlightedRequests };
};
-export const getSidebarItems = (items: NetworkItems): SidebarItems => {
- return items.map((item) => {
+export const getSidebarItems = (
+ items: NetworkItems,
+ onlyHighlighted: boolean,
+ query: string,
+ activeFilters: string[]
+): SidebarItems => {
+ const sideBarItems = items.map((item, index) => {
+ const isHighlighted = isHighlightedItem(item, query, activeFilters);
+ const offsetIndex = index + 1;
const { url, status, method } = item;
- return { url, status, method };
+ return { url, status, method, isHighlighted, offsetIndex };
});
+ if (onlyHighlighted) {
+ return sideBarItems.filter((item) => item.isHighlighted);
+ }
+ return sideBarItems;
};
export const getLegendItems = (): LegendItems => {
@@ -184,6 +234,7 @@ export const getLegendItems = (): LegendItems => {
{ name: FriendlyMimetypeLabels[mimeType], colour: MIME_TYPE_PALETTE[mimeType] },
];
});
+
return [...timingItems, ...mimeTypeItems];
};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts
index 8d261edc74bf4..e22caae0d9eb2 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts
@@ -61,16 +61,13 @@ export const TIMING_ORDER = [
Timings.Receive,
] as const;
-export type CalculatedTimings = {
- [K in Timings]?: number;
-};
-
export enum MimeType {
Html = 'html',
Script = 'script',
Stylesheet = 'stylesheet',
Media = 'media',
Font = 'font',
+ XHR = 'xhr',
Other = 'other',
}
@@ -99,6 +96,9 @@ export const FriendlyMimetypeLabels = {
[MimeType.Font]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.font', {
defaultMessage: 'Font',
}),
+ [MimeType.XHR]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.xhr', {
+ defaultMessage: 'XHR',
+ }),
[MimeType.Other]: i18n.translate(
'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.other',
{
@@ -112,7 +112,6 @@ export const FriendlyMimetypeLabels = {
export const MimeTypesMap: Record = {
'text/html': MimeType.Html,
'application/javascript': MimeType.Script,
- 'application/json': MimeType.Script,
'text/javascript': MimeType.Script,
'text/css': MimeType.Stylesheet,
// Images
@@ -146,38 +145,18 @@ export const MimeTypesMap: Record = {
'application/font-woff2': MimeType.Font,
'application/vnd.ms-fontobject': MimeType.Font,
'application/font-sfnt': MimeType.Font,
+
+ // XHR
+ 'application/json': MimeType.XHR,
};
export type NetworkItem = NetworkEvent;
export type NetworkItems = NetworkItem[];
-// NOTE: A number will always be present if the property exists, but that number might be -1, which represents no value.
-export interface PayloadTimings {
- dns_start: number;
- push_end: number;
- worker_fetch_start: number;
- worker_respond_with_settled: number;
- proxy_end: number;
- worker_start: number;
- worker_ready: number;
- send_end: number;
- connect_end: number;
- connect_start: number;
- send_start: number;
- proxy_start: number;
- push_start: number;
- ssl_end: number;
- receive_headers_end: number;
- ssl_start: number;
- request_time: number;
- dns_end: number;
-}
-
-export interface ExtraSeriesConfig {
- colour: string;
-}
-
-export type SidebarItem = Pick;
+export type SidebarItem = Pick & {
+ isHighlighted: boolean;
+ offsetIndex: number;
+};
export type SidebarItems = SidebarItem[];
export interface LegendItem {
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx
new file mode 100644
index 0000000000000..e22f4a4c63f59
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx
@@ -0,0 +1,248 @@
+/*
+ * 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 { act, fireEvent } from '@testing-library/react';
+import { WaterfallChartWrapper } from './waterfall_chart_wrapper';
+
+import { render } from '../../../../../lib/helper/rtl_helpers';
+
+import { extractItems, isHighlightedItem } from './data_formatting';
+
+import 'jest-canvas-mock';
+import { BAR_HEIGHT } from '../../waterfall/components/constants';
+import { MimeType } from './types';
+import {
+ FILTER_POPOVER_OPEN_LABEL,
+ FILTER_REQUESTS_LABEL,
+ FILTER_COLLAPSE_REQUESTS_LABEL,
+} from '../../waterfall/components/translations';
+
+const getHighLightedItems = (query: string, filters: string[]) => {
+ return NETWORK_EVENTS.events.filter((item) => isHighlightedItem(item, query, filters));
+};
+
+describe('waterfall chart wrapper', () => {
+ jest.useFakeTimers();
+
+ it('renders the correct sidebar items', () => {
+ const { getAllByTestId } = render(
+
+ );
+
+ const sideBarItems = getAllByTestId('middleTruncatedTextSROnly');
+
+ expect(sideBarItems).toHaveLength(5);
+ });
+
+ it('search by query works', () => {
+ const { getAllByTestId, getByTestId, getByLabelText } = render(
+
+ );
+
+ const filterInput = getByLabelText(FILTER_REQUESTS_LABEL);
+
+ const searchText = '.js';
+
+ fireEvent.change(filterInput, { target: { value: searchText } });
+
+ // inout has debounce effect so hence the timer
+ act(() => {
+ jest.advanceTimersByTime(300);
+ });
+
+ const highlightedItemsLength = getHighLightedItems(searchText, []).length;
+ expect(getAllByTestId('sideBarHighlightedItem')).toHaveLength(highlightedItemsLength);
+
+ expect(getAllByTestId('sideBarDimmedItem')).toHaveLength(
+ NETWORK_EVENTS.events.length - highlightedItemsLength
+ );
+
+ const SIDE_BAR_ITEMS_HEIGHT = NETWORK_EVENTS.events.length * BAR_HEIGHT;
+ expect(getByTestId('wfSidebarContainer')).toHaveAttribute('height', `${SIDE_BAR_ITEMS_HEIGHT}`);
+
+ expect(getByTestId('wfDataOnlyBarChart')).toHaveAttribute('height', `${SIDE_BAR_ITEMS_HEIGHT}`);
+ });
+
+ it('search by mime type works', () => {
+ const { getAllByTestId, getByLabelText, getAllByText } = render(
+
+ );
+
+ const sideBarItems = getAllByTestId('middleTruncatedTextSROnly');
+
+ expect(sideBarItems).toHaveLength(5);
+
+ fireEvent.click(getByLabelText(FILTER_POPOVER_OPEN_LABEL));
+
+ fireEvent.click(getAllByText('XHR')[1]);
+
+ // inout has debounce effect so hence the timer
+ act(() => {
+ jest.advanceTimersByTime(300);
+ });
+
+ const highlightedItemsLength = getHighLightedItems('', [MimeType.XHR]).length;
+
+ expect(getAllByTestId('sideBarHighlightedItem')).toHaveLength(highlightedItemsLength);
+ expect(getAllByTestId('sideBarDimmedItem')).toHaveLength(
+ NETWORK_EVENTS.events.length - highlightedItemsLength
+ );
+ });
+
+ it('renders sidebar even when filter matches 0 resources', () => {
+ const { getAllByTestId, getByLabelText, getAllByText, queryAllByTestId } = render(
+
+ );
+
+ const sideBarItems = getAllByTestId('middleTruncatedTextSROnly');
+
+ expect(sideBarItems).toHaveLength(5);
+
+ fireEvent.click(getByLabelText(FILTER_POPOVER_OPEN_LABEL));
+
+ fireEvent.click(getAllByText('CSS')[1]);
+
+ // inout has debounce effect so hence the timer
+ act(() => {
+ jest.advanceTimersByTime(300);
+ });
+
+ const highlightedItemsLength = getHighLightedItems('', [MimeType.Stylesheet]).length;
+
+ // no CSS items found
+ expect(queryAllByTestId('sideBarHighlightedItem')).toHaveLength(0);
+ expect(getAllByTestId('sideBarDimmedItem')).toHaveLength(
+ NETWORK_EVENTS.events.length - highlightedItemsLength
+ );
+
+ fireEvent.click(getByLabelText(FILTER_COLLAPSE_REQUESTS_LABEL));
+
+ // filter bar is still accessible even when no resources match filter
+ expect(getByLabelText(FILTER_REQUESTS_LABEL)).toBeInTheDocument();
+
+ // no resources items are in the chart as none match filter
+ expect(queryAllByTestId('sideBarHighlightedItem')).toHaveLength(0);
+ expect(queryAllByTestId('sideBarDimmedItem')).toHaveLength(0);
+ });
+});
+
+const NETWORK_EVENTS = {
+ events: [
+ {
+ timestamp: '2021-01-21T10:31:21.537Z',
+ method: 'GET',
+ url:
+ 'https://apv-static.minute.ly/videos/v-c2a526c7-450d-428e-1244649-a390-fb639ffead96-s45.746-54.421m.mp4',
+ status: 206,
+ mimeType: 'video/mp4',
+ requestSentTime: 241114127.474,
+ requestStartTime: 241114129.214,
+ loadEndTime: 241116573.402,
+ timings: {
+ total: 2445.928000001004,
+ queueing: 1.7399999778717756,
+ blocked: 0.391999987186864,
+ receive: 2283.964000031119,
+ connect: 91.5709999972023,
+ wait: 28.795999998692423,
+ proxy: -1,
+ dns: 36.952000024029985,
+ send: 0.10000000474974513,
+ ssl: 64.28900000173599,
+ },
+ },
+ {
+ timestamp: '2021-01-21T10:31:22.174Z',
+ method: 'GET',
+ url: 'https://dpm.demdex.net/ibs:dpid=73426&dpuuid=31597189268188866891125449924942215949',
+ status: 200,
+ mimeType: 'image/gif',
+ requestSentTime: 241114749.202,
+ requestStartTime: 241114750.426,
+ loadEndTime: 241114805.541,
+ timings: {
+ queueing: 1.2240000069141388,
+ receive: 2.218999987235293,
+ proxy: -1,
+ dns: -1,
+ send: 0.14200000441633165,
+ blocked: 1.033000007737428,
+ total: 56.33900000248104,
+ wait: 51.72099999617785,
+ ssl: -1,
+ connect: -1,
+ },
+ },
+ {
+ timestamp: '2021-01-21T10:31:21.679Z',
+ method: 'GET',
+ url: 'https://dapi.cms.mlbinfra.com/v2/content/en-us/sel-t119-homepage-mediawall',
+ status: 200,
+ mimeType: 'application/json',
+ requestSentTime: 241114268.04299998,
+ requestStartTime: 241114270.184,
+ loadEndTime: 241114665.609,
+ timings: {
+ total: 397.5659999996424,
+ dns: 29.5429999823682,
+ wait: 221.6830000106711,
+ queueing: 2.1410000044852495,
+ connect: 106.95499999565072,
+ ssl: 69.06899999012239,
+ receive: 2.027999988058582,
+ blocked: 0.877000013133511,
+ send: 23.719999997410923,
+ proxy: -1,
+ },
+ },
+ {
+ timestamp: '2021-01-21T10:31:21.740Z',
+ method: 'GET',
+ url: 'https://platform.twitter.com/embed/embed.runtime.b313577971db9c857801.js',
+ status: 200,
+ mimeType: 'application/javascript',
+ requestSentTime: 241114303.84899998,
+ requestStartTime: 241114306.416,
+ loadEndTime: 241114370.361,
+ timings: {
+ send: 1.357000001007691,
+ wait: 40.12299998430535,
+ receive: 16.78500001435168,
+ ssl: -1,
+ queueing: 2.5670000177342445,
+ total: 66.51200001942925,
+ connect: -1,
+ blocked: 5.680000002030283,
+ proxy: -1,
+ dns: -1,
+ },
+ },
+ {
+ timestamp: '2021-01-21T10:31:21.740Z',
+ method: 'GET',
+ url: 'https://platform.twitter.com/embed/embed.modules.7a266e7acfd42f2581a5.js',
+ status: 200,
+ mimeType: 'application/javascript',
+ requestSentTime: 241114305.939,
+ requestStartTime: 241114310.393,
+ loadEndTime: 241114938.264,
+ timings: {
+ wait: 51.61500000394881,
+ dns: -1,
+ ssl: -1,
+ receive: 506.5750000067055,
+ proxy: -1,
+ connect: -1,
+ blocked: 69.51599998865277,
+ queueing: 4.453999979887158,
+ total: 632.324999984121,
+ send: 0.16500000492669642,
+ },
+ },
+ ],
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx
index 91657981e7f89..8a0e9729a635b 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx
@@ -5,44 +5,14 @@
* 2.0.
*/
-import React, { useMemo, useState } from 'react';
-import { EuiHealth, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui';
+import React, { useCallback, useMemo, useState } from 'react';
+import { EuiHealth } from '@elastic/eui';
+import { useTrackMetric, METRIC_TYPE } from '../../../../../../../observability/public';
import { getSeriesAndDomain, getSidebarItems, getLegendItems } from './data_formatting';
import { SidebarItem, LegendItem, NetworkItems } from './types';
-import {
- WaterfallProvider,
- WaterfallChart,
- MiddleTruncatedText,
- RenderItem,
-} from '../../waterfall';
-
-export const renderSidebarItem: RenderItem = (item, index) => {
- const { status } = item;
-
- const isErrorStatusCode = (statusCode: number) => {
- const is400 = statusCode >= 400 && statusCode <= 499;
- const is500 = statusCode >= 500 && statusCode <= 599;
- const isSpecific300 = statusCode === 301 || statusCode === 307 || statusCode === 308;
- return is400 || is500 || isSpecific300;
- };
-
- return (
- <>
- {!status || !isErrorStatusCode(status) ? (
-
- ) : (
-
-
-
-
-
- {status}
-
-
- )}
- >
- );
-};
+import { WaterfallProvider, WaterfallChart, RenderItem } from '../../waterfall';
+import { WaterfallFilter } from './waterfall_filter';
+import { WaterfallSidebarItem } from './waterfall_sidebar_item';
export const renderLegendItem: RenderItem = (item) => {
return {item.name};
@@ -54,23 +24,64 @@ interface Props {
}
export const WaterfallChartWrapper: React.FC = ({ data, total }) => {
+ const [query, setQuery] = useState('');
+ const [activeFilters, setActiveFilters] = useState([]);
+ const [onlyHighlighted, setOnlyHighlighted] = useState(false);
+
const [networkData] = useState(data);
- const { series, domain } = useMemo(() => {
- return getSeriesAndDomain(networkData);
- }, [networkData]);
+ const hasFilters = activeFilters.length > 0;
+
+ const { series, domain, totalHighlightedRequests } = useMemo(() => {
+ return getSeriesAndDomain(networkData, onlyHighlighted, query, activeFilters);
+ }, [networkData, query, activeFilters, onlyHighlighted]);
const sidebarItems = useMemo(() => {
- return getSidebarItems(networkData);
- }, [networkData]);
+ return getSidebarItems(networkData, onlyHighlighted, query, activeFilters);
+ }, [networkData, query, activeFilters, onlyHighlighted]);
const legendItems = getLegendItems();
+ const renderFilter = useCallback(() => {
+ return (
+
+ );
+ }, [activeFilters, setActiveFilters, onlyHighlighted, setOnlyHighlighted, query, setQuery]);
+
+ const renderSidebarItem: RenderItem = useCallback(
+ (item) => {
+ return (
+
+ );
+ },
+ [hasFilters, onlyHighlighted]
+ );
+
+ useTrackMetric({ app: 'uptime', metric: 'waterfall_chart_view', metricType: METRIC_TYPE.COUNT });
+ useTrackMetric({
+ app: 'uptime',
+ metric: 'waterfall_chart_view',
+ metricType: METRIC_TYPE.COUNT,
+ delay: 15000,
+ });
+
return (
{
@@ -81,10 +92,19 @@ export const WaterfallChartWrapper: React.FC = ({ data, total }) => {
tickFormat={(d: number) => `${Number(d).toFixed(0)} ms`}
domain={domain}
barStyleAccessor={(datum) => {
+ if (!datum.datum.config.isHighlighted) {
+ return {
+ rect: {
+ fill: datum.datum.config.colour,
+ opacity: '0.1',
+ },
+ };
+ }
return datum.datum.config.colour;
}}
renderSidebarItem={renderSidebarItem}
renderLegendItem={renderLegendItem}
+ renderFilter={renderFilter}
fullHeight={true}
/>
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx
new file mode 100644
index 0000000000000..3acf6a269fb38
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx
@@ -0,0 +1,155 @@
+/*
+ * 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, { useState } from 'react';
+import { act, fireEvent } from '@testing-library/react';
+
+import { render } from '../../../../../lib/helper/rtl_helpers';
+
+import 'jest-canvas-mock';
+import { MIME_FILTERS, WaterfallFilter } from './waterfall_filter';
+import {
+ FILTER_REQUESTS_LABEL,
+ FILTER_COLLAPSE_REQUESTS_LABEL,
+ FILTER_POPOVER_OPEN_LABEL,
+} from '../../waterfall/components/translations';
+
+describe('waterfall filter', () => {
+ jest.useFakeTimers();
+
+ it('renders correctly', () => {
+ const { getByLabelText, getByTitle } = render(
+
+ );
+
+ fireEvent.click(getByLabelText(FILTER_POPOVER_OPEN_LABEL));
+
+ MIME_FILTERS.forEach((filter) => {
+ expect(getByTitle(filter.label));
+ });
+ });
+
+ it('filter icon changes color on active/inactive filters', () => {
+ const Component = () => {
+ const [activeFilters, setActiveFilters] = useState([]);
+
+ return (
+
+ );
+ };
+ const { getByLabelText, getByTitle } = render();
+
+ fireEvent.click(getByLabelText(FILTER_POPOVER_OPEN_LABEL));
+
+ fireEvent.click(getByTitle('XHR'));
+
+ expect(getByLabelText(FILTER_POPOVER_OPEN_LABEL)).toHaveAttribute(
+ 'class',
+ 'euiButtonIcon euiButtonIcon--primary'
+ );
+
+ // toggle it back to inactive
+ fireEvent.click(getByTitle('XHR'));
+
+ expect(getByLabelText(FILTER_POPOVER_OPEN_LABEL)).toHaveAttribute(
+ 'class',
+ 'euiButtonIcon euiButtonIcon--text'
+ );
+ });
+
+ it('search input is working properly', () => {
+ const setQuery = jest.fn();
+
+ const Component = () => {
+ return (
+
+ );
+ };
+ const { getByLabelText } = render();
+
+ const testText = 'js';
+
+ fireEvent.change(getByLabelText(FILTER_REQUESTS_LABEL), { target: { value: testText } });
+
+ // inout has debounce effect so hence the timer
+ act(() => {
+ jest.advanceTimersByTime(300);
+ });
+
+ expect(setQuery).toHaveBeenCalledWith(testText);
+ });
+
+ it('resets checkbox when filters are removed', () => {
+ const Component = () => {
+ const [onlyHighlighted, setOnlyHighlighted] = useState(false);
+ const [query, setQuery] = useState('');
+ const [activeFilters, setActiveFilters] = useState([]);
+ return (
+
+ );
+ };
+ const { getByLabelText, getByTitle } = render();
+ const input = getByLabelText(FILTER_REQUESTS_LABEL);
+ // apply filters
+ const testText = 'js';
+ fireEvent.change(input, { target: { value: testText } });
+ fireEvent.click(getByLabelText(FILTER_POPOVER_OPEN_LABEL));
+ const filterGroupButton = getByTitle('XHR');
+ fireEvent.click(filterGroupButton);
+
+ // input has debounce effect so hence the timer
+ act(() => {
+ jest.advanceTimersByTime(300);
+ });
+
+ const collapseCheckbox = getByLabelText(FILTER_COLLAPSE_REQUESTS_LABEL) as HTMLInputElement;
+ expect(collapseCheckbox).not.toBeDisabled();
+ fireEvent.click(collapseCheckbox);
+ expect(collapseCheckbox).toBeChecked();
+
+ // remove filters
+ fireEvent.change(input, { target: { value: '' } });
+ fireEvent.click(filterGroupButton);
+
+ // input has debounce effect so hence the timer
+ act(() => {
+ jest.advanceTimersByTime(300);
+ });
+
+ // expect the checkbox to reset to disabled and unchecked
+ expect(collapseCheckbox).not.toBeChecked();
+ expect(collapseCheckbox).toBeDisabled();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx
new file mode 100644
index 0000000000000..42c2df4553b4c
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx
@@ -0,0 +1,188 @@
+/*
+ * 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, { Dispatch, SetStateAction, useEffect, useState } from 'react';
+import {
+ EuiButtonIcon,
+ EuiCheckbox,
+ EuiFieldSearch,
+ EuiFilterButton,
+ EuiFilterGroup,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPopover,
+ EuiSpacer,
+} from '@elastic/eui';
+import useDebounce from 'react-use/lib/useDebounce';
+import {
+ FILTER_REQUESTS_LABEL,
+ FILTER_SCREENREADER_LABEL,
+ FILTER_REMOVE_SCREENREADER_LABEL,
+ FILTER_POPOVER_OPEN_LABEL,
+ FILTER_COLLAPSE_REQUESTS_LABEL,
+} from '../../waterfall/components/translations';
+import { MimeType, FriendlyMimetypeLabels } from './types';
+import { METRIC_TYPE, useUiTracker } from '../../../../../../../observability/public';
+
+interface Props {
+ query: string;
+ activeFilters: string[];
+ setActiveFilters: Dispatch>;
+ setQuery: (val: string) => void;
+ onlyHighlighted: boolean;
+ setOnlyHighlighted: (val: boolean) => void;
+}
+
+export const MIME_FILTERS = [
+ {
+ label: FriendlyMimetypeLabels[MimeType.XHR],
+ mimeType: MimeType.XHR,
+ },
+ {
+ label: FriendlyMimetypeLabels[MimeType.Html],
+ mimeType: MimeType.Html,
+ },
+ {
+ label: FriendlyMimetypeLabels[MimeType.Script],
+ mimeType: MimeType.Script,
+ },
+ {
+ label: FriendlyMimetypeLabels[MimeType.Stylesheet],
+ mimeType: MimeType.Stylesheet,
+ },
+ {
+ label: FriendlyMimetypeLabels[MimeType.Font],
+ mimeType: MimeType.Font,
+ },
+ {
+ label: FriendlyMimetypeLabels[MimeType.Media],
+ mimeType: MimeType.Media,
+ },
+];
+
+export const WaterfallFilter = ({
+ query,
+ setQuery,
+ activeFilters,
+ setActiveFilters,
+ onlyHighlighted,
+ setOnlyHighlighted,
+}: Props) => {
+ const [value, setValue] = useState(query);
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+ const trackMetric = useUiTracker({ app: 'uptime' });
+
+ const toggleFilters = (val: string) => {
+ setActiveFilters((prevState) =>
+ prevState.includes(val) ? prevState.filter((filter) => filter !== val) : [...prevState, val]
+ );
+ };
+ useDebounce(
+ () => {
+ setQuery(value);
+ },
+ 250,
+ [value]
+ );
+
+ /* reset checkbox when there is no query or active filters
+ * this prevents the checkbox from being checked in a disabled state */
+ useEffect(() => {
+ if (!(query || activeFilters.length > 0)) {
+ setOnlyHighlighted(false);
+ }
+ }, [activeFilters.length, setOnlyHighlighted, query]);
+
+ // indicates use of the query input box
+ useEffect(() => {
+ if (query) {
+ trackMetric({ metric: 'waterfall_filter_input_changed', metricType: METRIC_TYPE.CLICK });
+ }
+ }, [query, trackMetric]);
+
+ // indicates the collapse to show only highlighted checkbox has been clicked
+ useEffect(() => {
+ if (onlyHighlighted) {
+ trackMetric({
+ metric: 'waterfall_filter_collapse_checked',
+ metricType: METRIC_TYPE.CLICK,
+ });
+ }
+ }, [onlyHighlighted, trackMetric]);
+
+ // indicates filters have been applied or changed
+ useEffect(() => {
+ if (activeFilters.length > 0) {
+ trackMetric({
+ metric: `waterfall_filters_applied_changed`,
+ metricType: METRIC_TYPE.CLICK,
+ });
+ }
+ }, [activeFilters, trackMetric]);
+
+ return (
+
+
+ {
+ setValue(evt.target.value);
+ }}
+ value={value}
+ />
+
+
+ setIsPopoverOpen((prevState) => !prevState)}
+ color={activeFilters.length > 0 ? 'primary' : 'text'}
+ isSelected={activeFilters.length > 0}
+ />
+ }
+ isOpen={isPopoverOpen}
+ closePopover={() => setIsPopoverOpen(false)}
+ anchorPosition="rightCenter"
+ >
+
+ {MIME_FILTERS.map(({ label, mimeType }) => (
+ toggleFilters(mimeType)}
+ key={label}
+ withNext={true}
+ aria-label={`${
+ activeFilters.includes(mimeType)
+ ? FILTER_REMOVE_SCREENREADER_LABEL
+ : FILTER_SCREENREADER_LABEL
+ } ${label}`}
+ >
+ {label}
+
+ ))}
+
+
+ 0)}
+ id="onlyHighlighted"
+ label={FILTER_COLLAPSE_REQUESTS_LABEL}
+ checked={onlyHighlighted}
+ onChange={(e) => {
+ setOnlyHighlighted(e.target.checked);
+ }}
+ />
+
+
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_sidebar_item.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_sidebar_item.tsx
new file mode 100644
index 0000000000000..25b577ef9403a
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_sidebar_item.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui';
+import { SidebarItem } from '../waterfall/types';
+import { MiddleTruncatedText } from '../../waterfall';
+import { SideBarItemHighlighter } from '../../waterfall/components/styles';
+import { SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL } from '../../waterfall/components/translations';
+
+interface SidebarItemProps {
+ item: SidebarItem;
+ renderFilterScreenReaderText?: boolean;
+}
+
+export const WaterfallSidebarItem = ({ item, renderFilterScreenReaderText }: SidebarItemProps) => {
+ const { status, offsetIndex, isHighlighted } = item;
+
+ const isErrorStatusCode = (statusCode: number) => {
+ const is400 = statusCode >= 400 && statusCode <= 499;
+ const is500 = statusCode >= 500 && statusCode <= 599;
+ const isSpecific300 = statusCode === 301 || statusCode === 307 || statusCode === 308;
+ return is400 || is500 || isSpecific300;
+ };
+
+ const text = `${offsetIndex}. ${item.url}`;
+ const ariaLabel = `${
+ isHighlighted && renderFilterScreenReaderText
+ ? `${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} `
+ : ''
+ }${text}`;
+
+ return (
+
+ {!status || !isErrorStatusCode(status) ? (
+
+ ) : (
+
+
+
+
+
+ {status}
+
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfalll_sidebar_item.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfalll_sidebar_item.test.tsx
new file mode 100644
index 0000000000000..578d66a1ea3f1
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfalll_sidebar_item.test.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 { SidebarItem } from '../waterfall/types';
+
+import { render } from '../../../../../lib/helper/rtl_helpers';
+
+import 'jest-canvas-mock';
+import { WaterfallSidebarItem } from './waterfall_sidebar_item';
+import { SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL } from '../../waterfall/components/translations';
+
+describe('waterfall filter', () => {
+ const url = 'http://www.elastic.co';
+ const offsetIndex = 1;
+ const item: SidebarItem = {
+ url,
+ isHighlighted: true,
+ offsetIndex,
+ };
+
+ it('renders sidbar item', () => {
+ const { getByText } = render();
+
+ expect(getByText(`${offsetIndex}. ${url}`));
+ });
+
+ it('render screen reader text when renderFilterScreenReaderText is true', () => {
+ const { getByLabelText } = render(
+
+ );
+
+ expect(
+ getByLabelText(`${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} ${offsetIndex}. ${url}`)
+ ).toBeInTheDocument();
+ });
+
+ it('does not render screen reader text when renderFilterScreenReaderText is false', () => {
+ const { queryByLabelText } = render(
+
+ );
+
+ expect(
+ queryByLabelText(`${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} ${offsetIndex}. ${url}`)
+ ).not.toBeInTheDocument();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts
index 543d6004b8955..a4b75174543a8 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts
@@ -17,3 +17,5 @@ export const FIXED_AXIS_HEIGHT = 32;
// number of items to display in canvas, since canvas can only have limited size
export const CANVAS_MAX_ITEMS = 150;
+
+export const CHART_LEGEND_PADDING = 62;
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx
index 9a3d4efb63a3a..d6c1d777a40a7 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx
@@ -25,15 +25,21 @@ describe('getChunks', () => {
});
describe('Component', () => {
- it('renders truncated text', () => {
- const { getByText } = render();
+ it('renders truncated text and aria label', () => {
+ const { getByText, getByLabelText } = render(
+
+ );
expect(getByText(first)).toBeInTheDocument();
expect(getByText(last)).toBeInTheDocument();
+
+ expect(getByLabelText(longString)).toBeInTheDocument();
});
it('renders screen reader only text', () => {
- const { getByTestId } = render();
+ const { getByTestId } = render(
+
+ );
const { getByText } = within(getByTestId('middleTruncatedTextSROnly'));
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx
index 9c263312f78f5..ec363ed2b40a4 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx
@@ -10,6 +10,11 @@ import styled from 'styled-components';
import { EuiScreenReaderOnly, EuiToolTip } from '@elastic/eui';
import { FIXED_AXIS_HEIGHT } from './constants';
+interface Props {
+ ariaLabel: string;
+ text: string;
+}
+
const OuterContainer = styled.div`
width: 100%;
height: 100%;
@@ -50,14 +55,14 @@ export const getChunks = (text: string) => {
// Helper component for adding middle text truncation, e.g.
// really-really-really-long....ompressed.js
// Can be used to accomodate content in sidebar item rendering.
-export const MiddleTruncatedText = ({ text }: { text: string }) => {
+export const MiddleTruncatedText = ({ ariaLabel, text }: Props) => {
const chunks = useMemo(() => {
return getChunks(text);
}, [text]);
return (
<>
-
+
{text}
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.test.tsx
index f46bab8c33a85..63b4d2945a51c 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.test.tsx
@@ -12,7 +12,11 @@ import { render } from '../../../../../lib/helper/rtl_helpers';
describe('NetworkRequestsTotal', () => {
it('message in case total is greater than fetched', () => {
const { getByText, getByLabelText } = render(
-
+
);
expect(getByText('First 1000/1100 network requests')).toBeInTheDocument();
@@ -21,9 +25,52 @@ describe('NetworkRequestsTotal', () => {
it('message in case total is equal to fetched requests', () => {
const { getByText } = render(
-
+
);
expect(getByText('500 network requests')).toBeInTheDocument();
});
+
+ it('does not show highlighted item message when showHighlightedNetworkEvents is false', () => {
+ const { queryByText } = render(
+
+ );
+
+ expect(queryByText(/match the filter/)).not.toBeInTheDocument();
+ });
+
+ it('does not show highlighted item message when highlightedNetworkEvents is less than 0', () => {
+ const { queryByText } = render(
+
+ );
+
+ expect(queryByText(/match the filter/)).not.toBeInTheDocument();
+ });
+
+ it('show highlighted item message when highlightedNetworkEvents is greater than 0 and showHighlightedNetworkEvents is true', () => {
+ const { getByText } = render(
+
+ );
+
+ expect(getByText(/\(20 match the filter\)/)).toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.tsx
index fce86c6b5c29d..5ccd60b0ce7a8 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/network_requests_total.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiIconTip } from '@elastic/eui';
import { NetworkRequestsTotalStyle } from './styles';
@@ -13,24 +14,44 @@ import { NetworkRequestsTotalStyle } from './styles';
interface Props {
totalNetworkRequests: number;
fetchedNetworkRequests: number;
+ highlightedNetworkRequests: number;
+ showHighlightedNetworkRequests?: boolean;
}
-export const NetworkRequestsTotal = ({ totalNetworkRequests, fetchedNetworkRequests }: Props) => {
+export const NetworkRequestsTotal = ({
+ totalNetworkRequests,
+ fetchedNetworkRequests,
+ highlightedNetworkRequests,
+ showHighlightedNetworkRequests,
+}: Props) => {
return (
- {i18n.translate('xpack.uptime.synthetics.waterfall.requestsTotalMessage', {
- defaultMessage: '{numNetworkRequests} network requests',
- values: {
+ fetchedNetworkRequests
- ? i18n.translate('xpack.uptime.synthetics.waterfall.requestsTotalMessage.first', {
- defaultMessage: 'First {count}',
- values: { count: `${fetchedNetworkRequests}/${totalNetworkRequests}` },
- })
- : totalNetworkRequests,
- },
- })}
+ totalNetworkRequests > fetchedNetworkRequests ? (
+
+ ) : (
+ totalNetworkRequests
+ ),
+ }}
+ />{' '}
+ {showHighlightedNetworkRequests && highlightedNetworkRequests >= 0 && (
+
+ )}
{totalNetworkRequests > fetchedNetworkRequests && (
= ({ items, render }) => {
return (
-
+
- {items.map((item, index) => {
- return (
-
- {render(item, index)}
-
- );
- })}
+ {items.map((item) => (
+
+ {render(item)}
+
+ ))}
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts
index 333acd6e043df..9177902f8a613 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts
@@ -5,19 +5,18 @@
* 2.0.
*/
-import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiPanelProps } from '@elastic/eui';
import { rgba } from 'polished';
-import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
+import { FunctionComponent } from 'react';
+import { StyledComponent } from 'styled-components';
+import { euiStyled, EuiTheme } from '../../../../../../../../../src/plugins/kibana_react/common';
import { FIXED_AXIS_HEIGHT } from './constants';
interface WaterfallChartOuterContainerProps {
height?: string;
}
-export const WaterfallChartOuterContainer = euiStyled.div`
- height: ${(props) => (props.height ? `${props.height}` : 'auto')};
- overflow-y: ${(props) => (props.height ? 'scroll' : 'visible')};
- overflow-x: hidden;
+const StyledScrollDiv = euiStyled.div`
&::-webkit-scrollbar {
height: ${({ theme }) => theme.eui.euiScrollBar};
width: ${({ theme }) => theme.eui.euiScrollBar};
@@ -33,22 +32,50 @@ export const WaterfallChartOuterContainer = euiStyled.div`
+ height: ${(props) => (props.height ? `${props.height}` : 'auto')};
+ overflow-y: ${(props) => (props.height ? 'scroll' : 'visible')};
+ overflow-x: hidden;
+`;
+
+export const WaterfallChartFixedTopContainer = euiStyled(StyledScrollDiv)`
position: sticky;
top: 0;
z-index: ${(props) => props.theme.eui.euiZLevel4};
- border-bottom: ${(props) => `1px solid ${props.theme.eui.euiColorLightShade}`};
+ overflow-y: scroll;
+ overflow-x: hidden;
`;
-export const WaterfallChartFixedTopContainerSidebarCover = euiStyled(EuiPanel)`
+export const WaterfallChartAxisOnlyContainer = euiStyled(EuiFlexItem)`
+ margin-left: -22px;
+`;
+
+export const WaterfallChartTopContainer = euiStyled(EuiFlexGroup)`
+`;
+
+export const WaterfallChartFixedTopContainerSidebarCover: StyledComponent<
+ FunctionComponent,
+ EuiTheme
+> = euiStyled(EuiPanel)`
height: 100%;
border-radius: 0 !important;
border: none;
`; // NOTE: border-radius !important is here as the "border" prop isn't working
+export const WaterfallChartFilterContainer = euiStyled.div`
+ && {
+ padding: 16px;
+ z-index: ${(props) => props.theme.eui.euiZLevel5};
+ border-bottom: 0.3px solid ${(props) => props.theme.eui.euiColorLightShade};
+ }
+`; // NOTE: border-radius !important is here as the "border" prop isn't working
+
export const WaterfallChartFixedAxisContainer = euiStyled.div`
height: ${FIXED_AXIS_HEIGHT}px;
z-index: ${(props) => props.theme.eui.euiZLevel4};
+ height: 100%;
`;
interface WaterfallChartSidebarContainer {
@@ -60,7 +87,10 @@ export const WaterfallChartSidebarContainer = euiStyled.div,
+ EuiTheme
+> = euiStyled(EuiPanel)`
border: 0;
height: 100%;
`;
@@ -74,6 +104,12 @@ export const WaterfallChartSidebarFlexItem = euiStyled(EuiFlexItem)`
min-width: 0;
padding-left: ${(props) => props.theme.eui.paddingSizes.m};
padding-right: ${(props) => props.theme.eui.paddingSizes.m};
+ z-index: ${(props) => props.theme.eui.euiZLevel4};
+`;
+
+export const SideBarItemHighlighter = euiStyled.span<{ isHighlighted: boolean }>`
+ opacity: ${(props) => (props.isHighlighted ? 1 : 0.4)};
+ height: 100%;
`;
interface WaterfallChartChartContainer {
@@ -106,6 +142,12 @@ export const WaterfallChartTooltip = euiStyled.div`
`;
export const NetworkRequestsTotalStyle = euiStyled(EuiText)`
- line-height: ${FIXED_AXIS_HEIGHT}px;
- margin-left: ${(props) => props.theme.eui.paddingSizes.m}
+ line-height: 28px;
+ padding: 0 ${(props) => props.theme.eui.paddingSizes.m};
+ border-bottom: 0.3px solid ${(props) => props.theme.eui.euiColorLightShade};
+ z-index: ${(props) => props.theme.eui.euiZLevel5};
+`;
+
+export const RelativeContainer = euiStyled.div`
+ position: relative;
`;
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/translations.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/translations.ts
new file mode 100644
index 0000000000000..b63ffacaadd2e
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/translations.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const FILTER_REQUESTS_LABEL = i18n.translate(
+ 'xpack.uptime.synthetics.waterfall.searchBox.placeholder',
+ {
+ defaultMessage: 'Filter network requests',
+ }
+);
+
+export const FILTER_SCREENREADER_LABEL = i18n.translate(
+ 'xpack.uptime.synthetics.waterfall.filterGroup.filterScreenreaderLabel',
+ {
+ defaultMessage: 'Filter by',
+ }
+);
+
+export const FILTER_REMOVE_SCREENREADER_LABEL = i18n.translate(
+ 'xpack.uptime.synthetics.waterfall.filterGroup.removeFilterScreenReaderLabel',
+ {
+ defaultMessage: 'Remove filter by',
+ }
+);
+
+export const FILTER_POPOVER_OPEN_LABEL = i18n.translate(
+ 'xpack.uptime.pingList.synthetics.waterfall.filters.popover',
+ {
+ defaultMessage: 'Click to open waterfall filters',
+ }
+);
+
+export const FILTER_COLLAPSE_REQUESTS_LABEL = i18n.translate(
+ 'xpack.uptime.pingList.synthetics.waterfall.filters.collapseRequestsLabel',
+ {
+ defaultMessage: 'Collapse to only show matching requests',
+ }
+);
+
+export const SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL = i18n.translate(
+ 'xpack.uptime.synthetics.waterfall.sidebar.filterMatchesScreenReaderLabel',
+ {
+ defaultMessage: 'Resource matches filter',
+ }
+);
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.test.tsx
index 1ce46fc0d6e7b..a963fb1e2939c 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.test.tsx
@@ -10,9 +10,14 @@ import { renderHook } from '@testing-library/react-hooks';
import { IWaterfallContext } from '../context/waterfall_chart';
import { CANVAS_MAX_ITEMS } from './constants';
-const generateTestData = (): IWaterfallContext['data'] => {
+const generateTestData = (
+ {
+ xMultiplier,
+ }: {
+ xMultiplier: number;
+ } = { xMultiplier: 1 }
+): IWaterfallContext['data'] => {
const numberOfItems = 1000;
-
const data: IWaterfallContext['data'] = [];
const testItem = {
x: 0,
@@ -29,11 +34,11 @@ const generateTestData = (): IWaterfallContext['data'] => {
data.push(
{
...testItem,
- x: i,
+ x: xMultiplier * i,
},
{
...testItem,
- x: i,
+ x: xMultiplier * i,
y0: 7,
y: 25,
}
@@ -44,7 +49,7 @@ const generateTestData = (): IWaterfallContext['data'] => {
};
describe('useBarChartsHooks', () => {
- it('returns result as expected', () => {
+ it('returns result as expected for non filtered data', () => {
const { result, rerender } = renderHook((props) => useBarCharts(props), {
initialProps: { data: [] as IWaterfallContext['data'] },
});
@@ -70,4 +75,35 @@ describe('useBarChartsHooks', () => {
expect(lastChartItems[0].x).toBe(CANVAS_MAX_ITEMS * 4);
expect(lastChartItems[lastChartItems.length - 1].x).toBe(CANVAS_MAX_ITEMS * 5 - 1);
});
+
+ it('returns result as expected for filtered data', () => {
+ /* multiply x values to simulate filtered data, where x values can have gaps in the
+ * sequential order */
+ const xMultiplier = 2;
+ const { result, rerender } = renderHook((props) => useBarCharts(props), {
+ initialProps: { data: [] as IWaterfallContext['data'] },
+ });
+
+ expect(result.current).toHaveLength(0);
+ const newData = generateTestData({ xMultiplier });
+
+ rerender({ data: newData });
+
+ // Thousands items will result in 7 Canvas
+ expect(result.current.length).toBe(7);
+
+ const firstChartItems = result.current[0];
+ const lastChartItems = result.current[4];
+
+ // first chart items last item should be x 149, since we only display 150 items
+ expect(firstChartItems[firstChartItems.length - 1].x).toBe(
+ (CANVAS_MAX_ITEMS - 1) * xMultiplier
+ );
+
+ // since here are 5 charts, last chart first item should be x 600
+ expect(lastChartItems[0].x).toBe(CANVAS_MAX_ITEMS * 4 * xMultiplier);
+ expect(lastChartItems[lastChartItems.length - 1].x).toBe(
+ (CANVAS_MAX_ITEMS * 5 - 1) * xMultiplier
+ );
+ });
});
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.ts
index 79fd437039afe..2baf895504911 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.ts
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_bar_charts.ts
@@ -13,27 +13,36 @@ export interface UseBarHookProps {
data: IWaterfallContext['data'];
}
-export const useBarCharts = ({ data = [] }: UseBarHookProps) => {
+export const useBarCharts = ({ data }: UseBarHookProps) => {
const [charts, setCharts] = useState>([]);
useEffect(() => {
- if (data.length > 0) {
- let chartIndex = 0;
-
- const chartsN: Array = [];
+ const chartsN: Array = [];
+ if (data?.length > 0) {
+ let chartIndex = 0;
+ /* We want at most CANVAS_MAX_ITEMS **RESOURCES** per array.
+ * Resources !== individual timing items, but are comprised of many individual timing
+ * items. The X value of each item can be used as an id for the resource.
+ * We must keep track of the number of unique resources added to the each array. */
+ const uniqueResources = new Set();
+ let lastIndex: number;
data.forEach((item) => {
- // Subtract 1 to account for x value starting from 0
- if (item.x === CANVAS_MAX_ITEMS * chartIndex && !chartsN[item.x / CANVAS_MAX_ITEMS]) {
- chartsN.push([item]);
+ if (uniqueResources.size === CANVAS_MAX_ITEMS && item.x > lastIndex) {
chartIndex++;
+ uniqueResources.clear();
+ }
+ uniqueResources.add(item.x);
+ lastIndex = item.x;
+ if (!chartsN[chartIndex]) {
+ chartsN.push([item]);
return;
}
- chartsN[chartIndex - 1].push(item);
+ chartsN[chartIndex].push(item);
});
-
- setCharts(chartsN);
}
+
+ setCharts(chartsN);
}, [data]);
return charts;
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall.test.tsx
index 7c9051e8f6acf..528d749f576fc 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall.test.tsx
@@ -6,64 +6,38 @@
*/
import React from 'react';
-import { of } from 'rxjs';
-import { MountWithReduxProvider, mountWithRouter } from '../../../../../lib';
-import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public';
import { WaterfallChart } from './waterfall_chart';
-import {
- renderLegendItem,
- renderSidebarItem,
-} from '../../step_detail/waterfall/waterfall_chart_wrapper';
-import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common';
-import { WaterfallChartOuterContainer } from './styles';
+import { renderLegendItem } from '../../step_detail/waterfall/waterfall_chart_wrapper';
+import { render } from '../../../../../lib/helper/rtl_helpers';
+
+import 'jest-canvas-mock';
describe('waterfall', () => {
it('sets the correct height in case of full height', () => {
- const core = mockCore();
-
const Component = () => {
return (
- `${Number(d).toFixed(0)} ms`}
- domain={{
- max: 3371,
- min: 0,
- }}
- barStyleAccessor={(datum) => {
- return datum.datum.config.colour;
- }}
- renderSidebarItem={renderSidebarItem}
- renderLegendItem={renderLegendItem}
- fullHeight={true}
- />
+
+ `${Number(d).toFixed(0)} ms`}
+ domain={{
+ max: 3371,
+ min: 0,
+ }}
+ barStyleAccessor={(datum) => {
+ return datum.datum.config.colour;
+ }}
+ renderSidebarItem={undefined}
+ renderLegendItem={renderLegendItem}
+ fullHeight={true}
+ />
+
);
};
- const component = mountWithRouter(
-
-
-
-
-
-
-
- );
+ const { getByTestId } = render();
- const chartWrapper = component.find(WaterfallChartOuterContainer);
+ const chartWrapper = getByTestId('waterfallOuterContainer');
- expect(chartWrapper.get(0).props.height).toBe('calc(100vh - 0px)');
+ expect(chartWrapper).toHaveStyleRule('height', 'calc(100vh - 62px)');
});
});
-
-const mockCore: () => any = () => {
- return {
- application: {
- getUrlForApp: () => '/app/uptime',
- navigateToUrl: jest.fn(),
- },
- uiSettings: {
- get: (key: string) => 'MMM D, YYYY @ HH:mm:ss.SSS',
- get$: (key: string) => of('MMM D, YYYY @ HH:mm:ss.SSS'),
- },
- };
-};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_bar_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_bar_chart.tsx
new file mode 100644
index 0000000000000..df00df147fc6c
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_bar_chart.tsx
@@ -0,0 +1,112 @@
+/*
+ * 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 {
+ Axis,
+ BarSeries,
+ BarStyleAccessor,
+ Chart,
+ DomainRange,
+ Position,
+ ScaleType,
+ Settings,
+ TickFormatter,
+ TooltipInfo,
+} from '@elastic/charts';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { BAR_HEIGHT } from './constants';
+import { useChartTheme } from '../../../../../hooks/use_chart_theme';
+import { WaterfallChartChartContainer, WaterfallChartTooltip } from './styles';
+import { useWaterfallContext, WaterfallData } from '..';
+
+const getChartHeight = (data: WaterfallData): number => {
+ // We get the last item x(number of bars) and adds 1 to cater for 0 index
+ const noOfXBars = new Set(data.map((item) => item.x)).size;
+
+ return noOfXBars * BAR_HEIGHT;
+};
+
+const Tooltip = (tooltipInfo: TooltipInfo) => {
+ const { data, renderTooltipItem } = useWaterfallContext();
+ const relevantItems = data.filter((item) => {
+ return (
+ item.x === tooltipInfo.header?.value && item.config.showTooltip && item.config.tooltipProps
+ );
+ });
+ return relevantItems.length ? (
+
+
+ {relevantItems.map((item, index) => {
+ return (
+ {renderTooltipItem(item.config.tooltipProps)}
+ );
+ })}
+
+
+ ) : null;
+};
+
+interface Props {
+ index: number;
+ chartData: WaterfallData;
+ tickFormat: TickFormatter;
+ domain: DomainRange;
+ barStyleAccessor: BarStyleAccessor;
+}
+
+export const WaterfallBarChart = ({
+ chartData,
+ tickFormat,
+ domain,
+ barStyleAccessor,
+ index,
+}: Props) => {
+ const theme = useChartTheme();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx
index 8f831d0629b25..e0e5165b41e49 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx
@@ -5,62 +5,30 @@
* 2.0.
*/
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import {
- Axis,
- BarSeries,
- Chart,
- Position,
- ScaleType,
- Settings,
- TickFormatter,
- DomainRange,
- BarStyleAccessor,
- TooltipInfo,
- TooltipType,
-} from '@elastic/charts';
-import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
-// NOTE: The WaterfallChart has a hard requirement that consumers / solutions are making use of KibanaReactContext, and useKibana etc
-// can therefore be accessed.
-import { useUiSetting$ } from '../../../../../../../../../src/plugins/kibana_react/public';
+import { TickFormatter, DomainRange, BarStyleAccessor } from '@elastic/charts';
+
import { useWaterfallContext } from '../context/waterfall_chart';
import {
WaterfallChartOuterContainer,
WaterfallChartFixedTopContainer,
WaterfallChartFixedTopContainerSidebarCover,
- WaterfallChartFixedAxisContainer,
- WaterfallChartChartContainer,
- WaterfallChartTooltip,
+ WaterfallChartTopContainer,
+ RelativeContainer,
+ WaterfallChartFilterContainer,
+ WaterfallChartAxisOnlyContainer,
} from './styles';
-import { WaterfallData } from '../types';
-import { BAR_HEIGHT, CANVAS_MAX_ITEMS, MAIN_GROW_SIZE, SIDEBAR_GROW_SIZE } from './constants';
+import { CHART_LEGEND_PADDING, MAIN_GROW_SIZE, SIDEBAR_GROW_SIZE } from './constants';
import { Sidebar } from './sidebar';
import { Legend } from './legend';
import { useBarCharts } from './use_bar_charts';
+import { WaterfallBarChart } from './waterfall_bar_chart';
+import { WaterfallChartFixedAxis } from './waterfall_chart_fixed_axis';
import { NetworkRequestsTotal } from './network_requests_total';
-const Tooltip = (tooltipInfo: TooltipInfo) => {
- const { data, renderTooltipItem } = useWaterfallContext();
- const relevantItems = data.filter((item) => {
- return (
- item.x === tooltipInfo.header?.value && item.config.showTooltip && item.config.tooltipProps
- );
- });
- return relevantItems.length ? (
-
-
- {relevantItems.map((item, index) => {
- return (
- {renderTooltipItem(item.config.tooltipProps)}
- );
- })}
-
-
- ) : null;
-};
-
-export type RenderItem = (item: I, index: number) => JSX.Element;
+export type RenderItem = (item: I, index?: number) => JSX.Element;
+export type RenderFilter = () => JSX.Element;
export interface WaterfallChartProps {
tickFormat: TickFormatter;
@@ -68,159 +36,100 @@ export interface WaterfallChartProps {
barStyleAccessor: BarStyleAccessor;
renderSidebarItem?: RenderItem;
renderLegendItem?: RenderItem;
+ renderFilter?: RenderFilter;
maxHeight?: string;
fullHeight?: boolean;
}
-const getChartHeight = (data: WaterfallData, ind: number): number => {
- // We get the last item x(number of bars) and adds 1 to cater for 0 index
- return (data[data.length - 1]?.x + 1 - ind * CANVAS_MAX_ITEMS) * BAR_HEIGHT;
-};
-
export const WaterfallChart = ({
tickFormat,
domain,
barStyleAccessor,
renderSidebarItem,
renderLegendItem,
+ renderFilter,
maxHeight = '800px',
fullHeight = false,
}: WaterfallChartProps) => {
const {
data,
+ showOnlyHighlightedNetworkRequests,
sidebarItems,
legendItems,
totalNetworkRequests,
+ highlightedNetworkRequests,
fetchedNetworkRequests,
} = useWaterfallContext();
- const [darkMode] = useUiSetting$('theme:darkMode');
-
- const theme = useMemo(() => {
- return darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme;
- }, [darkMode]);
-
const chartWrapperDivRef = useRef(null);
const [height, setHeight] = useState(maxHeight);
- const shouldRenderSidebar = !!(sidebarItems && sidebarItems.length > 0 && renderSidebarItem);
+ const shouldRenderSidebar = !!(sidebarItems && renderSidebarItem);
const shouldRenderLegend = !!(legendItems && legendItems.length > 0 && renderLegendItem);
useEffect(() => {
if (fullHeight && chartWrapperDivRef.current) {
const chartOffset = chartWrapperDivRef.current.getBoundingClientRect().top;
- setHeight(`calc(100vh - ${chartOffset}px)`);
+ setHeight(`calc(100vh - ${chartOffset + CHART_LEGEND_PADDING}px)`);
}
}, [chartWrapperDivRef, fullHeight]);
const chartsToDisplay = useBarCharts({ data });
return (
-
- <>
-
-
- {shouldRenderSidebar && (
-
-
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {shouldRenderSidebar && (
+
+
+
+ {renderFilter && (
+ {renderFilter()}
+ )}
-
-
-
+ )}
+
+
+
+
+
+
+
+
{shouldRenderSidebar && }
-
+
+
{chartsToDisplay.map((chartData, ind) => (
-
-
-
-
-
-
-
-
-
+ chartData={chartData}
+ domain={domain}
+ barStyleAccessor={barStyleAccessor}
+ tickFormat={tickFormat}
+ />
))}
-
+
- {shouldRenderLegend && }
- >
-
+
+ {shouldRenderLegend && }
+
);
};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart_fixed_axis.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart_fixed_axis.tsx
new file mode 100644
index 0000000000000..3a7ab421b6277
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart_fixed_axis.tsx
@@ -0,0 +1,65 @@
+/*
+ * 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 {
+ Axis,
+ BarSeries,
+ BarStyleAccessor,
+ Chart,
+ DomainRange,
+ Position,
+ ScaleType,
+ Settings,
+ TickFormatter,
+ TooltipType,
+} from '@elastic/charts';
+import { useChartTheme } from '../../../../../hooks/use_chart_theme';
+import { WaterfallChartFixedAxisContainer } from './styles';
+
+interface Props {
+ tickFormat: TickFormatter;
+ domain: DomainRange;
+ barStyleAccessor: BarStyleAccessor;
+}
+
+export const WaterfallChartFixedAxis = ({ tickFormat, domain, barStyleAccessor }: Props) => {
+ const theme = useChartTheme();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx
index 68d24514a37d3..9e87d69ce38a8 100644
--- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx
@@ -7,12 +7,15 @@
import React, { createContext, useContext, Context } from 'react';
import { WaterfallData, WaterfallDataEntry } from '../types';
+import { SidebarItems } from '../../step_detail/waterfall/types';
export interface IWaterfallContext {
totalNetworkRequests: number;
+ highlightedNetworkRequests: number;
fetchedNetworkRequests: number;
data: WaterfallData;
- sidebarItems?: unknown[];
+ showOnlyHighlightedNetworkRequests: boolean;
+ sidebarItems?: SidebarItems;
legendItems?: unknown[];
renderTooltipItem: (
item: WaterfallDataEntry['config']['tooltipProps'],
@@ -24,8 +27,10 @@ export const WaterfallContext = createContext>({});
interface ProviderProps {
totalNetworkRequests: number;
+ highlightedNetworkRequests: number;
fetchedNetworkRequests: number;
data: IWaterfallContext['data'];
+ showOnlyHighlightedNetworkRequests: IWaterfallContext['showOnlyHighlightedNetworkRequests'];
sidebarItems?: IWaterfallContext['sidebarItems'];
legendItems?: IWaterfallContext['legendItems'];
renderTooltipItem: IWaterfallContext['renderTooltipItem'];
@@ -34,20 +39,24 @@ interface ProviderProps {
export const WaterfallProvider: React.FC = ({
children,
data,
+ showOnlyHighlightedNetworkRequests,
sidebarItems,
legendItems,
renderTooltipItem,
totalNetworkRequests,
+ highlightedNetworkRequests,
fetchedNetworkRequests,
}) => {
return (
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx
index ecc6231ba05fd..9ee6dc749b9eb 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/actions_popover/actions_popover.tsx
@@ -12,7 +12,7 @@ import { IntegrationGroup } from './integration_group';
import { MonitorSummary } from '../../../../../../common/runtime_types';
import { toggleIntegrationsPopover, PopoverState } from '../../../../../state/actions';
-interface ActionsPopoverProps {
+export interface ActionsPopoverProps {
summary: MonitorSummary;
popoverState: PopoverState | null;
togglePopoverIsVisible: typeof toggleIntegrationsPopover;
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/data.json b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/data.json
index 1bbdcd4a30078..905e982681dee 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/data.json
+++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/data.json
@@ -261,7 +261,25 @@
},
"state": {
"agent": null,
- "checks": ,
+ "checks": [
+ {
+ "agent": { "id": "8f9a37fb-573a-4fdc-9895-440a5b39c250", "__typename": "Agent" },
+ "container": null,
+ "kubernetes": null,
+ "monitor": {
+ "ip": "127.0.0.1",
+ "name": "localhost",
+ "status": "up",
+ "__typename": "CheckMonitor"
+ },
+ "observer": {
+ "geo": { "name": null, "location": null, "__typename": "CheckGeo" },
+ "__typename": "CheckObserver"
+ },
+ "timestamp": "1570538246143",
+ "__typename": "Check"
+ }
+ ],
"geo": null,
"observer": {
"geo": { "name": [], "location": null, "__typename": "StateGeo" },
diff --git a/x-pack/plugins/uptime/public/hooks/use_chart_theme.ts b/x-pack/plugins/uptime/public/hooks/use_chart_theme.ts
new file mode 100644
index 0000000000000..f9231abaa75a8
--- /dev/null
+++ b/x-pack/plugins/uptime/public/hooks/use_chart_theme.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
+import { useMemo } from 'react';
+import { useUiSetting$ } from '../../../../../src/plugins/kibana_react/public';
+
+export const useChartTheme = () => {
+ const [darkMode] = useUiSetting$('theme:darkMode');
+
+ const theme = useMemo(() => {
+ return darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme;
+ }, [darkMode]);
+
+ return theme;
+};
diff --git a/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx
index 9656c63274a13..4c81247fb2cf1 100644
--- a/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx
+++ b/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx
@@ -8,10 +8,17 @@
import React, { ReactElement } from 'react';
import { Router } from 'react-router-dom';
import { MemoryHistory } from 'history/createMemoryHistory';
-import { createMemoryHistory } from 'history';
+import { createMemoryHistory, History } from 'history';
import { mountWithIntl, renderWithIntl, shallowWithIntl } from '@kbn/test/jest';
import { MountWithReduxProvider } from './helper_with_redux';
import { AppState } from '../../state';
+import { mockState } from '../__mocks__/uptime_store.mock';
+import { KibanaProviderOptions, MockRouter } from './rtl_helpers';
+
+interface RenderRouterOptions extends KibanaProviderOptions {
+ history?: History;
+ state?: Partial;
+}
const helperWithRouter: (
helper: (node: ReactElement) => R,
@@ -67,3 +74,39 @@ export const mountWithRouterRedux = (
options?.storeState
);
};
+
+/* Custom enzyme render */
+export function render(
+ ui: ReactElement,
+ { history, core, kibanaProps, state }: RenderRouterOptions = {}
+) {
+ const testState: AppState = {
+ ...mockState,
+ ...state,
+ };
+ return renderWithIntl(
+
+
+ {ui}
+
+
+ );
+}
+
+/* Custom enzyme render */
+export function mount(
+ ui: ReactElement,
+ { history, core, kibanaProps, state }: RenderRouterOptions = {}
+) {
+ const testState: AppState = {
+ ...mockState,
+ ...state,
+ };
+ return mountWithIntl(
+
+
+ {ui}
+
+
+ );
+}
diff --git a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx
index abc0451bf8efa..e02a2c6f9832f 100644
--- a/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx
+++ b/x-pack/plugins/uptime/public/lib/helper/rtl_helpers.tsx
@@ -6,6 +6,7 @@
*/
import React, { ReactElement } from 'react';
+import { of } from 'rxjs';
import { render as reactTestLibRender, RenderOptions } from '@testing-library/react';
import { Router } from 'react-router-dom';
import { createMemoryHistory, History } from 'history';
@@ -26,7 +27,7 @@ interface KibanaProps {
services?: KibanaServices;
}
-interface KibanaProviderOptions {
+export interface KibanaProviderOptions {
core?: Partial & ExtraCore;
kibanaProps?: KibanaProps;
}
@@ -54,6 +55,11 @@ const mockCore: () => any = () => {
getUrlForApp: () => '/app/uptime',
navigateToUrl: jest.fn(),
},
+ uiSettings: {
+ get: (key: string) => 'MMM D, YYYY @ HH:mm:ss.SSS',
+ get$: (key: string) => of('MMM D, YYYY @ HH:mm:ss.SSS'),
+ },
+ usageCollection: { reportUiCounter: () => {} },
};
return core;
diff --git a/x-pack/plugins/uptime/public/state/alerts/alerts.ts b/x-pack/plugins/uptime/public/state/alerts/alerts.ts
index 4b48b157c3deb..f328bd5b9a5a7 100644
--- a/x-pack/plugins/uptime/public/state/alerts/alerts.ts
+++ b/x-pack/plugins/uptime/public/state/alerts/alerts.ts
@@ -53,7 +53,7 @@ export const deleteAnomalyAlertAction = createAsyncAction<{ alertId: string }, a
'DELETE ANOMALY ALERT'
);
-interface AlertState {
+export interface AlertState {
connectors: AsyncInitState;
newAlert: AsyncInitState>;
alerts: AsyncInitState;
diff --git a/x-pack/plugins/uptime/public/state/certificates/certificates.ts b/x-pack/plugins/uptime/public/state/certificates/certificates.ts
index d6d48f2ab7007..ca2d5e7a17a46 100644
--- a/x-pack/plugins/uptime/public/state/certificates/certificates.ts
+++ b/x-pack/plugins/uptime/public/state/certificates/certificates.ts
@@ -19,7 +19,7 @@ export const getCertificatesAction = createAsyncAction;
}
diff --git a/x-pack/plugins/uptime/public/state/index.ts b/x-pack/plugins/uptime/public/state/index.ts
index fa15e77f7fcc4..61b1a5f9d9527 100644
--- a/x-pack/plugins/uptime/public/state/index.ts
+++ b/x-pack/plugins/uptime/public/state/index.ts
@@ -5,17 +5,16 @@
* 2.0.
*/
-import { compose, createStore, applyMiddleware } from 'redux';
+import { createStore, applyMiddleware } from 'redux';
+import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import { rootEffect } from './effects';
import { rootReducer } from './reducers';
export type AppState = ReturnType;
-const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
-
const sagaMW = createSagaMiddleware();
-export const store = createStore(rootReducer, composeEnhancers(applyMiddleware(sagaMW)));
+export const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMW)));
sagaMW.run(rootEffect);
diff --git a/x-pack/plugins/uptime/public/state/reducers/journey.ts b/x-pack/plugins/uptime/public/state/reducers/journey.ts
index 273523f4592d6..361454e1b3fa1 100644
--- a/x-pack/plugins/uptime/public/state/reducers/journey.ts
+++ b/x-pack/plugins/uptime/public/state/reducers/journey.ts
@@ -24,7 +24,7 @@ export interface JourneyState {
error?: Error;
}
-interface JourneyKVP {
+export interface JourneyKVP {
[checkGroup: string]: JourneyState;
}
diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts
index 6310b79206a88..0c9f9dd849341 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts
@@ -10,7 +10,7 @@ import moment from 'moment';
import { schema } from '@kbn/config-schema';
import { ActionGroupIdsOf } from '../../../../alerts/common';
import { updateState } from './common';
-import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts';
+import { DURATION_ANOMALY } from '../../../common/constants/alerts';
import { commonStateTranslations, durationAnomalyTranslations } from './translations';
import { AnomaliesTableRecord } from '../../../../ml/common/types/anomalies';
import { getSeverityType } from '../../../../ml/common/util/anomaly_utils';
@@ -21,7 +21,6 @@ import { getMLJobId } from '../../../common/lib';
import { getLatestMonitor } from '../requests/get_latest_monitor';
import { uptimeAlertWrapper } from './uptime_alert_wrapper';
-const { DURATION_ANOMALY } = ACTION_GROUP_DEFINITIONS;
export type ActionGroupIds = ActionGroupIdsOf;
export const getAnomalySummary = (anomaly: AnomaliesTableRecord, monitorInfo: Ping) => {
diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
index cc1cb3a4ed0be..cee20d113c256 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
@@ -17,7 +17,7 @@ import {
Ping,
GetMonitorAvailabilityParams,
} from '../../../common/runtime_types';
-import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts';
+import { MONITOR_STATUS } from '../../../common/constants/alerts';
import { updateState } from './common';
import { commonMonitorStateI18, commonStateTranslations, DOWN_LABEL } from './translations';
import { stringifyKueries, combineFiltersAndUserSearch } from '../../../common/lib';
@@ -29,7 +29,6 @@ import { MonitorStatusTranslations } from '../../../common/translations';
import { getUptimeIndexPattern, IndexPatternTitleAndFields } from '../requests/get_index_pattern';
import { UMServerLibs, UptimeESClient } from '../lib';
-const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS;
export type ActionGroupIds = ActionGroupIdsOf;
const getMonIdByLoc = (monitorId: string, location: string) => {
diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts
index 345d2470ed705..7bc4c36b98e8b 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts
@@ -9,7 +9,7 @@ import moment from 'moment';
import { schema } from '@kbn/config-schema';
import { UptimeAlertTypeFactory } from './types';
import { updateState } from './common';
-import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts';
+import { TLS } from '../../../common/constants/alerts';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
import { Cert, CertResult } from '../../../common/runtime_types';
import { commonStateTranslations, tlsTranslations } from './translations';
@@ -17,7 +17,6 @@ import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs';
import { uptimeAlertWrapper } from './uptime_alert_wrapper';
import { ActionGroupIdsOf } from '../../../../alerts/common';
-const { TLS } = ACTION_GROUP_DEFINITIONS;
export type ActionGroupIds = ActionGroupIdsOf;
const DEFAULT_SIZE = 20;
diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts
index 53a79815a0c0f..5ac56d14c171d 100644
--- a/x-pack/plugins/uptime/server/lib/lib.ts
+++ b/x-pack/plugins/uptime/server/lib/lib.ts
@@ -27,7 +27,7 @@ export interface UMServerLibs extends UMDomainLibs {
framework: UMBackendFrameworkAdapter;
}
-interface CountResponse {
+export interface CountResponse {
body: {
count: number;
_shards: {
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts
index c942c3a8f69fd..e0edcc4576378 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts
@@ -8,7 +8,7 @@
import { UMElasticsearchQueryFn } from '../adapters/framework';
import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types';
-interface GetJourneyDetails {
+export interface GetJourneyDetails {
checkGroup: string;
}
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts
index 1abba0087cb44..9865bd95fe961 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts
@@ -8,7 +8,7 @@
import { UMElasticsearchQueryFn } from '../adapters/framework';
import { Ping } from '../../../common/runtime_types';
-interface GetJourneyStepsParams {
+export interface GetJourneyStepsParams {
checkGroups: string[];
}
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts
index ff9aec85e28bb..9cb5e1eedb6b0 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts
@@ -8,7 +8,7 @@
import { UMElasticsearchQueryFn } from '../adapters/framework';
import { Ping } from '../../../common/runtime_types/ping';
-interface GetJourneyScreenshotParams {
+export interface GetJourneyScreenshotParams {
checkGroup: string;
stepIndex: number;
}
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts
index d657b8b9aacf3..3055f169fc495 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts
@@ -8,7 +8,7 @@
import { UMElasticsearchQueryFn } from '../adapters/framework';
import { Ping } from '../../../common/runtime_types';
-interface GetJourneyStepsParams {
+export interface GetJourneyStepsParams {
checkGroup: string;
syntheticEventTypes?: string | string[];
}
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts
index f9936c6f273ba..fa76da0025305 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts
@@ -8,7 +8,7 @@
import { UMElasticsearchQueryFn } from '../adapters/framework';
import { NetworkEvent } from '../../../common/runtime_types';
-interface GetNetworkEventsParams {
+export interface GetNetworkEventsParams {
checkGroup: string;
stepIndex: string;
}
diff --git a/x-pack/plugins/uptime/server/lib/requests/helper.ts b/x-pack/plugins/uptime/server/lib/requests/helper.ts
index 2556d7b8fb8cd..e3969f84c8485 100644
--- a/x-pack/plugins/uptime/server/lib/requests/helper.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/helper.ts
@@ -5,14 +5,14 @@
* 2.0.
*/
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ElasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import {
elasticsearchServiceMock,
savedObjectsClientMock,
} from '../../../../../../src/core/server/mocks';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { ElasticsearchClientMock } from '../../../../../../src/core/server/elasticsearch/client/mocks';
-import { createUptimeESClient } from '../lib';
+import { createUptimeESClient, UptimeESClient } from '../lib';
export interface MultiPageCriteria {
after_key?: K;
@@ -60,7 +60,14 @@ export const setupMockEsCompositeQuery = (
return esMock;
};
-export const getUptimeESMockClient = (esClientMock?: ElasticsearchClientMock) => {
+interface UptimeEsMockClient {
+ esClient: ElasticsearchClientMock;
+ uptimeEsClient: UptimeESClient;
+}
+
+export const getUptimeESMockClient = (
+ esClientMock?: ElasticsearchClientMock
+): UptimeEsMockClient => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();
const savedObjectsClient = savedObjectsClientMock.create();
diff --git a/x-pack/plugins/uptime/tsconfig.json b/x-pack/plugins/uptime/tsconfig.json
new file mode 100644
index 0000000000000..5a195f6c2df25
--- /dev/null
+++ b/x-pack/plugins/uptime/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": [
+ "common/**/*",
+ "public/**/*",
+ "public/components/monitor/status_details/location_map/embeddables/low_poly_layer.json",
+ "server/**/*",
+ "server/lib/requests/__fixtures__/monitor_charts_mock.json",
+ "../../../typings/**/*"
+ ],
+ "references": [
+ { "path": "../alerts/tsconfig.json" },
+ { "path": "../ml/tsconfig.json" },
+ { "path": "../triggers_actions_ui/tsconfig.json" },
+ { "path": "../observability/tsconfig.json" }
+ ]
+}
diff --git a/x-pack/test/accessibility/apps/upgrade_assistant.ts b/x-pack/test/accessibility/apps/upgrade_assistant.ts
new file mode 100644
index 0000000000000..332a54006b0ec
--- /dev/null
+++ b/x-pack/test/accessibility/apps/upgrade_assistant.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { FtrProviderContext } from '../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const PageObjects = getPageObjects(['upgradeAssistant', 'common']);
+ const a11y = getService('a11y');
+ const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+
+ describe('Upgrade Assistant Home', () => {
+ before(async () => {
+ await PageObjects.upgradeAssistant.navigateToPage();
+ });
+
+ it('Overview Tab', async () => {
+ await retry.waitFor('Upgrade Assistant overview tab to be visible', async () => {
+ return testSubjects.exists('upgradeAssistantOverviewTabDetail');
+ });
+ await a11y.testAppSnapshot();
+ });
+
+ it('Cluster Tab', async () => {
+ await testSubjects.click('upgradeAssistantClusterTab');
+ await retry.waitFor('Upgrade Assistant Cluster tab to be visible', async () => {
+ return testSubjects.exists('upgradeAssistantClusterTabDetail');
+ });
+ await a11y.testAppSnapshot();
+ });
+
+ it('Indices Tab', async () => {
+ await testSubjects.click('upgradeAssistantIndicesTab');
+ await retry.waitFor('Upgrade Assistant Cluster tab to be visible', async () => {
+ return testSubjects.exists('upgradeAssistantIndexTabDetail');
+ });
+ await a11y.testAppSnapshot();
+ });
+ });
+}
diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts
index 94a09e3f767f6..24c46c1a1687e 100644
--- a/x-pack/test/accessibility/config.ts
+++ b/x-pack/test/accessibility/config.ts
@@ -31,6 +31,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./apps/index_lifecycle_management'),
require.resolve('./apps/ml'),
require.resolve('./apps/lens'),
+ require.resolve('./apps/upgrade_assistant'),
],
pageObjects,
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts
index 6cc5e2eaefb94..8bd0b8a790d40 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts
@@ -375,6 +375,34 @@ export default function jiraTest({ getService }: FtrProviderContext) {
});
});
});
+
+ it('should handle failing with a simulated success when labels containing a space', async () => {
+ await supertest
+ .post(`/api/actions/action/${simulatedActionId}/_execute`)
+ .set('kbn-xsrf', 'foo')
+ .send({
+ params: {
+ ...mockJira.params,
+ subActionParams: {
+ incident: {
+ ...mockJira.params.subActionParams.incident,
+ issueType: '10006',
+ labels: ['label with spaces'],
+ },
+ comments: [],
+ },
+ },
+ })
+ .then((resp: any) => {
+ expect(resp.body).to.eql({
+ actionId: simulatedActionId,
+ status: 'error',
+ retry: false,
+ message:
+ 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.labels]: types that failed validation:\n - [subActionParams.incident.labels.0.0]: The label label with spaces cannot contain spaces\n - [subActionParams.incident.labels.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]',
+ });
+ });
+ });
});
describe('Execution', () => {
diff --git a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts
index 2705406009062..39b343a361945 100644
--- a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts
+++ b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts
@@ -19,97 +19,97 @@ const EXPECTED_DATA = [
category: 'base',
field: '@timestamp',
values: ['2019-02-10T02:39:44.107Z'],
- originalValue: '2019-02-10T02:39:44.107Z',
+ originalValue: ['2019-02-10T02:39:44.107Z'],
},
{
category: '@version',
field: '@version',
values: ['1'],
- originalValue: '1',
+ originalValue: ['1'],
},
{
category: 'agent',
field: 'agent.ephemeral_id',
values: ['909cd6a1-527d-41a5-9585-a7fb5386f851'],
- originalValue: '909cd6a1-527d-41a5-9585-a7fb5386f851',
+ originalValue: ['909cd6a1-527d-41a5-9585-a7fb5386f851'],
},
{
category: 'agent',
field: 'agent.hostname',
values: ['raspberrypi'],
- originalValue: 'raspberrypi',
+ originalValue: ['raspberrypi'],
},
{
category: 'agent',
field: 'agent.id',
values: ['4d3ea604-27e5-4ec7-ab64-44f82285d776'],
- originalValue: '4d3ea604-27e5-4ec7-ab64-44f82285d776',
+ originalValue: ['4d3ea604-27e5-4ec7-ab64-44f82285d776'],
},
{
category: 'agent',
field: 'agent.type',
values: ['filebeat'],
- originalValue: 'filebeat',
+ originalValue: ['filebeat'],
},
{
category: 'agent',
field: 'agent.version',
values: ['7.0.0'],
- originalValue: '7.0.0',
+ originalValue: ['7.0.0'],
},
{
category: 'destination',
field: 'destination.domain',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
- originalValue: 's3-iad-2.cf.dash.row.aiv-cdn.net',
+ originalValue: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
},
{
category: 'destination',
field: 'destination.ip',
values: ['10.100.7.196'],
- originalValue: '10.100.7.196',
+ originalValue: ['10.100.7.196'],
},
{
category: 'destination',
field: 'destination.port',
- values: [40684],
- originalValue: 40684,
+ values: ['40684'],
+ originalValue: ['40684'],
},
{
category: 'ecs',
field: 'ecs.version',
values: ['1.0.0-beta2'],
- originalValue: '1.0.0-beta2',
+ originalValue: ['1.0.0-beta2'],
},
{
category: 'event',
field: 'event.dataset',
values: ['suricata.eve'],
- originalValue: 'suricata.eve',
+ originalValue: ['suricata.eve'],
},
{
category: 'event',
field: 'event.end',
values: ['2019-02-10T02:39:44.107Z'],
- originalValue: '2019-02-10T02:39:44.107Z',
+ originalValue: ['2019-02-10T02:39:44.107Z'],
},
{
category: 'event',
field: 'event.kind',
values: ['event'],
- originalValue: 'event',
+ originalValue: ['event'],
},
{
category: 'event',
field: 'event.module',
values: ['suricata'],
- originalValue: 'suricata',
+ originalValue: ['suricata'],
},
{
category: 'event',
field: 'event.type',
values: ['fileinfo'],
- originalValue: 'fileinfo',
+ originalValue: ['fileinfo'],
},
{
category: 'file',
@@ -117,260 +117,261 @@ const EXPECTED_DATA = [
values: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
- originalValue:
+ originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
+ ],
},
{
category: 'file',
field: 'file.size',
- values: [48277],
- originalValue: 48277,
+ values: ['48277'],
+ originalValue: ['48277'],
},
{
category: 'fileset',
field: 'fileset.name',
values: ['eve'],
- originalValue: 'eve',
+ originalValue: ['eve'],
},
{
category: 'flow',
field: 'flow.locality',
values: ['public'],
- originalValue: 'public',
+ originalValue: ['public'],
},
{
category: 'host',
field: 'host.architecture',
values: ['armv7l'],
- originalValue: 'armv7l',
+ originalValue: ['armv7l'],
},
{
category: 'host',
field: 'host.hostname',
values: ['raspberrypi'],
- originalValue: 'raspberrypi',
+ originalValue: ['raspberrypi'],
},
{
category: 'host',
field: 'host.id',
values: ['b19a781f683541a7a25ee345133aa399'],
- originalValue: 'b19a781f683541a7a25ee345133aa399',
+ originalValue: ['b19a781f683541a7a25ee345133aa399'],
},
{
category: 'host',
field: 'host.name',
values: ['raspberrypi'],
- originalValue: 'raspberrypi',
+ originalValue: ['raspberrypi'],
},
{
category: 'host',
field: 'host.os.codename',
values: ['stretch'],
- originalValue: 'stretch',
+ originalValue: ['stretch'],
},
{
category: 'host',
field: 'host.os.family',
values: [''],
- originalValue: '',
+ originalValue: [''],
},
{
category: 'host',
field: 'host.os.kernel',
values: ['4.14.50-v7+'],
- originalValue: '4.14.50-v7+',
+ originalValue: ['4.14.50-v7+'],
},
{
category: 'host',
field: 'host.os.name',
values: ['Raspbian GNU/Linux'],
- originalValue: 'Raspbian GNU/Linux',
+ originalValue: ['Raspbian GNU/Linux'],
},
{
category: 'host',
field: 'host.os.platform',
values: ['raspbian'],
- originalValue: 'raspbian',
+ originalValue: ['raspbian'],
},
{
category: 'host',
field: 'host.os.version',
values: ['9 (stretch)'],
- originalValue: '9 (stretch)',
+ originalValue: ['9 (stretch)'],
},
{
category: 'http',
field: 'http.request.method',
values: ['get'],
- originalValue: 'get',
+ originalValue: ['get'],
},
{
category: 'http',
field: 'http.response.body.bytes',
- values: [48277],
- originalValue: 48277,
+ values: ['48277'],
+ originalValue: ['48277'],
},
{
category: 'http',
field: 'http.response.status_code',
- values: [206],
- originalValue: 206,
+ values: ['206'],
+ originalValue: ['206'],
},
{
category: 'input',
field: 'input.type',
values: ['log'],
- originalValue: 'log',
+ originalValue: ['log'],
},
{
category: 'base',
field: 'labels.pipeline',
values: ['filebeat-7.0.0-suricata-eve-pipeline'],
- originalValue: 'filebeat-7.0.0-suricata-eve-pipeline',
+ originalValue: ['filebeat-7.0.0-suricata-eve-pipeline'],
},
{
category: 'log',
field: 'log.file.path',
values: ['/var/log/suricata/eve.json'],
- originalValue: '/var/log/suricata/eve.json',
+ originalValue: ['/var/log/suricata/eve.json'],
},
{
category: 'log',
field: 'log.offset',
- values: [1856288115],
- originalValue: 1856288115,
+ values: ['1856288115'],
+ originalValue: ['1856288115'],
},
{
category: 'network',
field: 'network.name',
values: ['iot'],
- originalValue: 'iot',
+ originalValue: ['iot'],
},
{
category: 'network',
field: 'network.protocol',
values: ['http'],
- originalValue: 'http',
+ originalValue: ['http'],
},
{
category: 'network',
field: 'network.transport',
values: ['tcp'],
- originalValue: 'tcp',
+ originalValue: ['tcp'],
},
{
category: 'service',
field: 'service.type',
values: ['suricata'],
- originalValue: 'suricata',
+ originalValue: ['suricata'],
},
{
category: 'source',
field: 'source.as.num',
- values: [16509],
- originalValue: 16509,
+ values: ['16509'],
+ originalValue: ['16509'],
},
{
category: 'source',
field: 'source.as.org',
values: ['Amazon.com, Inc.'],
- originalValue: 'Amazon.com, Inc.',
+ originalValue: ['Amazon.com, Inc.'],
},
{
category: 'source',
field: 'source.domain',
values: ['server-54-239-219-210.jfk51.r.cloudfront.net'],
- originalValue: 'server-54-239-219-210.jfk51.r.cloudfront.net',
+ originalValue: ['server-54-239-219-210.jfk51.r.cloudfront.net'],
},
{
category: 'source',
field: 'source.geo.city_name',
values: ['Seattle'],
- originalValue: 'Seattle',
+ originalValue: ['Seattle'],
},
{
category: 'source',
field: 'source.geo.continent_name',
values: ['North America'],
- originalValue: 'North America',
+ originalValue: ['North America'],
},
{
category: 'source',
field: 'source.geo.country_iso_code',
values: ['US'],
- originalValue: 'US',
+ originalValue: ['US'],
},
{
category: 'source',
field: 'source.geo.location.lat',
- values: [47.6103],
- originalValue: 47.6103,
+ values: ['47.6103'],
+ originalValue: ['47.6103'],
},
{
category: 'source',
field: 'source.geo.location.lon',
- values: [-122.3341],
- originalValue: -122.3341,
+ values: ['-122.3341'],
+ originalValue: ['-122.3341'],
},
{
category: 'source',
field: 'source.geo.region_iso_code',
values: ['US-WA'],
- originalValue: 'US-WA',
+ originalValue: ['US-WA'],
},
{
category: 'source',
field: 'source.geo.region_name',
values: ['Washington'],
- originalValue: 'Washington',
+ originalValue: ['Washington'],
},
{
category: 'source',
field: 'source.ip',
values: ['54.239.219.210'],
- originalValue: '54.239.219.210',
+ originalValue: ['54.239.219.210'],
},
{
category: 'source',
field: 'source.port',
- values: [80],
- originalValue: 80,
+ values: ['80'],
+ originalValue: ['80'],
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.state',
values: ['CLOSED'],
- originalValue: 'CLOSED',
+ originalValue: ['CLOSED'],
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.tx_id',
- values: [301],
- originalValue: 301,
+ values: ['301'],
+ originalValue: ['301'],
},
{
category: 'suricata',
field: 'suricata.eve.flow_id',
- values: [196625917175466],
- originalValue: 196625917175466,
+ values: ['196625917175466'],
+ originalValue: ['196625917175466'],
},
{
category: 'suricata',
field: 'suricata.eve.http.http_content_type',
values: ['video/mp4'],
- originalValue: 'video/mp4',
+ originalValue: ['video/mp4'],
},
{
category: 'suricata',
field: 'suricata.eve.http.protocol',
values: ['HTTP/1.1'],
- originalValue: 'HTTP/1.1',
+ originalValue: ['HTTP/1.1'],
},
{
category: 'suricata',
field: 'suricata.eve.in_iface',
values: ['eth0'],
- originalValue: 'eth0',
+ originalValue: ['eth0'],
},
{
category: 'base',
@@ -382,7 +383,7 @@ const EXPECTED_DATA = [
category: 'url',
field: 'url.domain',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
- originalValue: 's3-iad-2.cf.dash.row.aiv-cdn.net',
+ originalValue: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
},
{
category: 'url',
@@ -390,8 +391,9 @@ const EXPECTED_DATA = [
values: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
- originalValue:
+ originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
+ ],
},
{
category: 'url',
@@ -399,26 +401,27 @@ const EXPECTED_DATA = [
values: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
- originalValue:
+ originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
+ ],
},
{
category: '_index',
field: '_index',
values: ['filebeat-7.0.0-iot-2019.06'],
- originalValue: 'filebeat-7.0.0-iot-2019.06',
+ originalValue: ['filebeat-7.0.0-iot-2019.06'],
},
{
category: '_id',
field: '_id',
values: ['QRhG1WgBqd-n62SwZYDT'],
- originalValue: 'QRhG1WgBqd-n62SwZYDT',
+ originalValue: ['QRhG1WgBqd-n62SwZYDT'],
},
{
category: '_score',
field: '_score',
- values: [1],
- originalValue: 1,
+ values: ['1'],
+ originalValue: ['1'],
},
];
@@ -452,7 +455,6 @@ export default function ({ getService }: FtrProviderContext) {
eventId: ID,
})
.expect(200);
-
expect(sortBy(detailsData, 'name')).to.eql(sortBy(EXPECTED_DATA, 'name'));
});
diff --git a/x-pack/test/functional/apps/grok_debugger/grok_debugger.js b/x-pack/test/functional/apps/grok_debugger/grok_debugger.js
index 010341cedd3a7..b2a1c5363fcb6 100644
--- a/x-pack/test/functional/apps/grok_debugger/grok_debugger.js
+++ b/x-pack/test/functional/apps/grok_debugger/grok_debugger.js
@@ -12,8 +12,7 @@ export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['grokDebugger']);
- // FLAKY: https://github.com/elastic/kibana/issues/84440
- describe.skip('grok debugger app', function () {
+ describe('grok debugger app', function () {
this.tags('includeFirefox');
before(async () => {
await esArchiver.load('empty_kibana');
diff --git a/x-pack/test/functional/apps/lens/dashboard.ts b/x-pack/test/functional/apps/lens/dashboard.ts
index 738e45c1cbcf1..5cbd5dff45e1e 100644
--- a/x-pack/test/functional/apps/lens/dashboard.ts
+++ b/x-pack/test/functional/apps/lens/dashboard.ts
@@ -156,5 +156,43 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await panelActions.clickContextMenuMoreItem();
await testSubjects.existOrFail(ACTION_TEST_SUBJ);
});
+
+ it('unlink lens panel from embeddable library', async () => {
+ await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.clickNewDashboard();
+ await dashboardAddPanel.clickOpenAddPanel();
+ await dashboardAddPanel.filterEmbeddableNames('lnsPieVis');
+ await find.clickByButtonText('lnsPieVis');
+ await dashboardAddPanel.closeAddPanel();
+
+ const originalPanel = await testSubjects.find('embeddablePanelHeading-lnsPieVis');
+ await panelActions.unlinkFromLibary(originalPanel);
+ await testSubjects.existOrFail('unlinkPanelSuccess');
+
+ const updatedPanel = await testSubjects.find('embeddablePanelHeading-lnsPieVis');
+ const libraryActionExists = await testSubjects.descendantExists(
+ 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
+ updatedPanel
+ );
+ expect(libraryActionExists).to.be(false);
+ });
+
+ it('save lens panel to embeddable library', async () => {
+ const originalPanel = await testSubjects.find('embeddablePanelHeading-lnsPieVis');
+ await panelActions.saveToLibrary('lnsPieVis - copy', originalPanel);
+ await testSubjects.click('confirmSaveSavedObjectButton');
+ await testSubjects.existOrFail('addPanelToLibrarySuccess');
+
+ const updatedPanel = await testSubjects.find('embeddablePanelHeading-lnsPieVis-copy');
+ const libraryActionExists = await testSubjects.descendantExists(
+ 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
+ updatedPanel
+ );
+ expect(libraryActionExists).to.be(true);
+
+ await dashboardAddPanel.clickOpenAddPanel();
+ await dashboardAddPanel.filterEmbeddableNames('lnsPieVis');
+ await find.existsByLinkText('lnsPieVis');
+ });
});
}
diff --git a/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js b/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js
new file mode 100644
index 0000000000000..40e73f0d8a763
--- /dev/null
+++ b/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js
@@ -0,0 +1,80 @@
+/*
+ * 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 expect from '@kbn/expect';
+
+export default function ({ getPageObjects, getService }) {
+ const find = getService('find');
+ const testSubjects = getService('testSubjects');
+ const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'maps', 'visualize']);
+ const kibanaServer = getService('kibanaServer');
+ const security = getService('security');
+ const dashboardAddPanel = getService('dashboardAddPanel');
+ const dashboardPanelActions = getService('dashboardPanelActions');
+ const dashboardVisualizations = getService('dashboardVisualizations');
+
+ describe('maps in embeddable library', () => {
+ before(async () => {
+ await security.testUser.setRoles(
+ [
+ 'test_logstash_reader',
+ 'global_maps_all',
+ 'geoshape_data_reader',
+ 'global_dashboard_all',
+ 'meta_for_geoshape_data_reader',
+ ],
+ false
+ );
+ await kibanaServer.uiSettings.replace({
+ defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a',
+ });
+ await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.clickNewDashboard();
+ await dashboardAddPanel.clickCreateNewLink();
+ await dashboardVisualizations.ensureNewVisualizationDialogIsShowing();
+ await PageObjects.visualize.clickMapsApp();
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.maps.waitForLayersToLoad();
+ await PageObjects.maps.clickSaveAndReturnButton();
+ await PageObjects.dashboard.waitForRenderComplete();
+ });
+
+ after(async () => {
+ await security.testUser.restoreDefaults();
+ });
+
+ it('save map panel to embeddable library', async () => {
+ await dashboardPanelActions.saveToLibrary('embeddable library map');
+ await testSubjects.existOrFail('addPanelToLibrarySuccess');
+
+ const mapPanel = await testSubjects.find('embeddablePanelHeading-embeddablelibrarymap');
+ const libraryActionExists = await testSubjects.descendantExists(
+ 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
+ mapPanel
+ );
+ expect(libraryActionExists).to.be(true);
+ });
+
+ it('unlink map panel from embeddable library', async () => {
+ const originalPanel = await testSubjects.find('embeddablePanelHeading-embeddablelibrarymap');
+ await dashboardPanelActions.unlinkFromLibary(originalPanel);
+ await testSubjects.existOrFail('unlinkPanelSuccess');
+
+ const updatedPanel = await testSubjects.find('embeddablePanelHeading-embeddablelibrarymap');
+ const libraryActionExists = await testSubjects.descendantExists(
+ 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
+ updatedPanel
+ );
+ expect(libraryActionExists).to.be(false);
+
+ await dashboardAddPanel.clickOpenAddPanel();
+ await dashboardAddPanel.filterEmbeddableNames('embeddable library map');
+ await find.existsByLinkText('embeddable library map');
+ await dashboardAddPanel.closeAddPanel();
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/maps/embeddable/index.js b/x-pack/test/functional/apps/maps/embeddable/index.js
index 815de2e081309..9fd4c9db703db 100644
--- a/x-pack/test/functional/apps/maps/embeddable/index.js
+++ b/x-pack/test/functional/apps/maps/embeddable/index.js
@@ -9,6 +9,7 @@ export default function ({ loadTestFile }) {
describe('embeddable', function () {
loadTestFile(require.resolve('./save_and_return'));
loadTestFile(require.resolve('./dashboard'));
+ loadTestFile(require.resolve('./embeddable_library'));
loadTestFile(require.resolve('./embeddable_state'));
loadTestFile(require.resolve('./tooltip_filter_actions'));
});
diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js
index d76afb7ebdc24..dd20ed58afbc6 100644
--- a/x-pack/test/functional/apps/maps/index.js
+++ b/x-pack/test/functional/apps/maps/index.js
@@ -47,6 +47,7 @@ export default function ({ loadTestFile, getService }) {
loadTestFile(require.resolve('./es_geo_grid_source'));
loadTestFile(require.resolve('./es_pew_pew_source'));
loadTestFile(require.resolve('./joins'));
+ loadTestFile(require.resolve('./mapbox_styles'));
loadTestFile(require.resolve('./mvt_scaling'));
loadTestFile(require.resolve('./mvt_super_fine'));
loadTestFile(require.resolve('./add_layer_panel'));
diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js
index 094f5335cd05f..49717016f9c60 100644
--- a/x-pack/test/functional/apps/maps/joins.js
+++ b/x-pack/test/functional/apps/maps/joins.js
@@ -7,8 +7,6 @@
import expect from '@kbn/expect';
-import { MAPBOX_STYLES } from './mapbox_styles';
-
const JOIN_PROPERTY_NAME = '__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1';
const EXPECTED_JOIN_VALUES = {
alpha: 10,
@@ -18,10 +16,6 @@ const EXPECTED_JOIN_VALUES = {
};
const VECTOR_SOURCE_ID = 'n1t6f';
-const CIRCLE_STYLE_LAYER_INDEX = 0;
-const FILL_STYLE_LAYER_INDEX = 2;
-const LINE_STYLE_LAYER_INDEX = 3;
-const TOO_MANY_FEATURES_LAYER_INDEX = 4;
export default function ({ getPageObjects, getService }) {
const PageObjects = getPageObjects(['maps']);
@@ -95,34 +89,6 @@ export default function ({ getPageObjects, getService }) {
});
});
- it('should style fills, points, lines, and bounding-boxes independently', async () => {
- const mapboxStyle = await PageObjects.maps.getMapboxStyle();
- const layersForVectorSource = mapboxStyle.layers.filter((mbLayer) => {
- return mbLayer.id.startsWith(VECTOR_SOURCE_ID);
- });
-
- //circle layer for points
- expect(layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.POINT_LAYER);
-
- //fill layer
- expect(layersForVectorSource[FILL_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.FILL_LAYER);
-
- //line layer for borders
- expect(layersForVectorSource[LINE_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.LINE_LAYER);
-
- //Too many features layer (this is a static style config)
- expect(layersForVectorSource[TOO_MANY_FEATURES_LAYER_INDEX]).to.eql({
- id: 'n1t6f_toomanyfeatures',
- type: 'fill',
- source: 'n1t6f',
- minzoom: 0,
- maxzoom: 24,
- filter: ['==', ['get', '__kbn_too_many_features__'], true],
- layout: { visibility: 'visible' },
- paint: { 'fill-pattern': '__kbn_too_many_features_image_id__', 'fill-opacity': 0.75 },
- });
- });
-
it('should flag only the joined features as visible', async () => {
const mapboxStyle = await PageObjects.maps.getMapboxStyle();
const vectorSource = mapboxStyle.sources[VECTOR_SOURCE_ID];
diff --git a/x-pack/test/functional/apps/maps/mapbox_styles.js b/x-pack/test/functional/apps/maps/mapbox_styles.js
index d4496f13b8bef..b483b95e0ca1f 100644
--- a/x-pack/test/functional/apps/maps/mapbox_styles.js
+++ b/x-pack/test/functional/apps/maps/mapbox_styles.js
@@ -5,176 +5,242 @@
* 2.0.
*/
-export const MAPBOX_STYLES = {
- POINT_LAYER: {
- id: 'n1t6f_circle',
- type: 'circle',
- source: 'n1t6f',
- minzoom: 0,
- maxzoom: 24,
- filter: [
- 'all',
- ['==', ['get', '__kbn_isvisibleduetojoin__'], true],
- [
- 'all',
- ['!=', ['get', '__kbn_too_many_features__'], true],
- ['!=', ['get', '__kbn_is_centroid_feature__'], true],
- ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']],
- ],
- ],
- layout: { visibility: 'visible' },
- paint: {
- 'circle-color': [
- 'interpolate',
- ['linear'],
- [
- 'coalesce',
+import expect from '@kbn/expect';
+
+export default function ({ getPageObjects, getService }) {
+ const PageObjects = getPageObjects(['maps']);
+ const inspector = getService('inspector');
+ const security = getService('security');
+
+ describe('mapbox styles', () => {
+ let mapboxStyle;
+ before(async () => {
+ await security.testUser.setRoles(
+ ['global_maps_all', 'geoshape_data_reader', 'meta_for_geoshape_data_reader'],
+ false
+ );
+ await PageObjects.maps.loadSavedMap('join example');
+ mapboxStyle = await PageObjects.maps.getMapboxStyle();
+ });
+
+ after(async () => {
+ await inspector.close();
+ await security.testUser.restoreDefaults();
+ });
+
+ it('should style circle layer as expected', async () => {
+ const layer = mapboxStyle.layers.find((mbLayer) => {
+ return mbLayer.id === 'n1t6f_circle';
+ });
+ expect(layer).to.eql({
+ id: 'n1t6f_circle',
+ type: 'circle',
+ source: 'n1t6f',
+ minzoom: 0,
+ maxzoom: 24,
+ filter: [
+ 'all',
+ ['==', ['get', '__kbn_isvisibleduetojoin__'], true],
[
- 'case',
- [
- '==',
- ['feature-state', '__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1'],
- null,
- ],
- 2,
+ 'all',
+ ['!=', ['get', '__kbn_too_many_features__'], true],
+ ['!=', ['get', '__kbn_is_centroid_feature__'], true],
+ ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']],
+ ],
+ ],
+ layout: { visibility: 'visible' },
+ paint: {
+ 'circle-color': [
+ 'interpolate',
+ ['linear'],
[
- 'max',
+ 'coalesce',
[
- 'min',
+ 'case',
[
- 'to-number',
+ '==',
[
'feature-state',
'__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1',
],
+ null,
+ ],
+ 2,
+ [
+ 'max',
+ [
+ 'min',
+ [
+ 'to-number',
+ [
+ 'feature-state',
+ '__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1',
+ ],
+ ],
+ 12,
+ ],
+ 3,
],
- 12,
],
- 3,
+ 2,
],
+ 2,
+ 'rgba(0,0,0,0)',
+ 3,
+ '#ecf1f7',
+ 4.125,
+ '#d9e3ef',
+ 5.25,
+ '#c5d5e7',
+ 6.375,
+ '#b2c7df',
+ 7.5,
+ '#9eb9d8',
+ 8.625,
+ '#8bacd0',
+ 9.75,
+ '#769fc8',
+ 10.875,
+ '#6092c0',
],
- 2,
- ],
- 2,
- 'rgba(0,0,0,0)',
- 3,
- '#ecf1f7',
- 4.125,
- '#d9e3ef',
- 5.25,
- '#c5d5e7',
- 6.375,
- '#b2c7df',
- 7.5,
- '#9eb9d8',
- 8.625,
- '#8bacd0',
- 9.75,
- '#769fc8',
- 10.875,
- '#6092c0',
- ],
- 'circle-opacity': 0.75,
- 'circle-stroke-color': '#41937c',
- 'circle-stroke-opacity': 0.75,
- 'circle-stroke-width': 1,
- 'circle-radius': 10,
- },
- },
- FILL_LAYER: {
- id: 'n1t6f_fill',
- type: 'fill',
- source: 'n1t6f',
- minzoom: 0,
- maxzoom: 24,
- filter: [
- 'all',
- ['==', ['get', '__kbn_isvisibleduetojoin__'], true],
- [
- 'all',
- ['!=', ['get', '__kbn_too_many_features__'], true],
- ['!=', ['get', '__kbn_is_centroid_feature__'], true],
- ['any', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'MultiPolygon']],
- ],
- ],
- layout: { visibility: 'visible' },
- paint: {
- 'fill-color': [
- 'interpolate',
- ['linear'],
- [
- 'coalesce',
+ 'circle-opacity': 0.75,
+ 'circle-stroke-color': '#41937c',
+ 'circle-stroke-opacity': 0.75,
+ 'circle-stroke-width': 1,
+ 'circle-radius': 10,
+ },
+ });
+ });
+
+ it('should style fill layer as expected', async () => {
+ const layer = mapboxStyle.layers.find((mbLayer) => {
+ return mbLayer.id === 'n1t6f_fill';
+ });
+ expect(layer).to.eql({
+ id: 'n1t6f_fill',
+ type: 'fill',
+ source: 'n1t6f',
+ minzoom: 0,
+ maxzoom: 24,
+ filter: [
+ 'all',
+ ['==', ['get', '__kbn_isvisibleduetojoin__'], true],
[
- 'case',
+ 'all',
+ ['!=', ['get', '__kbn_too_many_features__'], true],
+ ['!=', ['get', '__kbn_is_centroid_feature__'], true],
[
- '==',
- ['feature-state', '__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1'],
- null,
+ 'any',
+ ['==', ['geometry-type'], 'Polygon'],
+ ['==', ['geometry-type'], 'MultiPolygon'],
],
- 2,
+ ],
+ ],
+ layout: { visibility: 'visible' },
+ paint: {
+ 'fill-color': [
+ 'interpolate',
+ ['linear'],
[
- 'max',
+ 'coalesce',
[
- 'min',
+ 'case',
[
- 'to-number',
+ '==',
[
'feature-state',
'__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1',
],
+ null,
+ ],
+ 2,
+ [
+ 'max',
+ [
+ 'min',
+ [
+ 'to-number',
+ [
+ 'feature-state',
+ '__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1',
+ ],
+ ],
+ 12,
+ ],
+ 3,
],
- 12,
],
- 3,
+ 2,
+ ],
+ 2,
+ 'rgba(0,0,0,0)',
+ 3,
+ '#ecf1f7',
+ 4.125,
+ '#d9e3ef',
+ 5.25,
+ '#c5d5e7',
+ 6.375,
+ '#b2c7df',
+ 7.5,
+ '#9eb9d8',
+ 8.625,
+ '#8bacd0',
+ 9.75,
+ '#769fc8',
+ 10.875,
+ '#6092c0',
+ ],
+ 'fill-opacity': 0.75,
+ },
+ });
+ });
+
+ it('should style fill layer as expected', async () => {
+ const layer = mapboxStyle.layers.find((mbLayer) => {
+ return mbLayer.id === 'n1t6f_line';
+ });
+ expect(layer).to.eql({
+ id: 'n1t6f_line',
+ type: 'line',
+ source: 'n1t6f',
+ minzoom: 0,
+ maxzoom: 24,
+ filter: [
+ 'all',
+ ['==', ['get', '__kbn_isvisibleduetojoin__'], true],
+ [
+ 'all',
+ ['!=', ['get', '__kbn_too_many_features__'], true],
+ ['!=', ['get', '__kbn_is_centroid_feature__'], true],
+ [
+ 'any',
+ ['==', ['geometry-type'], 'Polygon'],
+ ['==', ['geometry-type'], 'MultiPolygon'],
+ ['==', ['geometry-type'], 'LineString'],
+ ['==', ['geometry-type'], 'MultiLineString'],
],
],
- 2,
- ],
- 2,
- 'rgba(0,0,0,0)',
- 3,
- '#ecf1f7',
- 4.125,
- '#d9e3ef',
- 5.25,
- '#c5d5e7',
- 6.375,
- '#b2c7df',
- 7.5,
- '#9eb9d8',
- 8.625,
- '#8bacd0',
- 9.75,
- '#769fc8',
- 10.875,
- '#6092c0',
- ],
- 'fill-opacity': 0.75,
- },
- },
- LINE_LAYER: {
- id: 'n1t6f_line',
- type: 'line',
- source: 'n1t6f',
- minzoom: 0,
- maxzoom: 24,
- filter: [
- 'all',
- ['==', ['get', '__kbn_isvisibleduetojoin__'], true],
- [
- 'all',
- ['!=', ['get', '__kbn_too_many_features__'], true],
- ['!=', ['get', '__kbn_is_centroid_feature__'], true],
- [
- 'any',
- ['==', ['geometry-type'], 'Polygon'],
- ['==', ['geometry-type'], 'MultiPolygon'],
- ['==', ['geometry-type'], 'LineString'],
- ['==', ['geometry-type'], 'MultiLineString'],
],
- ],
- ],
- layout: { visibility: 'visible' },
- paint: { 'line-color': '#41937c', 'line-opacity': 0.75, 'line-width': 1 },
- },
-};
+ layout: { visibility: 'visible' },
+ paint: { 'line-color': '#41937c', 'line-opacity': 0.75, 'line-width': 1 },
+ });
+ });
+
+ it('should style incomplete data layer as expected', async () => {
+ const layer = mapboxStyle.layers.find((mbLayer) => {
+ return mbLayer.id === 'n1t6f_toomanyfeatures';
+ });
+ expect(layer).to.eql({
+ id: 'n1t6f_toomanyfeatures',
+ type: 'fill',
+ source: 'n1t6f',
+ minzoom: 0,
+ maxzoom: 24,
+ filter: ['==', ['get', '__kbn_too_many_features__'], true],
+ layout: { visibility: 'visible' },
+ paint: { 'fill-pattern': '__kbn_too_many_features_image_id__', 'fill-opacity': 0.75 },
+ });
+ });
+ });
+}
diff --git a/x-pack/test/functional/services/grok_debugger.js b/x-pack/test/functional/services/grok_debugger.js
index 730b4ca60c05a..42a80edd70c85 100644
--- a/x-pack/test/functional/services/grok_debugger.js
+++ b/x-pack/test/functional/services/grok_debugger.js
@@ -13,7 +13,7 @@ export function GrokDebuggerProvider({ getService }) {
const retry = getService('retry');
// test subject selectors
- const SUBJ_CONTAINER = 'grokDebugger';
+ const SUBJ_CONTAINER = 'grokDebuggerContainer';
const SUBJ_UI_ACE_EVENT_INPUT = `${SUBJ_CONTAINER} > aceEventInput > codeEditorContainer`;
const SUBJ_UI_ACE_PATTERN_INPUT = `${SUBJ_CONTAINER} > acePatternInput > codeEditorContainer`;
@@ -49,10 +49,8 @@ export function GrokDebuggerProvider({ getService }) {
}
async assertExists() {
- await retry.try(async () => {
- if (!(await testSubjects.exists(SUBJ_CONTAINER))) {
- throw new Error('Expected to find the grok debugger');
- }
+ await retry.waitFor('Grok Debugger to exist', async () => {
+ return await testSubjects.exists(SUBJ_CONTAINER);
});
}
diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json
index 0a7a30f373e07..2981346e80e1d 100644
--- a/x-pack/test/tsconfig.json
+++ b/x-pack/test/tsconfig.json
@@ -76,6 +76,10 @@
{ "path": "../plugins/triggers_actions_ui/tsconfig.json" },
{ "path": "../plugins/ui_actions_enhanced/tsconfig.json" },
{ "path": "../plugins/upgrade_assistant/tsconfig.json" },
- { "path": "../plugins/watcher/tsconfig.json" }
+ { "path": "../plugins/watcher/tsconfig.json" },
+ { "path": "../plugins/runtime_fields/tsconfig.json" },
+ { "path": "../plugins/index_management/tsconfig.json" },
+ { "path": "../plugins/watcher/tsconfig.json" },
+ { "path": "../plugins/uptime/tsconfig.json" }
]
}
diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json
index 5d51c2923abd0..740bac3f1b0de 100644
--- a/x-pack/tsconfig.json
+++ b/x-pack/tsconfig.json
@@ -56,6 +56,7 @@
"plugins/index_management/**/*",
"plugins/grokdebugger/**/*",
"plugins/upgrade_assistant/**/*",
+ "plugins/uptime/**/*",
"test/**/*"
],
"compilerOptions": {
@@ -145,6 +146,7 @@
{ "path": "./plugins/upgrade_assistant/tsconfig.json" },
{ "path": "./plugins/runtime_fields/tsconfig.json" },
{ "path": "./plugins/index_management/tsconfig.json" },
- { "path": "./plugins/watcher/tsconfig.json" }
+ { "path": "./plugins/watcher/tsconfig.json" },
+ { "path": "./plugins/uptime/tsconfig.json" }
]
}
diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json
index ae88ab6486e64..7a2eebc78b69b 100644
--- a/x-pack/tsconfig.refs.json
+++ b/x-pack/tsconfig.refs.json
@@ -50,6 +50,7 @@
{ "path": "./plugins/upgrade_assistant/tsconfig.json" },
{ "path": "./plugins/runtime_fields/tsconfig.json" },
{ "path": "./plugins/index_management/tsconfig.json" },
- { "path": "./plugins/watcher/tsconfig.json" }
+ { "path": "./plugins/watcher/tsconfig.json" },
+ { "path": "./plugins/uptime/tsconfig.json" }
]
}