"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.SwimlaneContainer = exports.SWIM_LANE_LABEL_WIDTH = void 0; exports.isViewBySwimLaneData = isViewBySwimLaneData; var _react = _interopRequireWildcard(require("react")); var _eui = require("@elastic/eui"); var _lodash = require("lodash"); var _charts = require("@elastic/charts"); var _moment = _interopRequireDefault(require("moment")); var _i18n = require("@kbn/i18n"); var _public = require("@kbn/charts-plugin/public"); var _react2 = require("@emotion/react"); var _mlAnomalyUtils = require("@kbn/ml-anomaly-utils"); var _mlDateUtils = require("@kbn/ml-date-utils"); var _mlKibanaTheme = require("@kbn/ml-kibana-theme"); var _swimlane_pagination = require("./swimlane_pagination"); var _explorer_constants = require("./explorer_constants"); var _string_utils = require("../util/string_utils"); var _chart_tooltip = require("../components/chart_tooltip/chart_tooltip"); require("./_explorer.scss"); var _entity_control = require("../timeseriesexplorer/components/entity_control/entity_control"); var _swimlane_annotation_container = require("./swimlane_annotation_container"); var _kibana = require("../contexts/kibana"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * 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. */ /** * Ignore insignificant resize, e.g. browser scrollbar appearance. */ const RESIZE_THROTTLE_TIME_MS = 500; const BORDER_WIDTH = 1; const CELL_HEIGHT = 30; const LEGEND_HEIGHT = 34; const X_AXIS_HEIGHT = 24; const SWIM_LANE_LABEL_WIDTH = _swimlane_annotation_container.Y_AXIS_LABEL_WIDTH + 2 * _swimlane_annotation_container.Y_AXIS_LABEL_PADDING; exports.SWIM_LANE_LABEL_WIDTH = SWIM_LANE_LABEL_WIDTH; function isViewBySwimLaneData(arg) { return arg && arg.hasOwnProperty('cardinality'); } /** * Provides a custom tooltip for the anomaly swim lane chart. */ const SwimLaneTooltip = fieldName => ({ values }) => { const tooltipData = []; if (values.length === 1 && fieldName) { // Y-axis tooltip for viewBy swim lane const [yAxis] = values; // @ts-ignore tooltipData.push({ skipHeader: true }); tooltipData.push({ label: fieldName, value: yAxis.value, // @ts-ignore seriesIdentifier: { key: yAxis.value } }); } else if (values.length === 3) { // Cell tooltip const [xAxis, yAxis, cell] = values; // Display date using same format as Kibana visualizations. const formattedDate = (0, _mlDateUtils.formatHumanReadableDateTime)(parseInt(xAxis.value, 10)); tooltipData.push({ label: formattedDate }); if (fieldName !== undefined) { tooltipData.push({ label: fieldName, value: yAxis.value, // @ts-ignore seriesIdentifier: { key: yAxis.value } }); } tooltipData.push({ label: _i18n.i18n.translate('xpack.ml.explorer.swimlane.maxAnomalyScoreLabel', { defaultMessage: 'Max anomaly score' }), value: cell.formattedValue === '0' ? ' < 1' : cell.formattedValue, color: cell.color, // @ts-ignore seriesIdentifier: { key: cell.value } }); } return /*#__PURE__*/_react.default.createElement(_chart_tooltip.FormattedTooltip, { tooltipData: tooltipData }); }; /** * Anomaly swim lane container responsible for handling resizing, pagination and * providing swim lane vis with required props. */ const SwimlaneContainer = ({ id, onResize, perPage, fromPage, swimlaneLimit, onPaginationChange, isLoading, noDataWarning, filterActive, swimlaneData, swimlaneType, selection, onCellsSelection, timeBuckets, chartsService, showTimeline = true, showYAxis = true, showLegend = true, 'data-test-subj': dataTestSubj, yAxisWidth }) => { var _swimlaneData$laneLab, _swimlaneData$laneLab2, _swimlaneData$laneLab3; const [chartWidth, setChartWidth] = (0, _react.useState)(0); const { services: { theme: themeService } } = (0, _kibana.useMlKibana)(); const isDarkTheme = (0, _mlKibanaTheme.useIsDarkTheme)(themeService); const { euiTheme } = (0, _kibana.useCurrentThemeVars)(); // Holds the container height for previously fetched data const containerHeightRef = (0, _react.useRef)(); // eslint-disable-next-line react-hooks/exhaustive-deps const resizeHandler = (0, _react.useCallback)((0, _lodash.throttle)(e => { const resultNewWidth = e.width - SWIM_LANE_LABEL_WIDTH; setChartWidth(resultNewWidth); onResize(resultNewWidth); }, RESIZE_THROTTLE_TIME_MS), [chartWidth]); const swimLanePoints = (0, _react.useMemo)(() => { const showFilterContext = filterActive === true && swimlaneType === _explorer_constants.SWIMLANE_TYPE.OVERALL; if (!(swimlaneData !== null && swimlaneData !== void 0 && swimlaneData.points)) { return []; } const sortedLaneValues = swimlaneData.laneLabels; return swimlaneData.points.map(v => { const formatted = { ...v, time: v.time * 1000, value: v.value === 0 ? null : v.value }; if (showFilterContext) { formatted.laneLabel = _i18n.i18n.translate('xpack.ml.explorer.overallSwimlaneUnfilteredLabel', { defaultMessage: '{label} (unfiltered)', values: { label: (0, _string_utils.mlEscape)(v.laneLabel) } }); } return formatted; }).sort((a, b) => { let aIndex = sortedLaneValues.indexOf(a.laneLabel); let bIndex = sortedLaneValues.indexOf(b.laneLabel); aIndex = aIndex > -1 ? aIndex : sortedLaneValues.length; bIndex = bIndex > -1 ? bIndex : sortedLaneValues.length; return aIndex - bIndex; }); }, [swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.points, filterActive, swimlaneType, swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.laneLabels]); const showSwimlane = (swimlaneData === null || swimlaneData === void 0 ? void 0 : (_swimlaneData$laneLab = swimlaneData.laneLabels) === null || _swimlaneData$laneLab === void 0 ? void 0 : _swimlaneData$laneLab.length) > 0 && swimLanePoints.length > 0; const isPaginationVisible = (showSwimlane || isLoading) && swimlaneLimit !== undefined && swimlaneLimit > (perPage !== null && perPage !== void 0 ? perPage : 5) && onPaginationChange && fromPage && perPage; const rowsCount = (_swimlaneData$laneLab2 = swimlaneData === null || swimlaneData === void 0 ? void 0 : (_swimlaneData$laneLab3 = swimlaneData.laneLabels) === null || _swimlaneData$laneLab3 === void 0 ? void 0 : _swimlaneData$laneLab3.length) !== null && _swimlaneData$laneLab2 !== void 0 ? _swimlaneData$laneLab2 : 0; const containerHeight = (0, _react.useMemo)(() => { // Persists container height during loading to prevent page from jumping return isLoading ? containerHeightRef.current : // TODO update when elastic charts X label will be fixed rowsCount * (CELL_HEIGHT + BORDER_WIDTH * 2) + (showLegend ? LEGEND_HEIGHT : 0) + (showTimeline ? X_AXIS_HEIGHT : 0); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoading, rowsCount]); (0, _react.useEffect)(() => { if (!isLoading) { containerHeightRef.current = containerHeight; } }, [isLoading, containerHeight]); const highlightedData = (0, _react.useMemo)(() => { if (!selection || !swimlaneData) return; if ((swimlaneType !== selection.type || (swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.fieldName) !== undefined && swimlaneData.fieldName !== selection.viewByFieldName) && filterActive === false) { // Not this swim lane which was selected. return; } return { x: selection.times.map(v => v * 1000), y: selection.lanes }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [selection, swimlaneData, swimlaneType]); const showBrush = !!onCellsSelection; const themeOverrides = (0, _react.useMemo)(() => { if (!showSwimlane) return {}; const theme = { background: { color: euiTheme.euiPanelBackgroundColorModifiers.plain }, heatmap: { grid: { stroke: { width: BORDER_WIDTH, color: euiTheme.euiBorderColor } }, cell: { maxWidth: 'fill', maxHeight: 'fill', label: { visible: false }, border: { stroke: euiTheme.euiBorderColor, strokeWidth: 0 } }, yAxisLabel: { visible: showYAxis, width: yAxisWidth, textColor: euiTheme.euiTextSubduedColor, padding: _swimlane_annotation_container.Y_AXIS_LABEL_PADDING, fontSize: parseInt(euiTheme.euiFontSizeXS, 10) }, xAxisLabel: { visible: showTimeline, textColor: euiTheme.euiTextSubduedColor, fontSize: parseInt(euiTheme.euiFontSizeXS, 10) }, brushMask: { visible: showBrush, fill: isDarkTheme ? 'rgb(30,31,35,80%)' : 'rgb(247,247,247,50%)' }, brushArea: { visible: showBrush, stroke: isDarkTheme ? 'rgb(255, 255, 255)' : 'rgb(105, 112, 125)' }, ...(showLegend ? { maxLegendHeight: LEGEND_HEIGHT } : {}) } }; return theme; // eslint-disable-next-line react-hooks/exhaustive-deps }, [showSwimlane, swimlaneType, swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.fieldName, isDarkTheme, timeBuckets, onCellsSelection]); const chartRef = (0, _react.useRef)(null); const handleCursorUpdate = (0, _public.useActiveCursor)(chartsService.activeCursor, chartRef, { isDateHistogram: true }); const onElementClick = (0, _react.useCallback)(e => { if (!onCellsSelection) return; const cell = e[0][0]; const startTime = cell.datum.x / 1000; const payload = { lanes: [String(cell.datum.y)], times: [startTime, startTime + swimlaneData.interval], type: swimlaneType, viewByFieldName: swimlaneData.fieldName }; onCellsSelection(payload); }, [swimlaneType, swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.fieldName, swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.interval, onCellsSelection]); const tooltipOptions = (0, _react.useMemo)(() => ({ placement: 'auto', fallbackPlacements: ['left'], boundary: 'chart', customTooltip: SwimLaneTooltip(swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.fieldName) }), [swimlaneData === null || swimlaneData === void 0 ? void 0 : swimlaneData.fieldName]); const xDomain = (0, _react.useMemo)(() => swimlaneData ? { min: swimlaneData.earliest * 1000, max: swimlaneData.latest * 1000, minInterval: swimlaneData.interval * 1000 } : undefined, [swimlaneData]); const onBrushEnd = e => { if (!e.cells.length || !showBrush) return; if (typeof onCellsSelection === 'function') { onCellsSelection({ lanes: e.y, times: e.x.map(v => v / 1000), type: swimlaneType, viewByFieldName: swimlaneData.fieldName }); } }; const noSwimLaneData = !isLoading && !showSwimlane && !!noDataWarning; // A resize observer is required to compute the bucket span based on the chart width to fetch the data accordingly return /*#__PURE__*/_react.default.createElement(_eui.EuiResizeObserver, { onResize: resizeHandler }, resizeRef => { var _window$_echDebugStat, _xDomain$minInterval; return /*#__PURE__*/_react.default.createElement(_eui.EuiFlexGroup, { gutterSize: 'none', direction: 'column', ref: resizeRef, "data-test-subj": dataTestSubj, css: { width: '100%', height: '100%', overflow: 'hidden' } }, /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, { css: { width: '100%', 'overflow-y': 'auto', 'overflow-x': 'hidden' }, grow: false }, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("div", { style: { height: `${containerHeight}px` }, css: (0, _react2.css)` position: relative; `, hidden: noSwimLaneData }, showSwimlane && !isLoading && /*#__PURE__*/_react.default.createElement(_charts.Chart, { className: 'mlSwimLaneContainer', ref: chartRef }, /*#__PURE__*/_react.default.createElement(_charts.Tooltip, tooltipOptions), /*#__PURE__*/_react.default.createElement(_charts.Settings // TODO use the EUI charts theme see src/plugins/charts/public/services/theme/README.md , { theme: themeOverrides, onElementClick: onElementClick, onPointerUpdate: handleCursorUpdate, showLegend: showLegend, legendPosition: _charts.Position.Top, xDomain: xDomain, debugState: (_window$_echDebugStat = window._echDebugStateFlag) !== null && _window$_echDebugStat !== void 0 ? _window$_echDebugStat : false, onBrushEnd: onBrushEnd }), /*#__PURE__*/_react.default.createElement(_charts.Heatmap, { id: id, timeZone: "UTC", colorScale: { type: 'bands', bands: [{ start: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.LOW, end: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.WARNING, color: _mlAnomalyUtils.ML_SEVERITY_COLORS.LOW }, { start: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.WARNING, end: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.MINOR, color: _mlAnomalyUtils.ML_SEVERITY_COLORS.WARNING }, { start: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.MINOR, end: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.MAJOR, color: _mlAnomalyUtils.ML_SEVERITY_COLORS.MINOR }, { start: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.MAJOR, end: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.CRITICAL, color: _mlAnomalyUtils.ML_SEVERITY_COLORS.MAJOR }, { start: _mlAnomalyUtils.ML_ANOMALY_THRESHOLD.CRITICAL, end: Infinity, color: _mlAnomalyUtils.ML_SEVERITY_COLORS.CRITICAL }] }, data: swimLanePoints, xAccessor: "time", yAccessor: "laneLabel", valueAccessor: "value", highlightedData: highlightedData, valueFormatter: _mlAnomalyUtils.getFormattedSeverityScore, xScale: { type: _charts.ScaleType.Time, interval: { type: 'fixed', unit: 'ms', // the xDomain.minInterval should always be available at rendering time // adding a fallback to 1m bucket value: (_xDomain$minInterval = xDomain === null || xDomain === void 0 ? void 0 : xDomain.minInterval) !== null && _xDomain$minInterval !== void 0 ? _xDomain$minInterval : 1000 * 60 } }, ySortPredicate: "dataIndex", yAxisLabelFormatter: laneLabel => { return laneLabel === '' ? _entity_control.EMPTY_FIELD_VALUE_LABEL : String(laneLabel); }, xAxisLabelFormatter: v => { timeBuckets.setInterval(`${swimlaneData.interval}s`); const scaledDateFormat = timeBuckets.getScaledDateFormat(); return (0, _moment.default)(v).format(scaledDateFormat); } })), isLoading && /*#__PURE__*/_react.default.createElement(_eui.EuiText, { textAlign: 'center', css: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)' } }, /*#__PURE__*/_react.default.createElement(_eui.EuiLoadingChart, { size: "xl", mono: true, "data-test-subj": "mlSwimLaneLoadingIndicator" }))), noSwimLaneData ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, noDataWarning) : null))), isPaginationVisible && /*#__PURE__*/_react.default.createElement(_eui.EuiFlexItem, { grow: false }, /*#__PURE__*/_react.default.createElement(_swimlane_pagination.SwimLanePagination, { cardinality: swimlaneLimit, fromPage: fromPage, perPage: perPage, onPaginationChange: onPaginationChange }))); }); }; exports.SwimlaneContainer = SwimlaneContainer;