From ce27e6d912a7b353b8dd1584c34452367acaa2d8 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 13 Sep 2018 17:27:59 +0200 Subject: [PATCH 1/6] [ML] Initial migration of swimlane from jQuery to d3. --- .../ml/public/explorer/explorer_swimlane.js | 259 +++++++++--------- 1 file changed, 126 insertions(+), 133 deletions(-) diff --git a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js index bb20a73afdd9c2..24bc838af45e3c 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js @@ -15,7 +15,6 @@ import React from 'react'; import _ from 'lodash'; import d3 from 'd3'; -import $ from 'jquery'; import moment from 'moment'; // don't use something like plugins/ml/../common @@ -43,16 +42,16 @@ export class ExplorerSwimlane extends React.Component { componentWillUnmount() { const { mlExplorerDashboardService } = this.props; mlExplorerDashboardService.dragSelect.unwatch(this.boundDragSelectListener); - const element = $(this.rootNode); + const element = d3.select(this.rootNode); element.empty(); } componentDidMount() { - const element = $(this.rootNode).parent(); + const element = d3.select(this.rootNode.parentNode); const { mlExplorerDashboardService } = this.props; // Consider the setting to support to select a range of cells if (!mlExplorerDashboardService.allowCellRangeSelection) { - element.addClass('ml-hide-range-selection'); + element.classed('ml-hide-range-selection', true); } // save the bound dragSelectListener to this property so it can be accessed again @@ -78,14 +77,15 @@ export class ExplorerSwimlane extends React.Component { // Listen for dragSelect events dragSelectListener({ action, elements = [] }) { - const element = $(this.rootNode).parent(); + const element = d3.select(this.rootNode.parentNode); const { swimlaneType } = this.props; if (action === DRAG_SELECT_ACTION.NEW_SELECTION && elements.length > 0) { - const firstCellData = $(elements[0]).data('click'); + const firstCellData = JSON.parse(d3.select(elements[0]).attr('data-click')); + if (typeof firstCellData !== 'undefined' && swimlaneType === firstCellData.swimlaneType) { const selectedData = elements.reduce((d, e) => { - const cellData = $(e).data('click'); + const cellData = JSON.parse(d3.select(e).attr('data-click')); d.bucketScore = Math.max(d.bucketScore, cellData.bucketScore); d.laneLabels.push(cellData.laneLabel); d.times.push(cellData.time); @@ -106,7 +106,7 @@ export class ExplorerSwimlane extends React.Component { this.setState({ cellMouseoverActive: true }); } else if (action === DRAG_SELECT_ACTION.ELEMENT_SELECT) { - element.addClass('ml-dragselect-dragging'); + element.classed('ml-dragselect-dragging', true); return; } else if (action === DRAG_SELECT_ACTION.DRAG_START) { this.setState({ cellMouseoverActive: false }); @@ -114,8 +114,8 @@ export class ExplorerSwimlane extends React.Component { } this.previousSelectedData = null; - element.removeClass('ml-dragselect-dragging'); - elements.map(e => $(e).removeClass('ds-selected')); + element.classed('ml-dragselect-dragging', false); + elements.map(e => d3.select(e).classed('ds-selected', false)); } cellClick(cellsToSelect, { laneLabels, bucketScore, times }) { @@ -127,7 +127,7 @@ export class ExplorerSwimlane extends React.Component { } checkForSelection() { - const element = $(this.rootNode).parent(); + const element = d3.select(this.rootNode.parentNode); const { appState, @@ -141,8 +141,8 @@ export class ExplorerSwimlane extends React.Component { const selectedType = _.get(selectionState, 'selectedType', undefined); const viewBy = _.get(selectionState, 'viewBy', ''); if (swimlaneType !== selectedType && selectedType !== undefined) { - $('.lane-label', element).addClass('lane-label-masked'); - $('.sl-cell-inner', element).addClass('sl-cell-inner-masked'); + element.selectAll('.lane-label').classed('lane-label-masked', true); + element.selectAll('.sl-cell-inner').classed('sl-cell-inner-masked', true); } if ((swimlaneType !== selectedType) || @@ -163,23 +163,20 @@ export class ExplorerSwimlane extends React.Component { selectedLanes.forEach((selectedLane) => { if (lanes.indexOf(selectedLane) > -1 && selectedTimeExtent[0] >= startTime && selectedTimeExtent[1] <= endTime) { // Locate matching cell - look for exact time, otherwise closest before. - const $swimlanes = element.find('.ml-swimlanes').first(); - const laneCells = $('div[data-lane-label="' + mlEscape(selectedLane) + '"]', $swimlanes); - if (laneCells.length === 0) { - return; - } + const swimlanes = element.select('.ml-swimlanes'); + const laneCells = swimlanes.selectAll(`div[data-lane-label="${mlEscape(selectedLane)}"]`); - for (let i = 0; i < laneCells.length; i++) { - const cell = laneCells[i]; - const cellTime = $(cell).attr('data-time'); + laneCells.each(function () { + const cell = d3.select(this); + const cellTime = cell.attr('data-time'); if (cellTime >= selectedTimeExtent[0] && cellTime <= selectedTimeExtent[1]) { - cellsToSelect.push(cell); + cellsToSelect.push(cell.node()); } - } + }); } }); const selectedMaxBucketScore = cellsToSelect.reduce((maxBucketScore, cell) => { - return Math.max(maxBucketScore, +$(cell).attr('data-score') || 0); + return Math.max(maxBucketScore, +d3.select(cell).attr('data-bucket-score') || 0); }, 0); if (cellsToSelect.length > 1 || selectedMaxBucketScore > 0) { this.selectCell(cellsToSelect, selectedLanes, selectedTimes, selectedMaxBucketScore); @@ -197,25 +194,28 @@ export class ExplorerSwimlane extends React.Component { swimlaneType } = this.props; - $('.lane-label', '.ml-explorer-swimlane').addClass('lane-label-masked'); - $('.sl-cell-inner,.sl-cell-inner-dragselect', '.ml-explorer-swimlane').addClass('sl-cell-inner-masked'); - $('.sl-cell-inner.sl-cell-inner-selected,.sl-cell-inner-dragselect.sl-cell-inner-selected', - '.ml-explorer-swimlane').removeClass('sl-cell-inner-selected'); + const wrapper = d3.select(this.rootNode.parentNode); - $(cellsToSelect).find('.sl-cell-inner,.sl-cell-inner-dragselect') - .removeClass('sl-cell-inner-masked') - .addClass('sl-cell-inner-selected'); + wrapper.selectAll('.lane-label').classed('lane-label-masked', true); + wrapper.selectAll('.sl-cell-inner,.sl-cell-inner-dragselect').classed('sl-cell-inner-masked', true); + wrapper.selectAll('.sl-cell-inner.sl-cell-inner-selected,.sl-cell-inner-dragselect.sl-cell-inner-selected') + .classed('sl-cell-inner-selected', false); - $('.lane-label').filter(function () { - return laneLabels.indexOf($(this).text()) > -1; - }).removeClass('lane-label-masked'); + d3.selectAll(cellsToSelect).selectAll('.sl-cell-inner,.sl-cell-inner-dragselect') + .classed('sl-cell-inner-masked', false) + .classed('sl-cell-inner-selected', true); + + wrapper.selectAll('.lane-label') + .classed('lane-label-masked', function () { + return (laneLabels.indexOf(d3.select(this).text()) > -1); + }); if (swimlaneType === 'viewBy') { // If selecting a cell in the 'view by' swimlane, indicate the corresponding time in the Overall swimlane. - const overallSwimlane = $('ml-explorer-swimlane[swimlane-type="overall"]'); + const overallSwimlane = d3.select('ml-explorer-swimlane[swimlane-type="overall"]'); times.forEach(time => { - const overallCell = $('div[data-time="' + time + '"]', overallSwimlane).find('.sl-cell-inner,.sl-cell-inner-dragselect'); - overallCell.addClass('sl-cell-inner-selected'); + const overallCell = overallSwimlane.selectAll(`div[data-time="${time}"]`).selectAll('.sl-cell-inner,.sl-cell-inner-dragselect'); + overallCell.classed('sl-cell-inner-selected', true); }); } @@ -256,11 +256,14 @@ export class ExplorerSwimlane extends React.Component { clearSelection() { const { appState, mlExplorerDashboardService } = this.props; - $('.lane-label', '.ml-explorer-swimlane').removeClass('lane-label-masked'); - $('.sl-cell-inner', '.ml-explorer-swimlane').removeClass('sl-cell-inner-masked'); - $('.sl-cell-inner.sl-cell-inner-selected', '.ml-explorer-swimlane').removeClass('sl-cell-inner-selected'); - $('.sl-cell-inner-dragselect.sl-cell-inner-selected', '.ml-explorer-swimlane').removeClass('sl-cell-inner-selected'); - $('.ds-selected', '.ml-explorer-swimlane').removeClass('ds-selected'); + + const wrapper = d3.select(this.rootNode.parentNode); + + wrapper.selectAll('.lane-label').classed('lane-label-masked', false); + wrapper.selectAll('.sl-cell-inner').classed('sl-cell-inner-masked', false); + wrapper.selectAll('.sl-cell-inner.sl-cell-inner-selected').classed('sl-cell-inner-selected', false); + wrapper.selectAll('.sl-cell-inner-dragselect.sl-cell-inner-selected').classed('sl-cell-inner-selected', false); + wrapper.selectAll('.ds-selected').classed('sl-cell-inner-selected', false); delete appState.mlExplorerSwimlane.selectedType; delete appState.mlExplorerSwimlane.selectedLanes; @@ -271,7 +274,7 @@ export class ExplorerSwimlane extends React.Component { } renderSwimlane() { - const element = $(this.rootNode).parent(); + const element = d3.select(this.rootNode.parentNode); const { cellMouseoverActive @@ -300,9 +303,9 @@ export class ExplorerSwimlane extends React.Component { const height = (lanes.length + 1) * cellHeight - 10; const laneLabelWidth = 170; - element.css('height', (height + 20) + 'px'); - const $swimlanes = element.find('.ml-swimlanes').first(); - $swimlanes.empty(); + element.style('height', `${(height + 20)}px`); + const swimlanes = element.select('.ml-swimlanes'); + swimlanes.empty(); const cellWidth = Math.floor(chartWidth / numBuckets); @@ -316,7 +319,7 @@ export class ExplorerSwimlane extends React.Component { timeBuckets.setInterval(`${stepSecs}s`); const xAxisTickFormat = timeBuckets.getScaledDateFormat(); - function cellMouseover($event, laneLabel, bucketScore, index, time) { + function cellMouseover(target, laneLabel, bucketScore, index, time) { if (bucketScore === undefined || cellMouseoverActive === false) { return; } @@ -331,9 +334,9 @@ export class ExplorerSwimlane extends React.Component { } contents += `Max anomaly score: ${displayScore}`; - const offsets = ($event.target.className === 'sl-cell-inner' ? { x: 0, y: 0 } : { x: 2, y: 1 }); - mlChartTooltipService.show(contents, $event.target, { - x: $event.target.offsetWidth - offsets.x, + const offsets = (target.className === 'sl-cell-inner' ? { x: 0, y: 0 } : { x: 2, y: 1 }); + mlChartTooltipService.show(contents, target, { + x: target.offsetWidth - offsets.x, y: 10 + offsets.y }); } @@ -343,109 +346,99 @@ export class ExplorerSwimlane extends React.Component { } const that = this; - _.each(lanes, (lane) => { - const $lane = $('
', { - class: 'lane', - }); - - const label = mlEscape(lane); - const fieldName = mlEscape(swimlaneData.fieldName); - const laneDivProps = { - class: 'lane-label', - css: { - width: laneLabelWidth + 'px' - }, - html: label - }; - - if (swimlaneData.fieldName !== undefined) { - laneDivProps['tooltip-html-unsafe'] = `${fieldName}: ${label}`; - laneDivProps['tooltip-placement'] = 'right'; - laneDivProps['aria-label'] = `${fieldName}: ${label}`; - } - const $label = $('
', laneDivProps); - $label.on('click', () => { + const d3Lanes = swimlanes.selectAll('.lane').data(lanes); + const d3LanesEnter = d3Lanes.enter().append('div').classed('lane', true); + + d3LanesEnter.append('div') + .classed('lane-label', true) + .style('width', `${laneLabelWidth}px`) + .attr('tooltip-html-unsafe', label => `${mlEscape(swimlaneData.fieldName)}: ${mlEscape(label)}`) + .attr('tooltip-placement', 'right') + .attr('aria-label', label => `${mlEscape(swimlaneData.fieldName)}: ${mlEscape(label)}`) + .html(label => mlEscape(label)) + .on('click', () => { if (typeof appState.mlExplorerSwimlane.selectedLanes !== 'undefined') { that.clearSelection(); } }); - $lane.append($label); - const $cellsContainer = $('
', { - class: 'cells-container' - }); - $lane.append($cellsContainer); + function cellMouseOverFactory(time, i) { + // Don't use an arrow function here because we need access to `this`, + // which is where d3 supplies a reference to the corresponding DOM element. + return function (lane) { + const bucketScore = getBucketScore(lane, time); + if (bucketScore === 0) { return; } - const cellMouseOverFactory = (safeLaneTxt, bucketScore, i, time) => { - return (e) => { - cellMouseover(e, safeLaneTxt, bucketScore, i, time); - }; + const safeLaneTxt = lane.replace(/(['\\])/g, '\\$1'); + cellMouseover(this, safeLaneTxt, bucketScore, i, time); }; + } - // TODO - mark if zoomed in to bucket width? - let time = startTime; - for (let i = 0; i < numBuckets; i++) { - const $cell = $('
', { - class: 'sl-cell ', - css: { - width: cellWidth + 'px' - }, - 'data-lane-label': label, - 'data-time': time, + const cellsContainer = d3LanesEnter.append('div').classed('cells-container', true); - }); + function getBucketScore(lane, time) { + let bucketScore = 0; + const point = points.find((p) => { + return (p.value > 0 && p.laneLabel === lane && p.time === time); + }); + if (typeof point !== 'undefined') { + bucketScore = point.value; + } + return bucketScore; + } + + // TODO - mark if zoomed in to bucket width? + let time = startTime; + Array(numBuckets).fill(null).forEach((v, i) => { + const cell = cellsContainer.append('div') + .classed('sl-cell', true) + .style('width', `${cellWidth}px`) + .attr('data-lane-label', label => mlEscape(label)) + .attr('data-time', time) + .attr('data-swimlane-type', swimlaneType) + .attr('data-bucket-score', (lane) => { + return getBucketScore(lane, time); + }) + .attr('data-click', (laneLabel) => JSON.stringify({ + bucketScore: getBucketScore(laneLabel, time), + laneLabel, + swimlaneType, + time + })) + // use a factory here to bind the `time` and `i` values + // of this iteration to the event. + .on('mouseover', cellMouseOverFactory(time, i)) + .on('mouseleave', cellMouseleave); + + // calls itself with each() to get access to lane (= d3 data) + cell.append('div').each(function (lane) { + const el = d3.select(this); let color = 'none'; let bucketScore = 0; - for (let j = 0; j < points.length; j++) { - // this may break if detectors have the duplicate descriptions - if (points[j].value > 0 && points[j].laneLabel === lane && points[j].time === time) { - bucketScore = points[j].value; - color = colorScore(bucketScore); - $cell.append($('
', { - class: 'sl-cell-inner', - css: { - 'background-color': color - } - })); - $cell.attr({ 'data-score': bucketScore }); - $cell.find('.sl-cell-inner-dragselect').remove(); - } else if ($cell.find('.sl-cell-inner-dragselect').length === 0 && $cell.find('.sl-cell-inner').length === 0) { - $cell.append($('
', { - class: 'sl-cell-inner-dragselect' - })); - } - } - // Escape single quotes and backslash characters in the HTML for the event handlers. - $cell.data({ - 'click': { - bucketScore, - laneLabel: lane, - swimlaneType, - time - } + const point = points.find((p) => { + return (p.value > 0 && p.laneLabel === lane && p.time === time); }); - if (bucketScore > 0) { - const safeLaneTxt = lane.replace(/(['\\])/g, '\\$1'); - const cellMouseOver = cellMouseOverFactory(safeLaneTxt, bucketScore, i, time); - $cell.on('mouseover', cellMouseOver); - $cell.on('mouseleave', () => { - cellMouseleave(); - }); + if (typeof point !== 'undefined') { + bucketScore = point.value; + color = colorScore(bucketScore); + el.classed('sl-cell-inner', true) + .style('background-color', color) + .attr('data-score', bucketScore); + } else { + el.classed('sl-cell-inner-dragselect', true); } + }); - $cellsContainer.append($cell); - - time += stepSecs; - } - - $swimlanes.append($lane); + time += stepSecs; }); - const laneTimes = d3.select($swimlanes.get(0)) + // ['x-axis'] is just a placeholder so we have an array of 1. + const laneTimes = swimlanes.selectAll('.time-tick-labels').data(['x-axis']) + .enter() .append('div') .classed('time-tick-labels', true); From e78cdebd7865f89414e9da324b58923b730eafb1 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 13 Sep 2018 17:56:24 +0200 Subject: [PATCH 2/6] [ML] Fixes deselection and re-rendering issue. --- .../plugins/ml/public/explorer/explorer_swimlane.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js index 24bc838af45e3c..5b38ee736d08da 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js @@ -43,7 +43,7 @@ export class ExplorerSwimlane extends React.Component { const { mlExplorerDashboardService } = this.props; mlExplorerDashboardService.dragSelect.unwatch(this.boundDragSelectListener); const element = d3.select(this.rootNode); - element.empty(); + element.html(''); } componentDidMount() { const element = d3.select(this.rootNode.parentNode); @@ -194,7 +194,8 @@ export class ExplorerSwimlane extends React.Component { swimlaneType } = this.props; - const wrapper = d3.select(this.rootNode.parentNode); + // This select both overall and viewby swimlane + const wrapper = d3.selectAll('.ml-explorer-swimlane'); wrapper.selectAll('.lane-label').classed('lane-label-masked', true); wrapper.selectAll('.sl-cell-inner,.sl-cell-inner-dragselect').classed('sl-cell-inner-masked', true); @@ -205,7 +206,8 @@ export class ExplorerSwimlane extends React.Component { .classed('sl-cell-inner-masked', false) .classed('sl-cell-inner-selected', true); - wrapper.selectAll('.lane-label') + const rootParent = d3.select(this.rootNode.parentNode); + rootParent.selectAll('.lane-label') .classed('lane-label-masked', function () { return (laneLabels.indexOf(d3.select(this).text()) > -1); }); @@ -257,7 +259,8 @@ export class ExplorerSwimlane extends React.Component { clearSelection() { const { appState, mlExplorerDashboardService } = this.props; - const wrapper = d3.select(this.rootNode.parentNode); + // This select both overall and viewby swimlane + const wrapper = d3.selectAll('.ml-explorer-swimlane'); wrapper.selectAll('.lane-label').classed('lane-label-masked', false); wrapper.selectAll('.sl-cell-inner').classed('sl-cell-inner-masked', false); @@ -305,7 +308,7 @@ export class ExplorerSwimlane extends React.Component { element.style('height', `${(height + 20)}px`); const swimlanes = element.select('.ml-swimlanes'); - swimlanes.empty(); + swimlanes.html(''); const cellWidth = Math.floor(chartWidth / numBuckets); From 350dba4961b4d97f3837b8d750c3d71a3e4dde55 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 13 Sep 2018 19:08:05 +0200 Subject: [PATCH 3/6] [ML] Don't use JSON.stringify/parse. --- .../ml/public/explorer/explorer_swimlane.js | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js index 5b38ee736d08da..ab0da1c873e731 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js @@ -81,11 +81,11 @@ export class ExplorerSwimlane extends React.Component { const { swimlaneType } = this.props; if (action === DRAG_SELECT_ACTION.NEW_SELECTION && elements.length > 0) { - const firstCellData = JSON.parse(d3.select(elements[0]).attr('data-click')); + const firstCellData = d3.select(elements[0]).node().__clickData__; if (typeof firstCellData !== 'undefined' && swimlaneType === firstCellData.swimlaneType) { const selectedData = elements.reduce((d, e) => { - const cellData = JSON.parse(d3.select(e).attr('data-click')); + const cellData = d3.select(e).node().__clickData__; d.bucketScore = Math.max(d.bucketScore, cellData.bucketScore); d.laneLabels.push(cellData.laneLabel); d.times.push(cellData.time); @@ -356,14 +356,19 @@ export class ExplorerSwimlane extends React.Component { d3LanesEnter.append('div') .classed('lane-label', true) .style('width', `${laneLabelWidth}px`) - .attr('tooltip-html-unsafe', label => `${mlEscape(swimlaneData.fieldName)}: ${mlEscape(label)}`) - .attr('tooltip-placement', 'right') - .attr('aria-label', label => `${mlEscape(swimlaneData.fieldName)}: ${mlEscape(label)}`) .html(label => mlEscape(label)) .on('click', () => { if (typeof appState.mlExplorerSwimlane.selectedLanes !== 'undefined') { that.clearSelection(); } + }) + .each(function () { + if (swimlaneData.fieldName !== undefined) { + d3.select(this) + .attr('tooltip-html-unsafe', label => `${mlEscape(swimlaneData.fieldName)}: ${mlEscape(label)}`) + .attr('tooltip-placement', 'right') + .attr('aria-label', label => `${mlEscape(swimlaneData.fieldName)}: ${mlEscape(label)}`); + } }); function cellMouseOverFactory(time, i) { @@ -399,20 +404,21 @@ export class ExplorerSwimlane extends React.Component { .style('width', `${cellWidth}px`) .attr('data-lane-label', label => mlEscape(label)) .attr('data-time', time) - .attr('data-swimlane-type', swimlaneType) .attr('data-bucket-score', (lane) => { return getBucketScore(lane, time); }) - .attr('data-click', (laneLabel) => JSON.stringify({ - bucketScore: getBucketScore(laneLabel, time), - laneLabel, - swimlaneType, - time - })) // use a factory here to bind the `time` and `i` values // of this iteration to the event. .on('mouseover', cellMouseOverFactory(time, i)) - .on('mouseleave', cellMouseleave); + .on('mouseleave', cellMouseleave) + .each(function (laneLabel) { + this.__clickData__ = { + bucketScore: getBucketScore(laneLabel, time), + laneLabel, + swimlaneType, + time + }; + }); // calls itself with each() to get access to lane (= d3 data) cell.append('div').each(function (lane) { @@ -429,8 +435,7 @@ export class ExplorerSwimlane extends React.Component { bucketScore = point.value; color = colorScore(bucketScore); el.classed('sl-cell-inner', true) - .style('background-color', color) - .attr('data-score', bucketScore); + .style('background-color', color); } else { el.classed('sl-cell-inner-dragselect', true); } From 33e5d5959d146093d0077418492c3f442c0f0e9b Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 13 Sep 2018 19:12:20 +0200 Subject: [PATCH 4/6] [ML] Fixed tests. --- .../explorer/__snapshots__/explorer_swimlane.test.js.snap | 2 +- x-pack/plugins/ml/public/explorer/explorer_swimlane.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/explorer/__snapshots__/explorer_swimlane.test.js.snap b/x-pack/plugins/ml/public/explorer/__snapshots__/explorer_swimlane.test.js.snap index d601b30ca757a3..9f52882052f8c6 100644 --- a/x-pack/plugins/ml/public/explorer/__snapshots__/explorer_swimlane.test.js.snap +++ b/x-pack/plugins/ml/public/explorer/__snapshots__/explorer_swimlane.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ExplorerSwimlane Overall swimlane 1`] = `"
Overall
2017-02-07T00:00:00Z2017-02-07T00:30:00Z2017-02-07T01:00:00Z2017-02-07T01:30:00Z2017-02-07T02:00:00Z2017-02-07T02:30:00Z2017-02-07T03:00:00Z2017-02-07T03:30:00Z2017-02-07T04:00:00Z2017-02-07T04:30:00Z2017-02-07T05:00:00Z2017-02-07T05:30:00Z2017-02-07T06:00:00Z2017-02-07T06:30:00Z2017-02-07T07:00:00Z2017-02-07T07:30:00Z2017-02-07T08:00:00Z2017-02-07T08:30:00Z2017-02-07T09:00:00Z2017-02-07T09:30:00Z2017-02-07T10:00:00Z2017-02-07T10:30:00Z2017-02-07T11:00:00Z2017-02-07T11:30:00Z2017-02-07T12:00:00Z2017-02-07T12:30:00Z2017-02-07T13:00:00Z2017-02-07T13:30:00Z2017-02-07T14:00:00Z2017-02-07T14:30:00Z2017-02-07T15:00:00Z2017-02-07T15:30:00Z2017-02-07T16:00:00Z
"`; +exports[`ExplorerSwimlane Overall swimlane 1`] = `"
Overall
2017-02-07T00:00:00Z2017-02-07T00:30:00Z2017-02-07T01:00:00Z2017-02-07T01:30:00Z2017-02-07T02:00:00Z2017-02-07T02:30:00Z2017-02-07T03:00:00Z2017-02-07T03:30:00Z2017-02-07T04:00:00Z2017-02-07T04:30:00Z2017-02-07T05:00:00Z2017-02-07T05:30:00Z2017-02-07T06:00:00Z2017-02-07T06:30:00Z2017-02-07T07:00:00Z2017-02-07T07:30:00Z2017-02-07T08:00:00Z2017-02-07T08:30:00Z2017-02-07T09:00:00Z2017-02-07T09:30:00Z2017-02-07T10:00:00Z2017-02-07T10:30:00Z2017-02-07T11:00:00Z2017-02-07T11:30:00Z2017-02-07T12:00:00Z2017-02-07T12:30:00Z2017-02-07T13:00:00Z2017-02-07T13:30:00Z2017-02-07T14:00:00Z2017-02-07T14:30:00Z2017-02-07T15:00:00Z2017-02-07T15:30:00Z2017-02-07T16:00:00Z
"`; diff --git a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js index ab0da1c873e731..96f4734142bd45 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js @@ -398,7 +398,7 @@ export class ExplorerSwimlane extends React.Component { // TODO - mark if zoomed in to bucket width? let time = startTime; - Array(numBuckets).fill(null).forEach((v, i) => { + Array(numBuckets || 0).fill(null).forEach((v, i) => { const cell = cellsContainer.append('div') .classed('sl-cell', true) .style('width', `${cellWidth}px`) From 28f592db6a7ab7e6186463d2c43c195b3ad9214a Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 14 Sep 2018 11:44:41 +0200 Subject: [PATCH 5/6] [ML] Fixes comment typo. --- x-pack/plugins/ml/public/explorer/explorer_swimlane.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js index 96f4734142bd45..23db1064fb9d0a 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js @@ -194,7 +194,7 @@ export class ExplorerSwimlane extends React.Component { swimlaneType } = this.props; - // This select both overall and viewby swimlane + // This selects both overall and viewby swimlane const wrapper = d3.selectAll('.ml-explorer-swimlane'); wrapper.selectAll('.lane-label').classed('lane-label-masked', true); @@ -259,7 +259,7 @@ export class ExplorerSwimlane extends React.Component { clearSelection() { const { appState, mlExplorerDashboardService } = this.props; - // This select both overall and viewby swimlane + // This selects both overall and viewby swimlane const wrapper = d3.selectAll('.ml-explorer-swimlane'); wrapper.selectAll('.lane-label').classed('lane-label-masked', false); From c4a263744d6ef3931bed25428db641a3d487d9be Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 14 Sep 2018 16:17:02 +0200 Subject: [PATCH 6/6] [ML] Remove angular template creation artefact. --- x-pack/plugins/ml/public/explorer/explorer_swimlane.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js index 23db1064fb9d0a..2c0000a264fbe2 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/plugins/ml/public/explorer/explorer_swimlane.js @@ -377,9 +377,7 @@ export class ExplorerSwimlane extends React.Component { return function (lane) { const bucketScore = getBucketScore(lane, time); if (bucketScore === 0) { return; } - - const safeLaneTxt = lane.replace(/(['\\])/g, '\\$1'); - cellMouseover(this, safeLaneTxt, bucketScore, i, time); + cellMouseover(this, lane, bucketScore, i, time); }; }