Skip to content

feat: add reset ability for selectors #93923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 80 additions & 41 deletions static/app/components/codecov/container/codecovParamsProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,113 @@
import {useEffect} from 'react';
import {useCallback, useEffect} from 'react';
import {useSearchParams} from 'react-router-dom';

import type {CodecovContextData} from 'sentry/components/codecov/context/codecovContext';
import type {
CodecovContextData,
CodecovContextDataParams,
} from 'sentry/components/codecov/context/codecovContext';
import {CodecovContext} from 'sentry/components/codecov/context/codecovContext';
import type {CodecovPeriodOptions} from 'sentry/components/codecov/datePicker/dateSelector';
import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';

type CodecovQueryParamsProviderProps = {
children?: NonNullable<React.ReactNode>;
};

const VALUES_TO_RESET_MAP = {
integratedOrg: ['repository', 'branch'],
repository: ['branch'],
branch: [],
codecovPeriod: [],
};

export default function CodecovQueryParamsProvider({
children,
}: CodecovQueryParamsProviderProps) {
const organization = useOrganization();

const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const [localStorageState, setLocalStorageState] = useLocalStorageState(
`codecov-selection:${organization.slug}`,
{}
);

function _defineParam(key: string, defaultValue?: string | CodecovPeriodOptions) {
const queryValue = searchParams.get(key);

if (queryValue) {
return decodeURIComponent(queryValue);
}

if (key in localStorageState) {
return (localStorageState as Record<string, string>)[key];
}

if (defaultValue) {
return defaultValue;
}

return undefined;
}

const changeContextValue = useCallback(
(value: Partial<CodecovContextDataParams>) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the chunk of the PR. Basically updates the URL + storage if there are values to reset

const currentParams = Object.fromEntries(searchParams.entries());
const valueKey = Object.keys(value)[0] as keyof typeof value;
const valuesToReset = VALUES_TO_RESET_MAP[valueKey];

valuesToReset.forEach(key => {
delete currentParams[key];
});

setLocalStorageState((prev: Partial<CodecovContextDataParams>) => {
const newState = {...prev};
valuesToReset.forEach(key => {
delete newState[key as keyof CodecovContextDataParams];
});
return newState;
});

const updatedParams = {
...currentParams,
...value,
};

setSearchParams(updatedParams);
},
[searchParams, setLocalStorageState, setSearchParams]
);

useEffect(() => {
const validEntries = {
repository: location.query.repository,
integratedOrg: location.query.integratedOrg,
branch: location.query.branch,
codecovPeriod: location.query.codecovPeriod,
const entries = {
repository: searchParams.get('repository'),
integratedOrg: searchParams.get('integratedOrg'),
branch: searchParams.get('branch'),
codecovPeriod: searchParams.get('codecovPeriod'),
};

for (const [key, value] of Object.entries(validEntries)) {
if (!value || typeof value !== 'string') {
delete validEntries[key as keyof CodecovContextData];
for (const [key, value] of Object.entries(entries)) {
if (!value) {
delete entries[key as keyof typeof entries];
}
}

setLocalStorageState(prev => ({
...prev,
...validEntries,
...entries,
}));
}, [setLocalStorageState, location.query]);
}, [setLocalStorageState, searchParams]);

const repository = _defineParam('repository');
const integratedOrg = _defineParam('integratedOrg');
const branch = _defineParam('branch');
const codecovPeriod = _defineParam('codecovPeriod', '24h') as CodecovPeriodOptions;

// Repository, org and branch default to null as its value to the option not being selected.
// These only represent the unselected values and shouldn't be used when fetching backend data.
const params: CodecovContextData = {
repository:
typeof location.query.repository === 'string'
? decodeURIComponent(location.query.repository)
: 'repository' in localStorageState
? (localStorageState.repository as string)
: null,
integratedOrg:
typeof location.query.integratedOrg === 'string'
? decodeURIComponent(location.query.integratedOrg)
: 'integratedOrg' in localStorageState
? (localStorageState.integratedOrg as string)
: null,
branch:
typeof location.query.branch === 'string'
? decodeURIComponent(location.query.branch)
: 'branch' in localStorageState
? (localStorageState.branch as string)
: null,
codecovPeriod:
typeof location.query.codecovPeriod === 'string'
? (decodeURIComponent(location.query.codecovPeriod) as CodecovPeriodOptions)
: 'codecovPeriod' in localStorageState
? (localStorageState.codecovPeriod as CodecovPeriodOptions)
: '24h',
...(repository ? {repository} : {}),
...(integratedOrg ? {integratedOrg} : {}),
...(branch ? {branch} : {}),
codecovPeriod,
changeContextValue,
};

return <CodecovContext.Provider value={params}>{children}</CodecovContext.Provider>;
Expand Down
9 changes: 6 additions & 3 deletions static/app/components/codecov/context/codecovContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import {createContext, useContext} from 'react';
import type {CodecovPeriodOptions} from 'sentry/components/codecov/datePicker/dateSelector';

export type CodecovContextData = {
branch: string | null;
changeContextValue: (value: Partial<CodecovContextDataParams>) => void;
codecovPeriod: CodecovPeriodOptions;
integratedOrg: string | null;
repository: string | null;
branch?: string;
integratedOrg?: string;
repository?: string;
};

export type CodecovContextDataParams = Omit<CodecovContextData, 'changeContextValue'>;

export const CodecovContext = createContext<CodecovContextData | undefined>(undefined);

export function useCodecovContext() {
Expand Down
23 changes: 0 additions & 23 deletions static/app/components/codecov/repoPicker/repoPicker.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {useCodecovContext} from 'sentry/components/codecov/context/codecovContext';
import {Button} from 'sentry/components/core/button';
import type {SelectOption, SingleSelectProps} from 'sentry/components/core/compactSelect';
import type {SelectOption} from 'sentry/components/core/compactSelect';
import {CompactSelect} from 'sentry/components/core/compactSelect';
import {Flex} from 'sentry/components/core/layout';
import DropdownButton from 'sentry/components/dropdownButton';
Expand Down Expand Up @@ -53,23 +54,16 @@ function MenuFooter({repoAccessLink}: MenuFooterProps) {
);
}

export interface RepoSelectorProps {
onChange: (data: string) => void;
/**
* Repository value
*/
repository: string | null;
/**
* Optional trigger for the assignee selector. If nothing passed in,
* the default trigger will be used
*/
trigger?: (
props: Omit<React.HTMLAttributes<HTMLElement>, 'children'>,
isOpen: boolean
) => React.ReactNode;
}
export function RepoSelector() {
const {repository, changeContextValue} = useCodecovContext();

const handleChange = useCallback(
(selectedOption: SelectOption<string>) => {
changeContextValue({repository: selectedOption.value});
},
[changeContextValue]
);

export function RepoSelector({onChange, trigger, repository}: RepoSelectorProps) {
const options = useMemo((): Array<SelectOption<string>> => {
// TODO: When API is ready, replace placeholder w/ api response
const repoSet = new Set([
Expand All @@ -88,13 +82,6 @@ export function RepoSelector({onChange, trigger, repository}: RepoSelectorProps)
});
}, [repository]);

const handleChange = useCallback<NonNullable<SingleSelectProps<string>['onChange']>>(
newSelected => {
onChange(newSelected.value);
},
[onChange]
);

return (
<CompactSelect
searchable
Expand All @@ -105,31 +92,28 @@ export function RepoSelector({onChange, trigger, repository}: RepoSelectorProps)
menuWidth={'16rem'}
menuBody={<SyncRepoButton />}
menuFooter={<MenuFooter repoAccessLink="placeholder" />}
trigger={
trigger ??
((triggerProps, isOpen) => {
const defaultLabel = options.some(item => item.value === repository)
? repository
: t('Select Repo');

return (
<DropdownButton
isOpen={isOpen}
data-test-id="page-filter-codecov-repository-selector"
{...triggerProps}
>
<TriggerLabelWrap>
<Flex align="center" gap={space(0.75)}>
<IconContainer>
<IconRepository />
</IconContainer>
<TriggerLabel>{defaultLabel}</TriggerLabel>
</Flex>
</TriggerLabelWrap>
</DropdownButton>
);
})
}
trigger={(triggerProps, isOpen) => {
const defaultLabel = options.some(item => item.value === repository)
? repository
: t('Select Repo');

return (
<DropdownButton
isOpen={isOpen}
data-test-id="page-filter-codecov-repository-selector"
{...triggerProps}
>
<TriggerLabelWrap>
<Flex align="center" gap={space(0.75)}>
<IconContainer>
<IconRepository />
</IconContainer>
<TriggerLabel>{defaultLabel}</TriggerLabel>
</Flex>
</TriggerLabelWrap>
</DropdownButton>
);
}}
/>
);
}
Expand Down
4 changes: 2 additions & 2 deletions static/app/views/codecov/tests/tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import {BranchSelector} from 'sentry/components/codecov/branchSelector/branchSelector';
import {DatePicker} from 'sentry/components/codecov/datePicker/datePicker';
import {IntegratedOrgSelector} from 'sentry/components/codecov/integratedOrgSelector/integratedOrgSelector';
import {RepoPicker} from 'sentry/components/codecov/repoPicker/repoPicker';
import {RepoSelector} from 'sentry/components/codecov/repoSelector/repoSelector';
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
import {space} from 'sentry/styles/space';
import {decodeSorts} from 'sentry/utils/queryString';
Expand All @@ -27,7 +27,7 @@ export default function TestsPage() {
<LayoutGap>
<PageFilterBar condensed>
<IntegratedOrgSelector />
<RepoPicker />
<RepoSelector />
<BranchSelector />
<DatePicker />
</PageFilterBar>
Expand Down
Loading