Skip to content

Commit 9bec3f9

Browse files
authored
feat: add test-suite dropdown (#94838)
This PR adds the test suite dropdown to the Test Analytics page. Notes - - Adds the `TestSuiteDropdown` component - Adds that component to the tests page
1 parent 6493e48 commit 9bec3f9

File tree

2 files changed

+133
-6
lines changed

2 files changed

+133
-6
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {useCallback, useMemo} from 'react';
2+
import {useSearchParams} from 'react-router-dom';
3+
import styled from '@emotion/styled';
4+
5+
import {Badge} from 'sentry/components/core/badge';
6+
import DropdownButton from 'sentry/components/dropdownButton';
7+
import {HybridFilter} from 'sentry/components/organizations/hybridFilter';
8+
import {t} from 'sentry/locale';
9+
import {space} from 'sentry/styles/space';
10+
import {trimSlug} from 'sentry/utils/string/trimSlug';
11+
12+
// TODO: have these come from the API
13+
const PLACEHOLDER_TEST_SUITES = [
14+
'option 1',
15+
'option 2',
16+
'option 3',
17+
'super-long-option-4',
18+
];
19+
20+
const TEST_SUITE = 'testSuite';
21+
const MAX_SUITE_UI_LENGTH = 22;
22+
23+
export function TestSuiteDropdown() {
24+
const [searchParams, setSearchParams] = useSearchParams();
25+
26+
const handleChange = useCallback(
27+
(newTestSuites: string[]) => {
28+
searchParams.delete(TEST_SUITE);
29+
30+
newTestSuites.forEach(suite => {
31+
searchParams.append(TEST_SUITE, suite);
32+
});
33+
34+
setSearchParams(searchParams);
35+
},
36+
[searchParams, setSearchParams]
37+
);
38+
39+
const options = useMemo(
40+
() =>
41+
PLACEHOLDER_TEST_SUITES.map(suite => ({
42+
value: suite,
43+
label: suite,
44+
})),
45+
[]
46+
);
47+
48+
/**
49+
* Validated values that only includes the currently available test suites
50+
*/
51+
const value = useMemo(() => {
52+
const urlTestSuites = searchParams.getAll(TEST_SUITE);
53+
return urlTestSuites.filter(suite => PLACEHOLDER_TEST_SUITES.includes(suite));
54+
}, [searchParams]);
55+
56+
return (
57+
<HybridFilter
58+
checkboxPosition="leading"
59+
searchable
60+
multiple
61+
options={options}
62+
value={value}
63+
defaultValue={[]}
64+
onChange={handleChange}
65+
// TODO: Add the disabled and emptyMessage when connected to backend hook
66+
menuTitle={t('Filter Test Suites')}
67+
menuWidth={`${MAX_SUITE_UI_LENGTH}em`}
68+
trigger={triggerProps => {
69+
const areAllSuitesSelected =
70+
value.length === 0 ||
71+
PLACEHOLDER_TEST_SUITES.every(suite => value.includes(suite));
72+
// Show 2 suites only if the combined string's length does not exceed 22.
73+
// Otherwise show only 1 test suite.
74+
const totalLength =
75+
(value[0]?.length ?? 0) + (value[1]?.length ?? 0) + (value[1] ? 2 : 0);
76+
const suitesToShow =
77+
totalLength < MAX_SUITE_UI_LENGTH ? value.slice(0, 2) : value.slice(0, 1);
78+
const enumeratedLabel = suitesToShow
79+
.map(env => trimSlug(env, MAX_SUITE_UI_LENGTH))
80+
.join(', ');
81+
82+
const label = areAllSuitesSelected ? t('All Test Suites') : enumeratedLabel;
83+
const remainingCount = areAllSuitesSelected
84+
? 0
85+
: value.length - suitesToShow.length;
86+
87+
return (
88+
<DropdownButton {...triggerProps}>
89+
<TriggerLabelWrap>
90+
<TriggerLabel>{label}</TriggerLabel>
91+
</TriggerLabelWrap>
92+
{remainingCount > 0 && (
93+
<StyledBadge type="default">{`+${remainingCount}`}</StyledBadge>
94+
)}
95+
</DropdownButton>
96+
);
97+
}}
98+
/>
99+
);
100+
}
101+
102+
const TriggerLabelWrap = styled('span')`
103+
position: relative;
104+
min-width: 0;
105+
`;
106+
107+
const TriggerLabel = styled('span')`
108+
${p => p.theme.overflowEllipsis};
109+
width: auto;
110+
`;
111+
112+
const StyledBadge = styled(Badge)`
113+
margin-top: -${space(0.5)};
114+
margin-bottom: -${space(0.5)};
115+
margin-left: ${space(0.5)};
116+
flex-shrink: 0;
117+
top: auto;
118+
`;

static/app/views/codecov/tests/tests.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {BranchSelector} from 'sentry/components/codecov/branchSelector/branchSel
44
import {DateSelector} from 'sentry/components/codecov/dateSelector/dateSelector';
55
import {IntegratedOrgSelector} from 'sentry/components/codecov/integratedOrgSelector/integratedOrgSelector';
66
import {RepoSelector} from 'sentry/components/codecov/repoSelector/repoSelector';
7+
import {TestSuiteDropdown} from 'sentry/components/codecov/testSuiteDropdown/testSuiteDropdown';
78
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
89
import {space} from 'sentry/styles/space';
910
import {decodeSorts} from 'sentry/utils/queryString';
@@ -26,12 +27,15 @@ export default function TestsPage() {
2627

2728
return (
2829
<LayoutGap>
29-
<PageFilterBar condensed>
30-
<IntegratedOrgSelector />
31-
<RepoSelector />
32-
<BranchSelector />
33-
<DateSelector />
34-
</PageFilterBar>
30+
<ControlsContainer>
31+
<PageFilterBar condensed>
32+
<IntegratedOrgSelector />
33+
<RepoSelector />
34+
<BranchSelector />
35+
<DateSelector />
36+
</PageFilterBar>
37+
<TestSuiteDropdown />
38+
</ControlsContainer>
3539
{/* TODO: Conditionally show these if the branch we're in is the main branch */}
3640
<Summaries />
3741
<TestAnalyticsTable response={response} sort={sorts[0]} />
@@ -43,3 +47,8 @@ const LayoutGap = styled('div')`
4347
display: grid;
4448
gap: ${space(2)};
4549
`;
50+
51+
const ControlsContainer = styled('div')`
52+
display: flex;
53+
gap: ${space(2)};
54+
`;

0 commit comments

Comments
 (0)