From 2101c11fd3321f38cc09327f86dff99d5d433e0f Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 14 Apr 2021 15:37:34 -0500 Subject: [PATCH 01/15] [ML] Add annotation --- .../explorer/actions/load_explorer_data.ts | 14 +- .../application/explorer/anomaly_timeline.tsx | 2 + .../application/explorer/explorer_utils.js | 61 ++++ .../reducers/explorer_reducer/state.ts | 6 + .../explorer/swimlane_container.tsx | 270 +++++++++++++----- .../services/ml_api_service/annotations.ts | 6 +- 6 files changed, 282 insertions(+), 77 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index e09e9f3d2c1ae..c6c980f82cef5 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -150,14 +150,23 @@ const loadExplorerDataProvider = ( const dateFormatTz = getDateFormatTz(); + const interval = swimlaneBucketInterval.asSeconds(); + // First get the data where we have all necessary args at hand using forkJoin: // annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues return forkJoin({ + rawAnnotationsData: memoizedLoadAnnotationsTableData( + lastRefresh, + undefined, + selectedJobs, + interval, + bounds + ), annotationsData: memoizedLoadAnnotationsTableData( lastRefresh, selectedCells, selectedJobs, - swimlaneBucketInterval.asSeconds(), + interval, bounds ), anomalyChartRecords: memoizedLoadDataForCharts( @@ -282,10 +291,11 @@ const loadExplorerDataProvider = ( ), }), ( - { annotationsData, overallState, tableData }, + { rawAnnotationsData, annotationsData, overallState, tableData }, { influencers, viewBySwimlaneState } ): Partial => { return { + allAnnotations: rawAnnotationsData, annotations: annotationsData, influencers: influencers as any, loading: false, diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 7c63d4087ce1e..9e8552cc0d286 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -87,6 +87,7 @@ export const AnomalyTimeline: FC = React.memo( viewByPerPage, swimlaneLimit, loading, + allAnnotations, } = explorerState; const menuItems = useMemo(() => { @@ -240,6 +241,7 @@ export const AnomalyTimeline: FC = React.memo( isLoading={loading} noDataWarning={} showTimeline={false} + annotationsData={allAnnotations.annotationsData} /> diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index ea101d104f783..62a437a5b7731 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -385,7 +385,68 @@ export function getViewBySwimlaneOptions({ }; } +export function loadAllAnnotationsData(selectedJobs, interval, bounds) { + const jobIds = selectedJobs.map((d) => d.id); + const timeRange = getSelectionTimeRange(undefined, interval, bounds); + + return new Promise((resolve) => { + ml.annotations + .getAnnotations$({ + jobIds, + earliestMs: timeRange.earliestMs, + latestMs: timeRange.latestMs, + maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE, + }) + .toPromise() + .then((resp) => { + if (resp.error !== undefined || resp.annotations === undefined) { + const errorMessage = extractErrorMessage(resp.error); + return resolve({ + annotationsData: [], + aggregations: {}, + error: errorMessage !== '' ? errorMessage : undefined, + }); + } + + const annotationsData = []; + jobIds.forEach((jobId) => { + const jobAnnotations = resp.annotations[jobId]; + if (jobAnnotations !== undefined) { + annotationsData.push(...jobAnnotations); + } + }); + + return resolve({ + annotationsData: annotationsData + .sort((a, b) => { + return a.timestamp - b.timestamp; + }) + .map((d, i) => { + d.key = (i + 1).toString(); + return d; + }), + aggregations: resp.aggregations, + }); + }) + .catch((resp) => { + const errorMessage = extractErrorMessage(resp); + return resolve({ + annotationsData: [], + aggregations: {}, + error: errorMessage !== '' ? errorMessage : undefined, + }); + }); + }); +} + export function loadAnnotationsTableData(selectedCells, selectedJobs, interval, bounds) { + console.log( + 'selectedCells, selectedJobs, interval, bounds', + selectedCells, + selectedJobs, + interval, + bounds + ); const jobIds = selectedCells !== undefined && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL ? selectedCells.lanes diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index bb90fedfc2315..be2b8d3c99bbe 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -27,6 +27,7 @@ import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../explorer_constants'; import { InfluencersFilterQuery } from '../../../../../common/types/es_client'; export interface ExplorerState { + allAnnotations: AnnotationsTable; annotations: AnnotationsTable; chartsData: ExplorerChartsData; fieldFormatsLoading: boolean; @@ -64,6 +65,11 @@ function getDefaultIndexPattern() { export function getExplorerDefaultState(): ExplorerState { return { + allAnnotations: { + error: undefined, + annotationsData: [], + aggregations: {}, + }, annotations: { error: undefined, annotationsData: [], diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 4adb79f065cd4..b193986514f33 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -32,19 +32,26 @@ import { import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { scaleTime } from 'd3-scale'; +import d3 from 'd3'; import { SwimLanePagination } from './swimlane_pagination'; import { AppStateSelectedCells, OverallSwimlaneData, ViewBySwimLaneData } from './explorer_utils'; import { ANOMALY_THRESHOLD, SEVERITY_COLORS } from '../../../common'; import { TimeBuckets as TimeBucketsClass } from '../util/time_buckets'; import { SWIMLANE_TYPE, SwimlaneType } from './explorer_constants'; import { mlEscape } from '../util/string_utils'; -import { FormattedTooltip } from '../components/chart_tooltip/chart_tooltip'; -import { formatHumanReadableDateTime } from '../../../common/util/date_utils'; +import { FormattedTooltip, MlTooltipComponent } from '../components/chart_tooltip/chart_tooltip'; +import { + formatHumanReadableDateTime, + formatHumanReadableDateTimeSeconds, +} from '../../../common/util/date_utils'; import { getFormattedSeverityScore } from '../../../common/util/anomaly_utils'; import './_explorer.scss'; import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control'; import { useUiSettings } from '../contexts/kibana'; +import { AnnotationsTable } from '../../../common/types/annotations'; +import { ChartTooltipService } from '../components/chart_tooltip'; declare global { interface Window { @@ -61,8 +68,13 @@ declare global { const RESIZE_THROTTLE_TIME_MS = 500; const CELL_HEIGHT = 30; const LEGEND_HEIGHT = 34; +const ANNOTATION_HEIGHT = 12; + const Y_AXIS_HEIGHT = 24; + export const SWIM_LANE_LABEL_WIDTH = 200; +const Y_AXIS_LABEL_WIDTH = 170; +const Y_AXIS_LABEL_PADDING = 8; export function isViewBySwimLaneData(arg: any): arg is ViewBySwimLaneData { return arg && arg.hasOwnProperty('cardinality'); @@ -147,6 +159,98 @@ export interface SwimlaneProps { showTimeline?: boolean; } +interface SwimlaneAnnotationContainerProps { + chartWidth: number; + domain: { + min: number; + max: number; + }; + annotationsData: AnnotationsTable['annotationsData']; + tooltipService: ChartTooltipService; +} +export const SwimlaneAnnotationContainer: FC = ({ + chartWidth, + domain, + annotationsData, + tooltipService, +}) => { + const canvasRef = React.useRef(null); + + useEffect(() => { + if (canvasRef.current !== null) { + const chartElement = d3.select(canvasRef.current); + chartElement.selectAll('*').remove(); + + const startingXPos = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING; + const endingXPos = startingXPos + chartWidth - Y_AXIS_LABEL_PADDING; + + const svg = chartElement + .append('svg') + .attr('width', '100%') + .attr('height', ANNOTATION_HEIGHT); + + const xScale = scaleTime().domain([domain.min, domain.max]).range([startingXPos, endingXPos]); + + // remove container altogether + // const container = svg.append('rect'); + // container + // .attr('x', startingXPos) + // .attr('y', 0) + // .attr('height', ANNOTATION_HEIGHT) + // .attr('width', endingXPos - startingXPos) + // .style('stroke', 'none') + // .style('fill', '#ccc') + // .style('stroke-width', 1); + + annotationsData.forEach((d) => { + svg + .append('rect') + .classed('mlAnnotationRect', true) + .attr('x', xScale(d.timestamp)) + .attr('y', 0) + .attr('height', ANNOTATION_HEIGHT) + .attr('width', 5) + .on('mouseover', function (a) { + const startingTime = formatHumanReadableDateTimeSeconds(d.timestamp); + const endingTime = + d.end_timestamp !== undefined + ? formatHumanReadableDateTimeSeconds(d.end_timestamp) + : undefined; + + const timeLabel = endingTime ? `${startingTime} - ${endingTime}` : startingTime; + + tooltipService.show( + [ + // @ts-ignore only need label to show + { + label: `${d.annotation}`, + seriesIdentifier: { + key: 'anomaly_timeline', + specId: d._id ?? `${d.annotation}-${d.timestamp}-label`, + }, + valueAccessor: 'timespan', + }, + // @ts-ignore only need label to show + { + label: `${timeLabel}`, + seriesIdentifier: { + key: 'anomaly_timeline', + specId: d._id ?? `${d.annotation}-${d.timestamp}-ts`, + }, + valueAccessor: 'timespan', + }, + ], + this + ); + }) + .on('mouseout', () => tooltipService.hide()); + }); + } + }, [chartWidth, domain, annotationsData]); + + return
; +}; + /** * Anomaly swim lane container responsible for handling resizing, pagination and * providing swim lane vis with required props. @@ -168,6 +272,7 @@ export const SwimlaneContainer: FC = ({ timeBuckets, maskAll, showTimeline = true, + annotationsData, 'data-test-subj': dataTestSubj, }) => { const [chartWidth, setChartWidth] = useState(0); @@ -292,10 +397,10 @@ export const SwimlaneContainer: FC = ({ }, yAxisLabel: { visible: true, - width: 170, + width: Y_AXIS_LABEL_WIDTH, // eui color subdued fill: `#6a717d`, - padding: 8, + padding: Y_AXIS_LABEL_PADDING, formatter: (laneLabel: string) => { return laneLabel === '' ? EMPTY_FIELD_VALUE_LABEL : laneLabel; }, @@ -354,6 +459,12 @@ export const SwimlaneContainer: FC = ({ [swimlaneData?.fieldName] ); + const xDomain = { + min: swimlaneData.earliest * 1000, + max: swimlaneData.latest * 1000, + minInterval: swimlaneData.interval * 1000, + }; + // A resize observer is required to compute the bucket span based on the chart width to fetch the data accordingly return ( @@ -372,77 +483,92 @@ export const SwimlaneContainer: FC = ({ }} grow={false} > -
- {showSwimlane && !isLoading && ( - - +
+ {showSwimlane && !isLoading && ( + + + + + + )} + + {isLoading && ( + - + + + )} + {!isLoading && !showSwimlane && ( + {noDataWarning}} /> - - )} - - {isLoading && ( - - - - )} - {!isLoading && !showSwimlane && ( - {noDataWarning}} - /> + )} +
+ {swimlaneType === SWIMLANE_TYPE.OVERALL && showSwimlane && !isLoading && ( + + {(tooltipService) => ( + + )} + )} -
+ {isPaginationVisible && ( diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/annotations.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/annotations.ts index 88c98b888f5e6..f3f9e935a92c7 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/annotations.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/annotations.ts @@ -19,9 +19,9 @@ export const annotations = { earliestMs: number; latestMs: number; maxAnnotations: number; - fields: FieldToBucket[]; - detectorIndex: number; - entities: any[]; + fields?: FieldToBucket[]; + detectorIndex?: number; + entities?: any[]; }) { const body = JSON.stringify(obj); return http$({ From af3433639d712a8ae88928778177d104f5ae4b6b Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 14 Apr 2021 15:56:09 -0500 Subject: [PATCH 02/15] [ML] Clean up --- .../application/explorer/anomaly_timeline.tsx | 2 +- .../application/explorer/explorer_utils.js | 7 -- .../swimlane_annotation_container.tsx | 109 ++++++++++++++++++ .../explorer/swimlane_container.tsx | 109 ++---------------- 4 files changed, 117 insertions(+), 110 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 9e8552cc0d286..cb0189b7e09c9 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -244,7 +244,7 @@ export const AnomalyTimeline: FC = React.memo( annotationsData={allAnnotations.annotationsData} /> - + {viewBySwimlaneOptions.length > 0 && ( = ({ + chartWidth, + domain, + annotationsData, + tooltipService, +}) => { + const canvasRef = React.useRef(null); + + useEffect(() => { + if (canvasRef.current !== null && Array.isArray(annotationsData)) { + const chartElement = d3.select(canvasRef.current); + chartElement.selectAll('*').remove(); + + const startingXPos = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING; + const endingXPos = startingXPos + chartWidth - Y_AXIS_LABEL_PADDING; + + const svg = chartElement + .append('svg') + .attr('width', '100%') + .attr('height', ANNOTATION_HEIGHT); + + const xScale = scaleTime().domain([domain.min, domain.max]).range([startingXPos, endingXPos]); + + annotationsData.forEach((d) => { + const annotationWidth = d.end_timestamp ? xScale(d.end_timestamp) - xScale(d.timestamp) : 0; + svg + .append('rect') + .classed('mlAnnotationRect', true) + .attr('x', xScale(d.timestamp)) + .attr('y', 0) + .attr('height', ANNOTATION_HEIGHT) + .attr('width', Math.max(annotationWidth, 5)) + .on('mouseover', function () { + const startingTime = formatHumanReadableDateTimeSeconds(d.timestamp); + const endingTime = + d.end_timestamp !== undefined + ? formatHumanReadableDateTimeSeconds(d.end_timestamp) + : undefined; + + const timeLabel = endingTime ? `${startingTime} - ${endingTime}` : startingTime; + + const tooltipData = [ + { + label: `${d.annotation}`, + seriesIdentifier: { + key: 'anomaly_timeline', + specId: d._id ?? `${d.annotation}-${d.timestamp}-label`, + }, + valueAccessor: 'label', + }, + { + label: `${timeLabel}`, + seriesIdentifier: { + key: 'anomaly_timeline', + specId: d._id ?? `${d.annotation}-${d.timestamp}-ts`, + }, + valueAccessor: 'time', + }, + ]; + if (d.partition_field_name !== undefined && d.partition_field_value !== undefined) { + tooltipData.push({ + label: `${d.partition_field_name}: ${d.partition_field_value}`, + seriesIdentifier: { + key: 'anomaly_timeline', + specId: d._id + ? `${d._id}-partition` + : `${d.partition_field_name}-${d.partition_field_value}-label`, + }, + valueAccessor: 'partition', + }); + } + // @ts-ignore we don't need all the fields for tooltip to show + tooltipService.show(tooltipData, this); + }) + .on('mouseout', () => tooltipService.hide()); + }); + } + }, [chartWidth, domain, annotationsData]); + + return
; +}; diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index b193986514f33..2f381587c9aeb 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -32,8 +32,6 @@ import { import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { scaleTime } from 'd3-scale'; -import d3 from 'd3'; import { SwimLanePagination } from './swimlane_pagination'; import { AppStateSelectedCells, OverallSwimlaneData, ViewBySwimLaneData } from './explorer_utils'; import { ANOMALY_THRESHOLD, SEVERITY_COLORS } from '../../../common'; @@ -41,17 +39,18 @@ import { TimeBuckets as TimeBucketsClass } from '../util/time_buckets'; import { SWIMLANE_TYPE, SwimlaneType } from './explorer_constants'; import { mlEscape } from '../util/string_utils'; import { FormattedTooltip, MlTooltipComponent } from '../components/chart_tooltip/chart_tooltip'; -import { - formatHumanReadableDateTime, - formatHumanReadableDateTimeSeconds, -} from '../../../common/util/date_utils'; +import { formatHumanReadableDateTime } from '../../../common/util/date_utils'; import { getFormattedSeverityScore } from '../../../common/util/anomaly_utils'; import './_explorer.scss'; import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control'; import { useUiSettings } from '../contexts/kibana'; +import { + SwimlaneAnnotationContainer, + Y_AXIS_LABEL_WIDTH, + Y_AXIS_LABEL_PADDING, +} from './swimlane_annotation_container'; import { AnnotationsTable } from '../../../common/types/annotations'; -import { ChartTooltipService } from '../components/chart_tooltip'; declare global { interface Window { @@ -68,13 +67,10 @@ declare global { const RESIZE_THROTTLE_TIME_MS = 500; const CELL_HEIGHT = 30; const LEGEND_HEIGHT = 34; -const ANNOTATION_HEIGHT = 12; const Y_AXIS_HEIGHT = 24; export const SWIM_LANE_LABEL_WIDTH = 200; -const Y_AXIS_LABEL_WIDTH = 170; -const Y_AXIS_LABEL_PADDING = 8; export function isViewBySwimLaneData(arg: any): arg is ViewBySwimLaneData { return arg && arg.hasOwnProperty('cardinality'); @@ -157,100 +153,9 @@ export interface SwimlaneProps { * Enables/disables timeline on the X-axis. */ showTimeline?: boolean; + annotationsData?: AnnotationsTable['annotationsData']; } -interface SwimlaneAnnotationContainerProps { - chartWidth: number; - domain: { - min: number; - max: number; - }; - annotationsData: AnnotationsTable['annotationsData']; - tooltipService: ChartTooltipService; -} -export const SwimlaneAnnotationContainer: FC = ({ - chartWidth, - domain, - annotationsData, - tooltipService, -}) => { - const canvasRef = React.useRef(null); - - useEffect(() => { - if (canvasRef.current !== null) { - const chartElement = d3.select(canvasRef.current); - chartElement.selectAll('*').remove(); - - const startingXPos = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING; - const endingXPos = startingXPos + chartWidth - Y_AXIS_LABEL_PADDING; - - const svg = chartElement - .append('svg') - .attr('width', '100%') - .attr('height', ANNOTATION_HEIGHT); - - const xScale = scaleTime().domain([domain.min, domain.max]).range([startingXPos, endingXPos]); - - // remove container altogether - // const container = svg.append('rect'); - // container - // .attr('x', startingXPos) - // .attr('y', 0) - // .attr('height', ANNOTATION_HEIGHT) - // .attr('width', endingXPos - startingXPos) - // .style('stroke', 'none') - // .style('fill', '#ccc') - // .style('stroke-width', 1); - - annotationsData.forEach((d) => { - svg - .append('rect') - .classed('mlAnnotationRect', true) - .attr('x', xScale(d.timestamp)) - .attr('y', 0) - .attr('height', ANNOTATION_HEIGHT) - .attr('width', 5) - .on('mouseover', function (a) { - const startingTime = formatHumanReadableDateTimeSeconds(d.timestamp); - const endingTime = - d.end_timestamp !== undefined - ? formatHumanReadableDateTimeSeconds(d.end_timestamp) - : undefined; - - const timeLabel = endingTime ? `${startingTime} - ${endingTime}` : startingTime; - - tooltipService.show( - [ - // @ts-ignore only need label to show - { - label: `${d.annotation}`, - seriesIdentifier: { - key: 'anomaly_timeline', - specId: d._id ?? `${d.annotation}-${d.timestamp}-label`, - }, - valueAccessor: 'timespan', - }, - // @ts-ignore only need label to show - { - label: `${timeLabel}`, - seriesIdentifier: { - key: 'anomaly_timeline', - specId: d._id ?? `${d.annotation}-${d.timestamp}-ts`, - }, - valueAccessor: 'timespan', - }, - ], - this - ); - }) - .on('mouseout', () => tooltipService.hide()); - }); - } - }, [chartWidth, domain, annotationsData]); - - return
; -}; - /** * Anomaly swim lane container responsible for handling resizing, pagination and * providing swim lane vis with required props. From e9429242625ebee9eaa28c45638bd32e6d3bebe0 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 14 Apr 2021 21:58:15 -0500 Subject: [PATCH 03/15] [ML] Clean up load all annotations --- .../explorer/actions/load_explorer_data.ts | 15 +++--- .../application/explorer/explorer_utils.d.ts | 6 +++ .../application/explorer/explorer_utils.js | 51 +++++++++++++++++++ .../outlier_detection_creation.ts | 4 +- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index c6c980f82cef5..f3114b4505b6a 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -24,6 +24,7 @@ import { loadDataForCharts, loadFilteredTopInfluencers, loadTopInfluencers, + loadAllAnnotations, AppStateSelectedCells, ExplorerJob, } from '../explorer_utils'; @@ -55,6 +56,8 @@ const memoize = any>(func: T, context?: any) => { return memoizeOne(wrapWithLastRefreshArg(func, context) as any, memoizeIsEqual); }; +const memoizedLoadAllAnnotations = memoize(loadAllAnnotations); + const memoizedLoadAnnotationsTableData = memoize( loadAnnotationsTableData ); @@ -155,13 +158,7 @@ const loadExplorerDataProvider = ( // First get the data where we have all necessary args at hand using forkJoin: // annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues return forkJoin({ - rawAnnotationsData: memoizedLoadAnnotationsTableData( - lastRefresh, - undefined, - selectedJobs, - interval, - bounds - ), + allAnnotations: memoizedLoadAllAnnotations(lastRefresh, selectedJobs, interval, bounds), annotationsData: memoizedLoadAnnotationsTableData( lastRefresh, selectedCells, @@ -291,11 +288,11 @@ const loadExplorerDataProvider = ( ), }), ( - { rawAnnotationsData, annotationsData, overallState, tableData }, + { allAnnotations, annotationsData, overallState, tableData }, { influencers, viewBySwimlaneState } ): Partial => { return { - allAnnotations: rawAnnotationsData, + allAnnotations, annotations: annotationsData, influencers: influencers as any, loading: false, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts index 9e24a4349584e..8f775cbc3365d 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts @@ -109,6 +109,12 @@ declare interface SwimlaneBounds { latest: number; } +export declare const loadAllAnnotations: ( + selectedJobs: ExplorerJob[], + interval: number, + bounds: TimeRangeBounds +) => Promise; + export declare const loadAnnotationsTableData: ( selectedCells: AppStateSelectedCells | undefined, selectedJobs: ExplorerJob[], diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index 2ebdf0a524f07..8099de12b8fae 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -439,6 +439,57 @@ export function loadAllAnnotationsData(selectedJobs, interval, bounds) { }); } +export function loadAllAnnotations(selectedJobs, interval, bounds) { + const jobIds = selectedJobs.map((d) => d.id); + const timeRange = getSelectionTimeRange(undefined, interval, bounds); + + return new Promise((resolve) => { + ml.annotations + .getAnnotations$({ + jobIds, + earliestMs: timeRange.earliestMs, + latestMs: timeRange.latestMs, + maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE, + }) + .toPromise() + .then((resp) => { + if (resp.error !== undefined || resp.annotations === undefined) { + const errorMessage = extractErrorMessage(resp.error); + return resolve({ + annotationsData: [], + error: errorMessage !== '' ? errorMessage : undefined, + }); + } + + const annotationsData = []; + jobIds.forEach((jobId) => { + const jobAnnotations = resp.annotations[jobId]; + if (jobAnnotations !== undefined) { + annotationsData.push(...jobAnnotations); + } + }); + + return resolve({ + annotationsData: annotationsData + .sort((a, b) => { + return a.timestamp - b.timestamp; + }) + .map((d, i) => { + d.key = (i + 1).toString(); + return d; + }), + }); + }) + .catch((resp) => { + const errorMessage = extractErrorMessage(resp); + return resolve({ + annotationsData: [], + error: errorMessage !== '' ? errorMessage : undefined, + }); + }); + }); +} + export function loadAnnotationsTableData(selectedCells, selectedJobs, interval, bounds) { const jobIds = selectedCells !== undefined && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index e73a477d21b1b..3d149bae3d3bb 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -77,6 +77,8 @@ export default function ({ getService }: FtrProviderContext) { status: 'stopped', progress: '100', }, + runtimeEditorContent: + '{"lowercase_central_air":{"type":"keyword","script":"emit(params._source.CentralAir.toLowerCase())"}}', }, }, ]; @@ -125,7 +127,7 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ - '{"lowercase_central_air":{"type":"keyword","script":"emit(params._source.CentralAir.toLowerCase())"}}', + testData.expected.runtimeEditorContent, ]); await ml.testExecution.logTestStep('does not display the dependent variable input'); From df8f981294edc88ed93446c3b3ad23e18a9058ea Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 14 Apr 2021 22:27:41 -0500 Subject: [PATCH 04/15] Fix allAnnotations --- .../explorer/actions/load_explorer_data.ts | 1 + .../application/explorer/explorer_utils.js | 54 ------------------- 2 files changed, 1 insertion(+), 54 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index 88dfa07c83296..6aa3f20d7cf58 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -220,6 +220,7 @@ const loadExplorerDataProvider = ( tap(explorerService.setChartsDataLoading), mergeMap( ({ + allAnnotations, anomalyChartRecords, influencers, overallState, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index dbbf20b587742..b27931dbb4ca5 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -385,60 +385,6 @@ export function getViewBySwimlaneOptions({ }; } -export function loadAllAnnotationsData(selectedJobs, interval, bounds) { - const jobIds = selectedJobs.map((d) => d.id); - const timeRange = getSelectionTimeRange(undefined, interval, bounds); - - return new Promise((resolve) => { - ml.annotations - .getAnnotations$({ - jobIds, - earliestMs: timeRange.earliestMs, - latestMs: timeRange.latestMs, - maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE, - }) - .toPromise() - .then((resp) => { - if (resp.error !== undefined || resp.annotations === undefined) { - const errorMessage = extractErrorMessage(resp.error); - return resolve({ - annotationsData: [], - aggregations: {}, - error: errorMessage !== '' ? errorMessage : undefined, - }); - } - - const annotationsData = []; - jobIds.forEach((jobId) => { - const jobAnnotations = resp.annotations[jobId]; - if (jobAnnotations !== undefined) { - annotationsData.push(...jobAnnotations); - } - }); - - return resolve({ - annotationsData: annotationsData - .sort((a, b) => { - return a.timestamp - b.timestamp; - }) - .map((d, i) => { - d.key = (i + 1).toString(); - return d; - }), - aggregations: resp.aggregations, - }); - }) - .catch((resp) => { - const errorMessage = extractErrorMessage(resp); - return resolve({ - annotationsData: [], - aggregations: {}, - error: errorMessage !== '' ? errorMessage : undefined, - }); - }); - }); -} - export function loadAllAnnotations(selectedJobs, interval, bounds) { const jobIds = selectedJobs.map((d) => d.id); const timeRange = getSelectionTimeRange(undefined, interval, bounds); From 7a5bedfbaa913db3ff4e7539052c75ffd46a3935 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 14 Apr 2021 22:31:45 -0500 Subject: [PATCH 05/15] Remove unrelated change --- .../ml/data_frame_analytics/outlier_detection_creation.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index 3d149bae3d3bb..e73a477d21b1b 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -77,8 +77,6 @@ export default function ({ getService }: FtrProviderContext) { status: 'stopped', progress: '100', }, - runtimeEditorContent: - '{"lowercase_central_air":{"type":"keyword","script":"emit(params._source.CentralAir.toLowerCase())"}}', }, }, ]; @@ -127,7 +125,7 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ - testData.expected.runtimeEditorContent, + '{"lowercase_central_air":{"type":"keyword","script":"emit(params._source.CentralAir.toLowerCase())"}}', ]); await ml.testExecution.logTestStep('does not display the dependent variable input'); From bc00e98d645d385fb388eeb331469e3f89f51d5e Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Thu, 15 Apr 2021 11:02:45 -0500 Subject: [PATCH 06/15] [ML] Add annotation border, label text, rename allAnnotations -> overallAnnotations --- .../explorer/actions/load_explorer_data.ts | 17 +++++++---- .../application/explorer/anomaly_timeline.tsx | 7 +++-- .../application/explorer/explorer_utils.d.ts | 2 +- .../application/explorer/explorer_utils.js | 2 +- .../reducers/explorer_reducer/state.ts | 4 +-- .../swimlane_annotation_container.tsx | 28 +++++++++++++++++++ .../explorer/swimlane_container.tsx | 4 ++- 7 files changed, 51 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index 6aa3f20d7cf58..f7de3d50d85a8 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -23,7 +23,7 @@ import { loadAnomaliesTableData, loadFilteredTopInfluencers, loadTopInfluencers, - loadAllAnnotations, + loadOverallAnnotations, AppStateSelectedCells, ExplorerJob, } from '../explorer_utils'; @@ -56,7 +56,9 @@ const memoize = any>(func: T, context?: any) => { return memoizeOne(wrapWithLastRefreshArg(func, context) as any, memoizeIsEqual); }; -const memoizedLoadAllAnnotations = memoize(loadAllAnnotations); +const memoizedLoadOverallAnnotations = memoize( + loadOverallAnnotations +); const memoizedLoadAnnotationsTableData = memoize( loadAnnotationsTableData @@ -157,7 +159,12 @@ const loadExplorerDataProvider = ( // First get the data where we have all necessary args at hand using forkJoin: // annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues return forkJoin({ - allAnnotations: memoizedLoadAllAnnotations(lastRefresh, selectedJobs, interval, bounds), + overallAnnotations: memoizedLoadOverallAnnotations( + lastRefresh, + selectedJobs, + interval, + bounds + ), annotationsData: memoizedLoadAnnotationsTableData( lastRefresh, selectedCells, @@ -220,7 +227,7 @@ const loadExplorerDataProvider = ( tap(explorerService.setChartsDataLoading), mergeMap( ({ - allAnnotations, + overallAnnotations, anomalyChartRecords, influencers, overallState, @@ -278,7 +285,7 @@ const loadExplorerDataProvider = ( }), map(({ viewBySwimlaneState, filteredTopInfluencers }) => { return { - allAnnotations, + overallAnnotations, annotations: annotationsData, influencers: filteredTopInfluencers as any, loading: false, diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 37276b91044b5..38cb556aaf0d2 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -87,7 +87,7 @@ export const AnomalyTimeline: FC = React.memo( viewByPerPage, swimlaneLimit, loading, - allAnnotations, + overallAnnotations, } = explorerState; const menuItems = useMemo(() => { @@ -241,10 +241,10 @@ export const AnomalyTimeline: FC = React.memo( isLoading={loading} noDataWarning={} showTimeline={false} - annotationsData={allAnnotations.annotationsData} + annotationsData={overallAnnotations.annotationsData} /> - + {viewBySwimlaneOptions.length > 0 && ( = React.memo( }) } timeBuckets={timeBuckets} + showLegend={false} swimlaneData={viewBySwimlaneData as ViewBySwimLaneData} swimlaneType={SWIMLANE_TYPE.VIEW_BY} selection={selectedCells} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts index 4fc734bf957e9..ebab308b86027 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts @@ -110,7 +110,7 @@ declare interface SwimlaneBounds { latest: number; } -export declare const loadAllAnnotations: ( +export declare const loadOverallAnnotations: ( selectedJobs: ExplorerJob[], interval: number, bounds: TimeRangeBounds diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index b27931dbb4ca5..ecf347e6b142f 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -385,7 +385,7 @@ export function getViewBySwimlaneOptions({ }; } -export function loadAllAnnotations(selectedJobs, interval, bounds) { +export function loadOverallAnnotations(selectedJobs, interval, bounds) { const jobIds = selectedJobs.map((d) => d.id); const timeRange = getSelectionTimeRange(undefined, interval, bounds); diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index 7e02a450253f6..faab658740a70 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -27,7 +27,7 @@ import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../explorer_constants'; import { InfluencersFilterQuery } from '../../../../../common/types/es_client'; export interface ExplorerState { - allAnnotations: AnnotationsTable; + overallAnnotations: AnnotationsTable; annotations: AnnotationsTable; anomalyChartsDataLoading: boolean; chartsData: ExplorerChartsData; @@ -66,7 +66,7 @@ function getDefaultIndexPattern() { export function getExplorerDefaultState(): ExplorerState { return { - allAnnotations: { + overallAnnotations: { error: undefined, annotationsData: [], aggregations: {}, diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index 9ed9b50de37b1..d241bb48792c9 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -8,6 +8,7 @@ import React, { FC, useEffect } from 'react'; import d3 from 'd3'; import { scaleTime } from 'd3-scale'; +import { i18n } from '@kbn/i18n'; import { formatHumanReadableDateTimeSeconds } from '../../../common/util/date_utils'; import { AnnotationsTable } from '../../../common/types/annotations'; import { ChartTooltipService } from '../components/chart_tooltip'; @@ -49,6 +50,33 @@ export const SwimlaneAnnotationContainer: FC = const xScale = scaleTime().domain([domain.min, domain.max]).range([startingXPos, endingXPos]); + // Add Annotation y axis label + svg + .append('text') + .attr('class', 'zoom-info-text') + .text( + i18n.translate('xpack.ml.explorer.swimlaneAnnotationLabel', { + defaultMessage: 'Annotations', + }) + ) + // @todo: figure out a better way to align this + .attr('x', Y_AXIS_LABEL_WIDTH / 2 + 24) + .attr('y', ANNOTATION_HEIGHT) + .style('fill', '#6a717d') + .style('font-size', '12px'); + + // Add border + svg + .append('rect') + .attr('x', startingXPos) + .attr('y', 0) + .attr('height', ANNOTATION_HEIGHT) + .attr('width', endingXPos - startingXPos) + .style('stroke', '#cccccc') + .style('fill', 'none') + .style('stroke-width', 1); + + // Add annotation marker annotationsData.forEach((d) => { const annotationWidth = d.end_timestamp ? xScale(d.end_timestamp) - xScale(d.timestamp) : 0; svg diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index ade47b301bb8b..cadb13f22ce54 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -133,6 +133,7 @@ export interface SwimlaneProps { filterActive?: boolean; maskAll?: boolean; timeBuckets: InstanceType; + showLegend?: boolean; swimlaneData: OverallSwimlaneData | ViewBySwimLaneData; swimlaneType: SwimlaneType; selection?: AppStateSelectedCells; @@ -177,6 +178,7 @@ export const SwimlaneContainer: FC = ({ timeBuckets, maskAll, showTimeline = true, + showLegend = true, annotationsData, 'data-test-subj': dataTestSubj, }) => { @@ -394,7 +396,7 @@ export const SwimlaneContainer: FC = ({ Date: Thu, 15 Apr 2021 11:26:22 -0500 Subject: [PATCH 07/15] [ML] Update annotation label styling --- .../explorer/swimlane_annotation_container.tsx | 9 +++++---- .../public/application/explorer/swimlane_container.tsx | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index d241bb48792c9..784b878014df0 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -16,6 +16,7 @@ import { ChartTooltipService } from '../components/chart_tooltip'; const ANNOTATION_HEIGHT = 12; export const Y_AXIS_LABEL_WIDTH = 170; export const Y_AXIS_LABEL_PADDING = 8; +export const Y_AXIS_LABEL_FONT_COLOR = '#6a717d'; interface SwimlaneAnnotationContainerProps { chartWidth: number; @@ -53,16 +54,16 @@ export const SwimlaneAnnotationContainer: FC = // Add Annotation y axis label svg .append('text') - .attr('class', 'zoom-info-text') + .attr('text-anchor', 'end') + .attr('class', 'swimlaneAnnotationLabel') .text( i18n.translate('xpack.ml.explorer.swimlaneAnnotationLabel', { defaultMessage: 'Annotations', }) ) - // @todo: figure out a better way to align this - .attr('x', Y_AXIS_LABEL_WIDTH / 2 + 24) + .attr('x', Y_AXIS_LABEL_WIDTH + Y_AXIS_LABEL_PADDING) .attr('y', ANNOTATION_HEIGHT) - .style('fill', '#6a717d') + .style('fill', Y_AXIS_LABEL_FONT_COLOR) .style('font-size', '12px'); // Add border diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index cadb13f22ce54..3885e9def1cdc 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -49,6 +49,7 @@ import { SwimlaneAnnotationContainer, Y_AXIS_LABEL_WIDTH, Y_AXIS_LABEL_PADDING, + Y_AXIS_LABEL_FONT_COLOR, } from './swimlane_annotation_container'; import { AnnotationsTable } from '../../../common/types/annotations'; @@ -306,11 +307,12 @@ export const SwimlaneContainer: FC = ({ visible: true, width: Y_AXIS_LABEL_WIDTH, // eui color subdued - fill: `#6a717d`, + fill: Y_AXIS_LABEL_FONT_COLOR, padding: Y_AXIS_LABEL_PADDING, formatter: (laneLabel: string) => { return laneLabel === '' ? EMPTY_FIELD_VALUE_LABEL : laneLabel; }, + fontSize: 12, }, xAxisLabel: { visible: true, @@ -321,6 +323,7 @@ export const SwimlaneContainer: FC = ({ const scaledDateFormat = timeBuckets.getScaledDateFormat(); return moment(v).format(scaledDateFormat); }, + fontSize: 12, }, brushMask: { fill: isDarkTheme ? 'rgb(30,31,35,80%)' : 'rgb(247,247,247,50%)', From f2138020c88b39485bfc304d4f18e424322a9b55 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Thu, 15 Apr 2021 14:01:41 -0500 Subject: [PATCH 08/15] [ML] Fix swimlane embeddable broken --- .../explorer/actions/load_explorer_data.ts | 2 +- .../explorer/swimlane_container.tsx | 39 +++++---- .../apps/ml/anomaly_detection/index.ts | 22 ++--- x-pack/test/functional/apps/ml/index.ts | 82 +++++++++---------- 4 files changed, 75 insertions(+), 70 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index f7de3d50d85a8..6d70566af1a64 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -169,7 +169,7 @@ const loadExplorerDataProvider = ( lastRefresh, selectedCells, selectedJobs, - interval, + swimlaneBucketInterval.asSeconds(), bounds ), anomalyChartRecords: anomalyExplorerChartsService.loadDataForCharts$( diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 3885e9def1cdc..b0a7bb4975604 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -369,11 +369,13 @@ export const SwimlaneContainer: FC = ({ [swimlaneData?.fieldName] ); - const xDomain = { - min: swimlaneData.earliest * 1000, - max: swimlaneData.latest * 1000, - minInterval: swimlaneData.interval * 1000, - }; + const xDomain = swimlaneData + ? { + min: swimlaneData.earliest * 1000, + max: swimlaneData.latest * 1000, + minInterval: swimlaneData.interval * 1000, + } + : undefined; // A resize observer is required to compute the bucket span based on the chart width to fetch the data accordingly return ( @@ -466,18 +468,21 @@ export const SwimlaneContainer: FC = ({ /> )}
- {swimlaneType === SWIMLANE_TYPE.OVERALL && showSwimlane && !isLoading && ( - - {(tooltipService) => ( - - )} - - )} + {swimlaneType === SWIMLANE_TYPE.OVERALL && + showSwimlane && + xDomain !== undefined && + !isLoading && ( + + {(tooltipService) => ( + + )} + + )} diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts index 6b7afacbb721a..fab42949147b4 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts @@ -11,17 +11,17 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('anomaly detection', function () { this.tags(['skipFirefox']); - loadTestFile(require.resolve('./single_metric_job')); - loadTestFile(require.resolve('./single_metric_job_without_datafeed_start')); - loadTestFile(require.resolve('./multi_metric_job')); - loadTestFile(require.resolve('./population_job')); - loadTestFile(require.resolve('./saved_search_job')); - loadTestFile(require.resolve('./advanced_job')); - loadTestFile(require.resolve('./single_metric_viewer')); + // loadTestFile(require.resolve('./single_metric_job')); + // loadTestFile(require.resolve('./single_metric_job_without_datafeed_start')); + // loadTestFile(require.resolve('./multi_metric_job')); + // loadTestFile(require.resolve('./population_job')); + // loadTestFile(require.resolve('./saved_search_job')); + // loadTestFile(require.resolve('./advanced_job')); + // loadTestFile(require.resolve('./single_metric_viewer')); loadTestFile(require.resolve('./anomaly_explorer')); - loadTestFile(require.resolve('./categorization_job')); - loadTestFile(require.resolve('./date_nanos_job')); - loadTestFile(require.resolve('./annotations')); - loadTestFile(require.resolve('./aggregated_scripted_job')); + // loadTestFile(require.resolve('./categorization_job')); + // loadTestFile(require.resolve('./date_nanos_job')); + // loadTestFile(require.resolve('./annotations')); + // loadTestFile(require.resolve('./aggregated_scripted_job')); }); } diff --git a/x-pack/test/functional/apps/ml/index.ts b/x-pack/test/functional/apps/ml/index.ts index 73207bc91e8d3..921ca8c210f85 100644 --- a/x-pack/test/functional/apps/ml/index.ts +++ b/x-pack/test/functional/apps/ml/index.ts @@ -45,49 +45,49 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.securityUI.logout(); }); - loadTestFile(require.resolve('./permissions')); - loadTestFile(require.resolve('./pages')); + // loadTestFile(require.resolve('./permissions')); + // loadTestFile(require.resolve('./pages')); loadTestFile(require.resolve('./anomaly_detection')); - loadTestFile(require.resolve('./data_visualizer')); - loadTestFile(require.resolve('./data_frame_analytics')); + // loadTestFile(require.resolve('./data_visualizer')); + // loadTestFile(require.resolve('./data_frame_analytics')); }); - describe('', function () { - this.tags('ciGroup13'); - - before(async () => { - await ml.securityCommon.createMlRoles(); - await ml.securityCommon.createMlUsers(); - }); - - after(async () => { - await ml.securityCommon.cleanMlUsers(); - await ml.securityCommon.cleanMlRoles(); - await ml.testResources.deleteSavedSearches(); - await ml.testResources.deleteDashboards(); - await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); - await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); - await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); - await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); - await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); - await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); - await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); - await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); - await esArchiver.unload('ml/farequote'); - await esArchiver.unload('ml/ecommerce'); - await esArchiver.unload('ml/categorization'); - await esArchiver.unload('ml/event_rate_nanos'); - await esArchiver.unload('ml/bm_classification'); - await esArchiver.unload('ml/ihp_outlier'); - await esArchiver.unload('ml/egs_regression'); - await esArchiver.unload('ml/module_sample_ecommerce'); - await ml.testResources.resetKibanaTimeZone(); - await ml.securityUI.logout(); - }); - - loadTestFile(require.resolve('./feature_controls')); - loadTestFile(require.resolve('./settings')); - loadTestFile(require.resolve('./embeddables')); - }); + // describe('', function () { + // this.tags('ciGroup13'); + // + // before(async () => { + // await ml.securityCommon.createMlRoles(); + // await ml.securityCommon.createMlUsers(); + // }); + // + // after(async () => { + // await ml.securityCommon.cleanMlUsers(); + // await ml.securityCommon.cleanMlRoles(); + // await ml.testResources.deleteSavedSearches(); + // await ml.testResources.deleteDashboards(); + // await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + // await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + // await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); + // await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); + // await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); + // await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); + // await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); + // await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + // await esArchiver.unload('ml/farequote'); + // await esArchiver.unload('ml/ecommerce'); + // await esArchiver.unload('ml/categorization'); + // await esArchiver.unload('ml/event_rate_nanos'); + // await esArchiver.unload('ml/bm_classification'); + // await esArchiver.unload('ml/ihp_outlier'); + // await esArchiver.unload('ml/egs_regression'); + // await esArchiver.unload('ml/module_sample_ecommerce'); + // await ml.testResources.resetKibanaTimeZone(); + // await ml.securityUI.logout(); + // }); + // + // loadTestFile(require.resolve('./feature_controls')); + // loadTestFile(require.resolve('./settings')); + // loadTestFile(require.resolve('./embeddables')); + // }); }); } From 1a8cee311b70eac2af8b25e74427942bcd2796d4 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Mon, 19 Apr 2021 11:41:48 -0500 Subject: [PATCH 09/15] [ML] Re-enable tests --- .../apps/ml/anomaly_detection/index.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts index fab42949147b4..6b7afacbb721a 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts @@ -11,17 +11,17 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('anomaly detection', function () { this.tags(['skipFirefox']); - // loadTestFile(require.resolve('./single_metric_job')); - // loadTestFile(require.resolve('./single_metric_job_without_datafeed_start')); - // loadTestFile(require.resolve('./multi_metric_job')); - // loadTestFile(require.resolve('./population_job')); - // loadTestFile(require.resolve('./saved_search_job')); - // loadTestFile(require.resolve('./advanced_job')); - // loadTestFile(require.resolve('./single_metric_viewer')); + loadTestFile(require.resolve('./single_metric_job')); + loadTestFile(require.resolve('./single_metric_job_without_datafeed_start')); + loadTestFile(require.resolve('./multi_metric_job')); + loadTestFile(require.resolve('./population_job')); + loadTestFile(require.resolve('./saved_search_job')); + loadTestFile(require.resolve('./advanced_job')); + loadTestFile(require.resolve('./single_metric_viewer')); loadTestFile(require.resolve('./anomaly_explorer')); - // loadTestFile(require.resolve('./categorization_job')); - // loadTestFile(require.resolve('./date_nanos_job')); - // loadTestFile(require.resolve('./annotations')); - // loadTestFile(require.resolve('./aggregated_scripted_job')); + loadTestFile(require.resolve('./categorization_job')); + loadTestFile(require.resolve('./date_nanos_job')); + loadTestFile(require.resolve('./annotations')); + loadTestFile(require.resolve('./aggregated_scripted_job')); }); } From 985fcede29b89b1cb306c3feb473f309b1b5777d Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 20 Apr 2021 09:38:23 -0500 Subject: [PATCH 10/15] [ML] Fix markers overflowing beyond the start and ending time range --- .../explorer/swimlane_annotation_container.tsx | 17 +++++++++++++---- .../application/explorer/swimlane_container.tsx | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index 784b878014df0..bdad2fc5edf6e 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -17,6 +17,8 @@ const ANNOTATION_HEIGHT = 12; export const Y_AXIS_LABEL_WIDTH = 170; export const Y_AXIS_LABEL_PADDING = 8; export const Y_AXIS_LABEL_FONT_COLOR = '#6a717d'; +const ANNOTATION_RECT_MARGIN = 2; +const ANNOTATION_MIN_WIDTH = 5; interface SwimlaneAnnotationContainerProps { chartWidth: number; @@ -41,8 +43,10 @@ export const SwimlaneAnnotationContainer: FC = const chartElement = d3.select(canvasRef.current); chartElement.selectAll('*').remove(); + const dimensions = canvasRef.current.getBoundingClientRect(); + const startingXPos = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING; - const endingXPos = startingXPos + chartWidth - Y_AXIS_LABEL_PADDING; + const endingXPos = dimensions.width - 2 * Y_AXIS_LABEL_PADDING - 4; const svg = chartElement .append('svg') @@ -79,14 +83,19 @@ export const SwimlaneAnnotationContainer: FC = // Add annotation marker annotationsData.forEach((d) => { - const annotationWidth = d.end_timestamp ? xScale(d.end_timestamp) - xScale(d.timestamp) : 0; + const annotationWidth = d.end_timestamp + ? xScale(Math.min(d.end_timestamp, domain.max)) - xScale(d.timestamp) + : ANNOTATION_MIN_WIDTH; + svg .append('rect') .classed('mlAnnotationRect', true) - .attr('x', xScale(d.timestamp)) + .attr('x', d.timestamp >= domain.min ? xScale(d.timestamp) : startingXPos) .attr('y', 0) .attr('height', ANNOTATION_HEIGHT) - .attr('width', Math.max(annotationWidth, 5)) + .attr('width', annotationWidth) + .attr('rx', ANNOTATION_RECT_MARGIN) + .attr('ry', ANNOTATION_RECT_MARGIN) .on('mouseover', function () { const startingTime = formatHumanReadableDateTimeSeconds(d.timestamp); const endingTime = diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index b0a7bb4975604..0f445a4872417 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -71,7 +71,7 @@ const LEGEND_HEIGHT = 34; const Y_AXIS_HEIGHT = 24; -export const SWIM_LANE_LABEL_WIDTH = 200; +export const SWIM_LANE_LABEL_WIDTH = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING; export function isViewBySwimLaneData(arg: any): arg is ViewBySwimLaneData { return arg && arg.hasOwnProperty('cardinality'); From 229b4fa7d8a4e1d082dc388f912b353932288e80 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 20 Apr 2021 09:44:49 -0500 Subject: [PATCH 11/15] [ML] Make styling more consistent with SMV --- .../explorer/swimlane_annotation_container.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index bdad2fc5edf6e..74296f3ac8c4a 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -13,12 +13,13 @@ import { formatHumanReadableDateTimeSeconds } from '../../../common/util/date_ut import { AnnotationsTable } from '../../../common/types/annotations'; import { ChartTooltipService } from '../components/chart_tooltip'; -const ANNOTATION_HEIGHT = 12; export const Y_AXIS_LABEL_WIDTH = 170; export const Y_AXIS_LABEL_PADDING = 8; export const Y_AXIS_LABEL_FONT_COLOR = '#6a717d'; -const ANNOTATION_RECT_MARGIN = 2; +const ANNOTATION_CONTAINER_HEIGHT = 12; +const ANNOTATION_MARGIN = 2; const ANNOTATION_MIN_WIDTH = 5; +const ANNOTATION_HEIGHT = ANNOTATION_CONTAINER_HEIGHT - 2 * ANNOTATION_MARGIN; interface SwimlaneAnnotationContainerProps { chartWidth: number; @@ -51,7 +52,7 @@ export const SwimlaneAnnotationContainer: FC = const svg = chartElement .append('svg') .attr('width', '100%') - .attr('height', ANNOTATION_HEIGHT); + .attr('height', ANNOTATION_CONTAINER_HEIGHT); const xScale = scaleTime().domain([domain.min, domain.max]).range([startingXPos, endingXPos]); @@ -66,7 +67,7 @@ export const SwimlaneAnnotationContainer: FC = }) ) .attr('x', Y_AXIS_LABEL_WIDTH + Y_AXIS_LABEL_PADDING) - .attr('y', ANNOTATION_HEIGHT) + .attr('y', ANNOTATION_CONTAINER_HEIGHT) .style('fill', Y_AXIS_LABEL_FONT_COLOR) .style('font-size', '12px'); @@ -75,7 +76,7 @@ export const SwimlaneAnnotationContainer: FC = .append('rect') .attr('x', startingXPos) .attr('y', 0) - .attr('height', ANNOTATION_HEIGHT) + .attr('height', ANNOTATION_CONTAINER_HEIGHT) .attr('width', endingXPos - startingXPos) .style('stroke', '#cccccc') .style('fill', 'none') @@ -91,11 +92,11 @@ export const SwimlaneAnnotationContainer: FC = .append('rect') .classed('mlAnnotationRect', true) .attr('x', d.timestamp >= domain.min ? xScale(d.timestamp) : startingXPos) - .attr('y', 0) + .attr('y', ANNOTATION_MARGIN) .attr('height', ANNOTATION_HEIGHT) .attr('width', annotationWidth) - .attr('rx', ANNOTATION_RECT_MARGIN) - .attr('ry', ANNOTATION_RECT_MARGIN) + .attr('rx', ANNOTATION_MARGIN) + .attr('ry', ANNOTATION_MARGIN) .on('mouseover', function () { const startingTime = formatHumanReadableDateTimeSeconds(d.timestamp); const endingTime = From ce159b030e71c3cb69d3aaa79e48e5268b3b2eff Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 20 Apr 2021 09:48:10 -0500 Subject: [PATCH 12/15] [ML] Renable tests --- x-pack/test/functional/apps/ml/index.ts | 82 ++++++++++++------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/x-pack/test/functional/apps/ml/index.ts b/x-pack/test/functional/apps/ml/index.ts index 921ca8c210f85..73207bc91e8d3 100644 --- a/x-pack/test/functional/apps/ml/index.ts +++ b/x-pack/test/functional/apps/ml/index.ts @@ -45,49 +45,49 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.securityUI.logout(); }); - // loadTestFile(require.resolve('./permissions')); - // loadTestFile(require.resolve('./pages')); + loadTestFile(require.resolve('./permissions')); + loadTestFile(require.resolve('./pages')); loadTestFile(require.resolve('./anomaly_detection')); - // loadTestFile(require.resolve('./data_visualizer')); - // loadTestFile(require.resolve('./data_frame_analytics')); + loadTestFile(require.resolve('./data_visualizer')); + loadTestFile(require.resolve('./data_frame_analytics')); }); - // describe('', function () { - // this.tags('ciGroup13'); - // - // before(async () => { - // await ml.securityCommon.createMlRoles(); - // await ml.securityCommon.createMlUsers(); - // }); - // - // after(async () => { - // await ml.securityCommon.cleanMlUsers(); - // await ml.securityCommon.cleanMlRoles(); - // await ml.testResources.deleteSavedSearches(); - // await ml.testResources.deleteDashboards(); - // await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); - // await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); - // await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); - // await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); - // await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); - // await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); - // await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); - // await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); - // await esArchiver.unload('ml/farequote'); - // await esArchiver.unload('ml/ecommerce'); - // await esArchiver.unload('ml/categorization'); - // await esArchiver.unload('ml/event_rate_nanos'); - // await esArchiver.unload('ml/bm_classification'); - // await esArchiver.unload('ml/ihp_outlier'); - // await esArchiver.unload('ml/egs_regression'); - // await esArchiver.unload('ml/module_sample_ecommerce'); - // await ml.testResources.resetKibanaTimeZone(); - // await ml.securityUI.logout(); - // }); - // - // loadTestFile(require.resolve('./feature_controls')); - // loadTestFile(require.resolve('./settings')); - // loadTestFile(require.resolve('./embeddables')); - // }); + describe('', function () { + this.tags('ciGroup13'); + + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + await ml.testResources.deleteSavedSearches(); + await ml.testResources.deleteDashboards(); + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); + await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); + await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); + await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); + await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + await esArchiver.unload('ml/farequote'); + await esArchiver.unload('ml/ecommerce'); + await esArchiver.unload('ml/categorization'); + await esArchiver.unload('ml/event_rate_nanos'); + await esArchiver.unload('ml/bm_classification'); + await esArchiver.unload('ml/ihp_outlier'); + await esArchiver.unload('ml/egs_regression'); + await esArchiver.unload('ml/module_sample_ecommerce'); + await ml.testResources.resetKibanaTimeZone(); + await ml.securityUI.logout(); + }); + + loadTestFile(require.resolve('./feature_controls')); + loadTestFile(require.resolve('./settings')); + loadTestFile(require.resolve('./embeddables')); + }); }); } From 49d3a8e1ede6a55540cdceae0fd1749d4de2d7e5 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 20 Apr 2021 11:23:27 -0500 Subject: [PATCH 13/15] [ML] Fix model snapshot annotation missing --- .../application/explorer/swimlane_annotation_container.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index 74296f3ac8c4a..bb2e1a1485f75 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -86,15 +86,15 @@ export const SwimlaneAnnotationContainer: FC = annotationsData.forEach((d) => { const annotationWidth = d.end_timestamp ? xScale(Math.min(d.end_timestamp, domain.max)) - xScale(d.timestamp) - : ANNOTATION_MIN_WIDTH; + : 0; svg .append('rect') .classed('mlAnnotationRect', true) - .attr('x', d.timestamp >= domain.min ? xScale(d.timestamp) : startingXPos) + .attr('x', xScale(d.timestamp)) .attr('y', ANNOTATION_MARGIN) .attr('height', ANNOTATION_HEIGHT) - .attr('width', annotationWidth) + .attr('width', Math.max(annotationWidth, ANNOTATION_MIN_WIDTH)) .attr('rx', ANNOTATION_MARGIN) .attr('ry', ANNOTATION_MARGIN) .on('mouseover', function () { From c65cd32aa9010b032f0902bf797e174697c42ac3 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 20 Apr 2021 11:28:08 -0500 Subject: [PATCH 14/15] [ML] Fix model snapshot annotation missing --- .../application/explorer/swimlane_annotation_container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index bb2e1a1485f75..af4802537aec4 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -91,7 +91,7 @@ export const SwimlaneAnnotationContainer: FC = svg .append('rect') .classed('mlAnnotationRect', true) - .attr('x', xScale(d.timestamp)) + .attr('x', d.timestamp >= domain.min ? xScale(d.timestamp) : startingXPos) .attr('y', ANNOTATION_MARGIN) .attr('height', ANNOTATION_HEIGHT) .attr('width', Math.max(annotationWidth, ANNOTATION_MIN_WIDTH)) From 55b53621389bb6102112c6173d821fa53f145b66 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 20 Apr 2021 11:34:47 -0500 Subject: [PATCH 15/15] [ML] Fix duration to account for contextXRangeStart --- .../application/explorer/swimlane_annotation_container.tsx | 3 ++- .../components/timeseries_chart/timeseries_chart.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx index af4802537aec4..686413ff0188b 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_annotation_container.tsx @@ -85,7 +85,8 @@ export const SwimlaneAnnotationContainer: FC = // Add annotation marker annotationsData.forEach((d) => { const annotationWidth = d.end_timestamp - ? xScale(Math.min(d.end_timestamp, domain.max)) - xScale(d.timestamp) + ? xScale(Math.min(d.end_timestamp, domain.max)) - + Math.max(xScale(d.timestamp), startingXPos) : 0; svg diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 3725f57eab026..9eb2390b4bf99 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -1135,7 +1135,7 @@ class TimeseriesChartIntl extends Component { .attr('y', cxtChartHeight + swlHeight + 2) .attr('height', ANNOTATION_SYMBOL_HEIGHT) .attr('width', (d) => { - const start = this.contextXScale(moment(d.timestamp)) + 1; + const start = Math.max(this.contextXScale(moment(d.timestamp)) + 1, contextXRangeStart); const end = typeof d.end_timestamp !== 'undefined' ? this.contextXScale(moment(d.end_timestamp)) - 1