diff --git a/ui/perfherder/graphs/LegendCard.jsx b/ui/perfherder/graphs/LegendCard.jsx index 8ef6cdbfc74..b96cc10b0e2 100644 --- a/ui/perfherder/graphs/LegendCard.jsx +++ b/ui/perfherder/graphs/LegendCard.jsx @@ -1,9 +1,8 @@ - +import React from 'react'; import PropTypes from 'prop-types'; import { Badge, Button, Form, CloseButton } from 'react-bootstrap'; import { getFrameworkName } from '../perf-helpers/helpers'; -import { graphColors } from '../perf-helpers/constants'; import { Perfdocs } from '../perf-helpers/perfdocs'; import GraphIcon from '../../shared/GraphIcon'; @@ -22,42 +21,49 @@ const LegendCard = ({ const newSymbols = [...symbols]; const errorMessages = []; let updates; - const newTestData = [...testData].map((item) => { - if (item.signature_id === series.signature_id) { - const isVisible = !item.visible; - - if (isVisible && newColors.length && newSymbols.length) { - item.color = newColors.pop(); - item.symbol = newSymbols.pop(); - item.visible = isVisible; - item.data = item.data.map((test) => ({ - ...test, - z: item.color[1], - _z: item.symbol, - })); - } else if (!isVisible) { - newColors.push(item.color); - newSymbols.push(item.symbol); - item.color = ['border-secondary', '']; - item.symbol = ['circle', 'outline']; - item.visible = isVisible; - item.data = item.data.map((test) => ({ - ...test, - z: item.color[1], - _z: item.symbol, - })); - } else { - errorMessages.push( - "The graph supports viewing 6 tests at a time. To select and view a test that isn't currently visible, first deselect a visible test", - ); - } - } - return item; - }); + const targetIndex = testData.findIndex( + (item) => item.signature_id === series.signature_id, + ); + const item = testData[targetIndex]; + const isVisible = !item.visible; + const updatedItem = { ...item }; + + if (isVisible && newColors.length && newSymbols.length) { + updatedItem.color = newColors.pop(); + updatedItem.symbol = newSymbols.pop(); + updatedItem.visible = isVisible; + updatedItem.data = item.data.map((test) => ({ + ...test, + z: updatedItem.color[1], + _z: updatedItem.symbol, + })); + } else if (!isVisible) { + newColors.push(item.color); + newSymbols.push(item.symbol); + updatedItem.color = ['border-secondary', '']; + updatedItem.symbol = ['circle', 'outline']; + updatedItem.visible = isVisible; + updatedItem.data = item.data.map((test) => ({ + ...test, + z: updatedItem.color[1], + _z: updatedItem.symbol, + })); + } else { + errorMessages.push( + "The graph supports viewing 6 tests at a time. To select and view a test that isn't currently visible, first deselect a visible test", + ); + } if (errorMessages.length) { updates = { errorMessages, visibilityChanged: false }; } else { + // rebuild the array by slicing around the updated item + const newTestData = [ + ...testData.slice(0, targetIndex), + updatedItem, + ...testData.slice(targetIndex + 1), + ]; + updates = { testData: newTestData, colors: newColors, @@ -103,28 +109,39 @@ const LegendCard = ({ newData.splice(index, 1); - // when removing a test, check to see if the next test in the queue had a color; - // if it had secondary and was deselected, reset its color and visibility to - // the removed test's color, otherwise push that color back into the colors list - if ( - newData[graphColors.length - 1] && - newData[graphColors.length - 1].color[0] === 'border-secondary' - ) { - newData[graphColors.length - 1].color = series.color; - newData[graphColors.length - 1].visible = true; - newData[graphColors.length - 1].data = newData[ - graphColors.length - 1 - ].data.map((item) => ({ - ...item, - z: series.color[1], - })); + // removing a disabled test frees nothing, since it never held a + // color, just drop it. Removing a visible test frees its + // color: promote the first currently-disabled test to take its + // place, or return the color/symbol to the pool if none is waiting. + if (series.color[0] === 'border-secondary') { resetParams(newData); - } else if (series.color[0] === 'border-secondary') { - resetParams(newData); - } else { - const newColors = [...colors, ...[series.color]]; - resetParams(newData, newColors); + return; } + + const promoteIndex = newData.findIndex( + (item) => item.color[0] === 'border-secondary', + ); + + if (promoteIndex === -1) { + const newColors = [...colors, series.color]; + const newSymbols = [...symbols, series.symbol]; + resetParams(newData, newColors, newSymbols); + return; + } + + const promoted = newData[promoteIndex]; + newData[promoteIndex] = { + ...promoted, + color: series.color, + symbol: series.symbol, + visible: true, + data: promoted.data.map((item) => ({ + ...item, + z: series.color[1], + _z: series.symbol, + })), + }; + resetParams(newData); }; const subtitleStyle = 'p-0 mb-0 border-0 text-secondary text-start'; @@ -228,4 +245,11 @@ LegendCard.propTypes = { selectedDataPoint: PropTypes.shape({}), }; -export default LegendCard; +const areEqual = (prev, next) => + prev.series === next.series && + prev.testData === next.testData && + prev.colors === next.colors && + prev.symbols === next.symbols && + prev.selectedDataPoint === next.selectedDataPoint; + +export default React.memo(LegendCard, areEqual);