From 8cf2512e6d026a3702806003935c5872645f8b12 Mon Sep 17 00:00:00 2001 From: Andrej Glavic Date: Wed, 8 Jul 2026 17:00:15 -0400 Subject: [PATCH] Bug 1901066 - Make graph tooltip view link to treeherder filter to just the single job --- .../graphs-view/graphs_view_test.jsx | 15 ++++++++ ui/perfherder/graphs/GraphTooltip.jsx | 35 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/ui/perfherder/graphs-view/graphs_view_test.jsx b/tests/ui/perfherder/graphs-view/graphs_view_test.jsx index 420640e2dad..17cd6a016fe 100644 --- a/tests/ui/perfherder/graphs-view/graphs_view_test.jsx +++ b/tests/ui/perfherder/graphs-view/graphs_view_test.jsx @@ -34,6 +34,21 @@ import { fetchMock.mock(`begin:${getApiUrl(endpoints.changelog)}`, changelogData); +// Mock the single-job endpoint so that request resolves in tests +fetchMock.mock(/\/jobs\/\d+\/$/, { + id: 1, + job_type_name: 'test-linux64/opt-talos-tp5o', + job_type_symbol: 'tp5o', + job_group_name: 'Talos performance tests', + platform: 'linux64', + platform_option: 'opt', + result: 'success', + state: 'completed', + submit_timestamp: 0, + start_timestamp: 0, + end_timestamp: 0, +}); + const graphData = createGraphData( testData, alertSummaries, diff --git a/ui/perfherder/graphs/GraphTooltip.jsx b/ui/perfherder/graphs/GraphTooltip.jsx index 26cb62505e4..edb487036b7 100644 --- a/ui/perfherder/graphs/GraphTooltip.jsx +++ b/ui/perfherder/graphs/GraphTooltip.jsx @@ -1,4 +1,4 @@ -import { useLayoutEffect, useRef, useState } from 'react'; +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import countBy from 'lodash/countBy'; import { Button } from 'react-bootstrap'; @@ -27,6 +27,9 @@ import { notify } from '../../shared/stores/notificationStore'; import { getAction } from '../../helpers/taskcluster'; import { formatTaskclusterError } from '../../helpers/errorMessage'; +// Cache job.searchStr so we can hover over multiple data points +const jobSearchStrCache = new Map(); + const GraphTooltip = ({ testData, infraAffectedData, @@ -115,11 +118,41 @@ const GraphTooltip = ({ }); } + const cacheKey = `${testDetails.repository_name}/${dataPointDetails.jobId}`; + const [jobSearchStr, setJobSearchStr] = useState( + dataPointDetails.jobId ? jobSearchStrCache.get(cacheKey) || '' : '', + ); + + useEffect(() => { + if (!dataPointDetails.jobId) return undefined; + + if (jobSearchStrCache.has(cacheKey)) { + setJobSearchStr(jobSearchStrCache.get(cacheKey)); + return undefined; + } + + let cancelled = false; + JobModel.get(testDetails.repository_name, dataPointDetails.jobId) + .then((job) => { + const searchStr = job.searchStr || ''; + jobSearchStrCache.set(cacheKey, searchStr); + if (!cancelled) setJobSearchStr(searchStr); + }) + .catch(() => { + if (!cancelled) setJobSearchStr(''); + }); + + return () => { + cancelled = true; + }; + }, [cacheKey, dataPointDetails.jobId, testDetails.repository_name]); + const jobsUrl = getJobsUrl({ repo: testDetails.repository_name, revision: dataPointDetails.revision, selectedJob: dataPointDetails.jobId, group_state: 'expanded', + ...(jobSearchStr ? { searchStr: jobSearchStr.split(' ') } : {}), }); const createAlert = async () => {