From 254eb772929d0fd672ba35ec32a36127bcdcc0c4 Mon Sep 17 00:00:00 2001 From: Adrian Date: Thu, 3 Jul 2025 16:46:55 -0700 Subject: [PATCH 1/7] feat: add test search bar w/ dynamic title to TA page --- .../tests/queries/useGetTestResults.ts | 1 + .../tests/testSearchBar/testSearchBar.tsx | 78 +++++++++++++++++++ static/app/views/codecov/tests/tests.tsx | 2 + 3 files changed, 81 insertions(+) create mode 100644 static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx diff --git a/static/app/views/codecov/tests/queries/useGetTestResults.ts b/static/app/views/codecov/tests/queries/useGetTestResults.ts index 5fbc2b28db3f61..e64875db326416 100644 --- a/static/app/views/codecov/tests/queries/useGetTestResults.ts +++ b/static/app/views/codecov/tests/queries/useGetTestResults.ts @@ -149,6 +149,7 @@ export function useInfiniteTestResults() { return { data: memoizedData, + totalCount: data?.pages?.[0]?.[0]?.totalCount ?? 0, // TODO: only provide the values that we're interested in ...rest, }; diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx new file mode 100644 index 00000000000000..3fa4b4e100cf25 --- /dev/null +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -0,0 +1,78 @@ +import {useMemo} from 'react'; +import {useSearchParams} from 'react-router-dom'; +import styled from '@emotion/styled'; +import debounce from 'lodash/debounce'; + +import BaseSearchBar from 'sentry/components/searchBar'; +import {t} from 'sentry/locale'; +import {space} from 'sentry/styles/space'; + +const FILTER_TO_NAME = { + slowestTests: 'Slowest Tests', + flakyTests: 'Flaky Tests', + failedTests: 'Failed Tests', + skippedTests: 'Skipped Tests', +}; + +type TestSearchBarProps = { + testCount: number; +}; + +export function TestSearchBar({testCount}: TestSearchBarProps) { + const [searchParams, setSearchParams] = useSearchParams(); + const term = searchParams.get('term') || ''; + + const filterBy = searchParams.get('filterBy') || ''; + const testTitle = + filterBy in FILTER_TO_NAME + ? FILTER_TO_NAME[filterBy as keyof typeof FILTER_TO_NAME] + : 'Tests'; + const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; + const searchTitle = `${testTitle} (${count})`; + + const handleSearchChange = useMemo( + () => + debounce((newValue: string) => { + const currentParams = Object.fromEntries(searchParams.entries()); + + if (newValue) { + currentParams.term = newValue; + } else { + delete currentParams.term; + } + + setSearchParams(currentParams); + }, 500), + [setSearchParams, searchParams] + ); + + return ( + + {searchTitle} + + + ); +} + +const StyledSearchBar = styled(BaseSearchBar)` + flex: 1 1 auto; + min-width: 0; +`; + +const Title = styled('h2')` + white-space: nowrap; + flex-shrink: 0; + margin: 0; + font-size: ${p => p.theme.fontSize.xl}; +`; + +const Container = styled('div')` + display: flex; + align-items: center; + gap: ${space(1.5)}; + width: 100%; +`; diff --git a/static/app/views/codecov/tests/tests.tsx b/static/app/views/codecov/tests/tests.tsx index f4fd39da548f77..521452ed96ffa2 100644 --- a/static/app/views/codecov/tests/tests.tsx +++ b/static/app/views/codecov/tests/tests.tsx @@ -15,6 +15,7 @@ import type {ValidSort} from 'sentry/views/codecov/tests/testAnalyticsTable/test import TestAnalyticsTable, { isAValidSort, } from 'sentry/views/codecov/tests/testAnalyticsTable/testAnalyticsTable'; +import {TestSearchBar} from 'sentry/views/codecov/tests/testSearchBar/testSearchBar'; export default function TestsPage() { const location = useLocation(); @@ -34,6 +35,7 @@ export default function TestsPage() { {/* TODO: Conditionally show these if the branch we're in is the main branch */} + ); From c4e56f2423572dcdd592a112eb2607d4992fd211 Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 7 Jul 2025 16:42:29 -0700 Subject: [PATCH 2/7] feat: get rid of unecessary useMemo hook --- .../tests/testSearchBar/testSearchBar.tsx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx index 3fa4b4e100cf25..a40471b68de1fb 100644 --- a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -1,4 +1,3 @@ -import {useMemo} from 'react'; import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; @@ -30,21 +29,16 @@ export function TestSearchBar({testCount}: TestSearchBarProps) { const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; const searchTitle = `${testTitle} (${count})`; - const handleSearchChange = useMemo( - () => - debounce((newValue: string) => { - const currentParams = Object.fromEntries(searchParams.entries()); + const handleSearchChange = debounce((newValue: string) => { + const currentParams = Object.fromEntries(searchParams.entries()); + if (newValue) { + currentParams.term = newValue; + } else { + delete currentParams.term; + } - if (newValue) { - currentParams.term = newValue; - } else { - delete currentParams.term; - } - - setSearchParams(currentParams); - }, 500), - [setSearchParams, searchParams] - ); + setSearchParams(currentParams); + }, 500); return ( From 8fe17d88405a7d04f2af1412c52febd4ca63df56 Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 7 Jul 2025 16:54:44 -0700 Subject: [PATCH 3/7] try fixing debouncing recreation every render --- .../tests/testSearchBar/testSearchBar.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx index a40471b68de1fb..0cbcf50f7b4ae7 100644 --- a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -1,3 +1,4 @@ +import {useMemo} from 'react'; import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; @@ -29,16 +30,23 @@ export function TestSearchBar({testCount}: TestSearchBarProps) { const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; const searchTitle = `${testTitle} (${count})`; - const handleSearchChange = debounce((newValue: string) => { - const currentParams = Object.fromEntries(searchParams.entries()); - if (newValue) { - currentParams.term = newValue; - } else { - delete currentParams.term; - } + const handleSearchChange = useMemo( + () => + debounce((newValue: string) => { + setSearchParams(prev => { + const currentParams = Object.fromEntries(prev.entries()); - setSearchParams(currentParams); - }, 500); + if (newValue) { + currentParams.term = newValue; + } else { + delete currentParams.term; + } + + return currentParams; + }); + }, 500), + [setSearchParams] + ); return ( From 251a6cc49a9c49aa7ef71928fbaae00d5027d5c2 Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 7 Jul 2025 20:46:12 -0700 Subject: [PATCH 4/7] replace usememo with usecallback --- .../app/views/codecov/tests/testSearchBar/testSearchBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx index 0cbcf50f7b4ae7..9d41170ce91c90 100644 --- a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -1,4 +1,4 @@ -import {useMemo} from 'react'; +import {useCallback} from 'react'; import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; @@ -30,7 +30,7 @@ export function TestSearchBar({testCount}: TestSearchBarProps) { const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; const searchTitle = `${testTitle} (${count})`; - const handleSearchChange = useMemo( + const handleSearchChange = useCallback( () => debounce((newValue: string) => { setSearchParams(prev => { From 5b38785fd646ee40eab5917120f5800d72cc443e Mon Sep 17 00:00:00 2001 From: Adrian Date: Tue, 8 Jul 2025 10:17:53 -0700 Subject: [PATCH 5/7] add useMemo to prevent fn recreation --- .../app/views/codecov/tests/testSearchBar/testSearchBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx index 9d41170ce91c90..0cbcf50f7b4ae7 100644 --- a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -1,4 +1,4 @@ -import {useCallback} from 'react'; +import {useMemo} from 'react'; import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; @@ -30,7 +30,7 @@ export function TestSearchBar({testCount}: TestSearchBarProps) { const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; const searchTitle = `${testTitle} (${count})`; - const handleSearchChange = useCallback( + const handleSearchChange = useMemo( () => debounce((newValue: string) => { setSearchParams(prev => { From 27f1c3e355dc4224f185c4471bc8144b3bdc35a8 Mon Sep 17 00:00:00 2001 From: Adrian Date: Tue, 8 Jul 2025 11:28:17 -0700 Subject: [PATCH 6/7] fix: add useeffect to dlelete debounce ref on unmount --- .../tests/testSearchBar/testSearchBar.tsx | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx index 0cbcf50f7b4ae7..c71c398db78234 100644 --- a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -1,4 +1,4 @@ -import {useMemo} from 'react'; +import {useEffect, useRef} from 'react'; import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; @@ -30,23 +30,34 @@ export function TestSearchBar({testCount}: TestSearchBarProps) { const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; const searchTitle = `${testTitle} (${count})`; - const handleSearchChange = useMemo( - () => - debounce((newValue: string) => { - setSearchParams(prev => { - const currentParams = Object.fromEntries(prev.entries()); - - if (newValue) { - currentParams.term = newValue; - } else { - delete currentParams.term; - } - - return currentParams; - }); - }, 500), - [setSearchParams] - ); + const handleSearchChangeRef = useRef<((newValue: string) => void) | null>(null); + + useEffect(() => { + const debounced = debounce((newValue: string) => { + setSearchParams(prev => { + const currentParams = Object.fromEntries(prev.entries()); + + if (newValue) { + currentParams.term = newValue; + } else { + delete currentParams.term; + } + + return currentParams; + }); + }, 500); + + handleSearchChangeRef.current = debounced; + + // Create a use effect to cancel debounce fn on unmount to avoid memory leaks + return () => { + debounced.cancel(); + }; + }, [setSearchParams]); + + const handleSearchChange = (value: string) => { + handleSearchChangeRef.current?.(value); + }; return ( From 65e4c2b3ca947a34cec6e9cf7e960ca0edb4277c Mon Sep 17 00:00:00 2001 From: Adrian Date: Thu, 10 Jul 2025 10:18:53 -0700 Subject: [PATCH 7/7] replace ref implementation w/ useMemo instead --- .../tests/testSearchBar/testSearchBar.tsx | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx index c71c398db78234..0298f0118971ac 100644 --- a/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx +++ b/static/app/views/codecov/tests/testSearchBar/testSearchBar.tsx @@ -1,4 +1,4 @@ -import {useEffect, useRef} from 'react'; +import {useEffect, useMemo} from 'react'; import {useSearchParams} from 'react-router-dom'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; @@ -30,34 +30,30 @@ export function TestSearchBar({testCount}: TestSearchBarProps) { const count = testCount > 999 ? `${(testCount / 1000).toFixed(1)}K` : testCount; const searchTitle = `${testTitle} (${count})`; - const handleSearchChangeRef = useRef<((newValue: string) => void) | null>(null); + const handleSearchChange = useMemo( + () => + debounce((newValue: string) => { + setSearchParams(prev => { + const currentParams = Object.fromEntries(prev.entries()); + + if (newValue) { + currentParams.term = newValue; + } else { + delete currentParams.term; + } + + return currentParams; + }); + }, 500), + [setSearchParams] + ); useEffect(() => { - const debounced = debounce((newValue: string) => { - setSearchParams(prev => { - const currentParams = Object.fromEntries(prev.entries()); - - if (newValue) { - currentParams.term = newValue; - } else { - delete currentParams.term; - } - - return currentParams; - }); - }, 500); - - handleSearchChangeRef.current = debounced; - - // Create a use effect to cancel debounce fn on unmount to avoid memory leaks + // Create a use effect to cancel handleSearchChange fn on unmount to avoid memory leaks return () => { - debounced.cancel(); + handleSearchChange.cancel(); }; - }, [setSearchParams]); - - const handleSearchChange = (value: string) => { - handleSearchChangeRef.current?.(value); - }; + }, [handleSearchChange]); return (