ui/perfherder/graphs/LegendCard.jsx (231 lines of code) (raw):

import React from 'react'; import PropTypes from 'prop-types'; import { Badge, Button, FormGroup, Input } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { getFrameworkName } from '../perf-helpers/helpers'; import { graphColors } from '../perf-helpers/constants'; import { Perfdocs } from '../perf-helpers/perfdocs'; import GraphIcon from '../../shared/GraphIcon'; const LegendCard = ({ series, testData, updateState, updateStateParams, selectedDataPoint, frameworks, colors, symbols, }) => { const updateSelectedTest = () => { const newColors = [...colors]; 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; }); if (errorMessages.length) { updates = { errorMessages, visibilityChanged: false }; } else { updates = { testData: newTestData, colors: newColors, symbols: newSymbols, errorMessages, visibilityChanged: true, }; } updateStateParams(updates); }; const addTestData = (option) => { const options = { option, relatedSeries: series }; updateState({ options, showModal: true }); }; const resetParams = (testData, newColors = null, newSymbols = null) => { const updates = { testData }; if (newColors) updates.colors = newColors; if (newSymbols) updates.symbols = newSymbols; if ( selectedDataPoint && selectedDataPoint.signature_id === series.signature_id ) { updates.selectedDataPoint = null; } if (testData.length === 0) { updates.highlightedRevisions = ['', '']; updates.zoom = {}; } updateStateParams(updates); }; const removeTest = () => { const index = testData.findIndex((test) => test === series); const newData = [...testData]; if (index === -1) { return; } 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], })); resetParams(newData); } else if (series.color[0] === 'border-secondary') { resetParams(newData); } else { const newColors = [...colors, ...[series.color]]; resetParams(newData, newColors); } }; const subtitleStyle = 'p-0 mb-0 border-0 text-secondary text-left'; const symbolType = series.symbol || ['circle', 'outline']; const { suite, platform, framework_id: frameworkId } = series; const framework = getFrameworkName(frameworks, frameworkId); const perfdocs = new Perfdocs(framework, suite, platform); const hasDocumentation = perfdocs.hasDocumentation(); return ( <FormGroup check className="pl-0 border"> <Button className="close mr-3 my-2 ml-2 bg-transparent" onClick={removeTest} data-testid="remove-test-button" > <FontAwesomeIcon className="pointer" icon={faTimes} size="xs" title="" /> </Button> <div className={`${series.color[0]} graph-legend-card p-3`}> <Button color="link" outline className={`p-0 mb-0 pointer border-0 ${ series.visible ? series.color[0] : 'text-muted' } text-left`} onClick={() => addTestData('addRelatedConfigs')} title="Add related configurations" > <GraphIcon iconType={symbolType[0]} fill={symbolType[1] === 'fill' ? series.color[1] : '#ffffff'} stroke={series.color[1]} /> {series.name} </Button> <div className="small legend-docs"> {hasDocumentation && ( <a href={perfdocs.documentationURL} target="_blank" rel="noopener noreferrer" > (docs) </a> )} </div> <Button color="link" outline className={`w-100 ${subtitleStyle}`} onClick={() => addTestData('addRelatedBranches')} title="Add related branches" > {series.repository_name} </Button> <Button color="link" outline className={`w-100 ${subtitleStyle}`} onClick={() => addTestData('addRelatedPlatform')} title="Add related platforms and branches" > {series.platform} </Button> {series.application && ( <Button color="link" outline className={`w-100 ${subtitleStyle}`} title="Add related applications" onClick={() => addTestData('addRelatedApplications')} > {series.application} </Button> )} <Badge> {framework} </Badge> <div className="small">{`should_alert: ${ series.shouldAlert !== false }`}</div> <div className="small">{`alert_change_type: ${ series.alertChangeType === 1 ? 'absolute' : 'percentage' }`}</div> <div className="small">{`alert_threshold: ${series.alertThreshold}`}</div> <div className="small">{`${series.signatureHash.slice(0, 16)}...`}</div> </div> <Input className="show-hide-check" type="checkbox" checked={series.visible} aria-label="Show/Hide series" title="Show/Hide series" onChange={updateSelectedTest} /> </FormGroup> ); }; LegendCard.propTypes = { series: PropTypes.PropTypes.shape({ visible: PropTypes.bool, }).isRequired, updateState: PropTypes.func.isRequired, testData: PropTypes.arrayOf(PropTypes.shape({})), updateStateParams: PropTypes.func.isRequired, colors: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired, selectedDataPoint: PropTypes.shape({}), }; LegendCard.defaultProps = { testData: [], selectedDataPoint: null, }; export default LegendCard;