diff --git a/static/app/components/codecov/datePicker/datePicker.spec.tsx b/static/app/components/codecov/datePicker/datePicker.spec.tsx index 20f173cba17b37..16154d66b455b9 100644 --- a/static/app/components/codecov/datePicker/datePicker.spec.tsx +++ b/static/app/components/codecov/datePicker/datePicker.spec.tsx @@ -1,152 +1,53 @@ -import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; -import {initializeUrlState} from 'sentry/actionCreators/pageFilters'; +import CodecovQueryParamsProvider from 'sentry/components/codecov/container/codecovParamsProvider'; import {DatePicker} from 'sentry/components/codecov/datePicker/datePicker'; -import OrganizationStore from 'sentry/stores/organizationStore'; -import PageFiltersStore from 'sentry/stores/pageFiltersStore'; - -const {organization, router} = initializeOrg({ - router: { - location: { - query: {}, - pathname: '/codecov/tests', - }, - params: {}, - }, -}); describe('DatePicker', function () { - beforeEach(() => { - PageFiltersStore.init(); - OrganizationStore.init(); - - OrganizationStore.onUpdate(organization, {replace: true}); - PageFiltersStore.onInitializeUrlState( + it('can change period', async function () { + const {router} = render( + + + , { - projects: [], - environments: [], - datetime: { - period: '7d', - start: null, - end: null, - utc: false, + initialRouterConfig: { + location: { + pathname: '/codecov/tests', + query: {codecovPeriod: '7d'}, + }, }, - }, - new Set(['datetime']) + } ); - }); - - it('can change period', async function () { - render(, { - router, - deprecatedRouterMocks: true, - }); await userEvent.click(screen.getByRole('button', {name: '7D', expanded: false})); await userEvent.click(screen.getByRole('option', {name: 'Last 30 days'})); + expect(router.location.search).toBe('?codecovPeriod=30d'); + expect( screen.getByRole('button', {name: '30D', expanded: false}) ).toBeInTheDocument(); - expect(router.push).toHaveBeenCalledWith( - expect.objectContaining({query: {statsPeriod: '30d'}}) - ); - expect(PageFiltersStore.getState()).toEqual({ - isReady: true, - shouldPersist: true, - desyncedFilters: new Set(), - pinnedFilters: new Set(['datetime']), - selection: { - datetime: { - period: '30d', - end: null, - start: null, - utc: false, - }, - environments: [], - projects: [], - }, - }); }); - it('adjusts period if invalid', async function () { - PageFiltersStore.reset(); - PageFiltersStore.onInitializeUrlState( + it('displays invalid button for invalid values', async function () { + render( + + + , { - projects: [], - environments: [], - datetime: { - period: '123d', - start: null, - end: null, - utc: false, + initialRouterConfig: { + location: { + pathname: '/codecov/tests', + query: {codecovPeriod: '123Dd12'}, + }, }, - }, - new Set(['datetime']) + } ); - render(, { - router, - deprecatedRouterMocks: true, + const button = await screen.findByRole('button', { + name: 'Invalid Period', + expanded: false, }); - - // Confirm selection changed to default Codecov period - const button = await screen.findByRole('button', {name: '24H', expanded: false}); expect(button).toBeInTheDocument(); - expect(router.push).toHaveBeenCalledWith( - expect.objectContaining({query: {statsPeriod: '24h'}}) - ); - expect(PageFiltersStore.getState()).toEqual({ - isReady: true, - shouldPersist: true, - desyncedFilters: new Set(), - pinnedFilters: new Set(['datetime']), - selection: { - datetime: { - period: '24h', - end: null, - start: null, - utc: false, - }, - environments: [], - projects: [], - }, - }); - }); - - it('displays a desynced state message', async function () { - const {organization: desyncOrganization, router: desyncRouter} = initializeOrg({ - router: { - location: { - query: {statsPeriod: '7d'}, - pathname: '/codecov/test', - }, - params: {}, - }, - }); - - PageFiltersStore.reset(); - initializeUrlState({ - memberProjects: [], - nonMemberProjects: [], - organization: desyncOrganization, - queryParams: {statsPeriod: '30d'}, - router: desyncRouter, - shouldEnforceSingleProject: false, - }); - - render(, { - router: desyncRouter, - organization: desyncOrganization, - deprecatedRouterMocks: true, - }); - - await userEvent.click(screen.getByRole('button', {name: '30D', expanded: false})); - expect(screen.getByText('Filters Updated')).toBeInTheDocument(); - expect( - screen.getByRole('button', {name: 'Restore Previous Values'}) - ).toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Got It'})).toBeInTheDocument(); }); }); diff --git a/static/app/components/codecov/datePicker/datePicker.tsx b/static/app/components/codecov/datePicker/datePicker.tsx index 7cb3c716302caa..475f61eb9d816c 100644 --- a/static/app/components/codecov/datePicker/datePicker.tsx +++ b/static/app/components/codecov/datePicker/datePicker.tsx @@ -1,64 +1,23 @@ -import {useEffect} from 'react'; +import {useSearchParams} from 'react-router-dom'; -import {updateDateTime} from 'sentry/actionCreators/pageFilters'; -import type {DateSelectorProps} from 'sentry/components/codecov/datePicker/dateSelector'; +import {useCodecovContext} from 'sentry/components/codecov/context/codecovContext'; import {DateSelector} from 'sentry/components/codecov/datePicker/dateSelector'; -import {isValidCodecovRelativePeriod} from 'sentry/components/codecov/utils'; -import {DesyncedFilterMessage} from 'sentry/components/organizations/pageFilters/desyncedFilter'; -import {t} from 'sentry/locale'; -import usePageFilters from 'sentry/utils/usePageFilters'; -import useRouter from 'sentry/utils/useRouter'; -const CODECOV_DEFAULT_RELATIVE_PERIOD = '24h'; -export const CODECOV_DEFAULT_RELATIVE_PERIODS = { - '24h': t('Last 24 hours'), - '7d': t('Last 7 days'), - '30d': t('Last 30 days'), -}; - -interface DatePickerProps - extends Partial>> {} - -export function DatePicker({ - onChange, - menuTitle, - menuWidth, - triggerProps = {}, - ...selectProps -}: DatePickerProps) { - const router = useRouter(); - const {selection, desyncedFilters} = usePageFilters(); - const desynced = desyncedFilters.has('datetime'); - const period = selection.datetime?.period; - - // Adjusts to valid Codecov relative period since Codecov only accepts a subset of dates other components accept, defined in CODECOV_DEFAULT_RELATIVE_PERIODS - useEffect(() => { - if (!isValidCodecovRelativePeriod(period)) { - const newTimePeriod = {period: CODECOV_DEFAULT_RELATIVE_PERIOD}; - updateDateTime(newTimePeriod, router, { - save: true, - }); - } - }, [period, router]); +export function DatePicker() { + const {codecovPeriod} = useCodecovContext(); + const [searchParams, setSearchParams] = useSearchParams(); return ( { - const {relative} = timePeriodUpdate; - const newTimePeriod = {period: relative}; - - onChange?.(timePeriodUpdate); - updateDateTime(newTimePeriod, router, { - save: true, - }); + relativeDate={codecovPeriod} + onChange={newCodecovPeriod => { + const currentParams = Object.fromEntries(searchParams.entries()); + const updatedParams = { + ...currentParams, + codecovPeriod: newCodecovPeriod, + }; + setSearchParams(updatedParams); }} - menuTitle={menuTitle ?? t('Filter Time Range')} - menuWidth={(menuWidth ?? desynced) ? '22em' : undefined} - menuBody={desynced && } - triggerProps={triggerProps} /> ); } diff --git a/static/app/components/codecov/datePicker/dateSelector.spec.tsx b/static/app/components/codecov/datePicker/dateSelector.spec.tsx index f2a0be2fa9b87a..55b8e4b853bf20 100644 --- a/static/app/components/codecov/datePicker/dateSelector.spec.tsx +++ b/static/app/components/codecov/datePicker/dateSelector.spec.tsx @@ -4,12 +4,12 @@ import {DateSelector} from 'sentry/components/codecov/datePicker/dateSelector'; describe('DateSelector', function () { it('renders when given relative period', async function () { - render(); + render( {}} />); expect(await screen.findByRole('button', {name: '7D'})).toBeInTheDocument(); }); it('renders when given an invalid relative period', async function () { - render(); + render( {}} />); expect( await screen.findByRole('button', {name: 'Invalid Period'}) ).toBeInTheDocument(); diff --git a/static/app/components/codecov/datePicker/dateSelector.tsx b/static/app/components/codecov/datePicker/dateSelector.tsx index 87fa5def7c4f88..b0325c5b45eb5f 100644 --- a/static/app/components/codecov/datePicker/dateSelector.tsx +++ b/static/app/components/codecov/datePicker/dateSelector.tsx @@ -1,136 +1,87 @@ -import {useCallback} from 'react'; +import {useCallback, useMemo} from 'react'; import styled from '@emotion/styled'; import type {SelectOption, SingleSelectProps} from 'sentry/components/core/compactSelect'; import {CompactSelect} from 'sentry/components/core/compactSelect'; -import type {Item} from 'sentry/components/dropdownAutoComplete/types'; import DropdownButton from 'sentry/components/dropdownButton'; -import HookOrDefault from 'sentry/components/hookOrDefault'; -import {DesyncedFilterIndicator} from 'sentry/components/organizations/pageFilters/desyncedFilter'; -import SelectorItems from 'sentry/components/timeRangeSelector/selectorItems'; -import { - getArbitraryRelativePeriod, - getSortedRelativePeriods, -} from 'sentry/components/timeRangeSelector/utils'; +import {getArbitraryRelativePeriod} from 'sentry/components/timeRangeSelector/utils'; import {t} from 'sentry/locale'; -import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours'; -import {CODECOV_DEFAULT_RELATIVE_PERIODS} from './datePicker'; - -const SelectorItemsHook = HookOrDefault({ - hookName: 'component:header-selector-items', - defaultComponent: SelectorItems, -}); - -type ChangeData = { - relative: string | null; +export const CODECOV_DEFAULT_RELATIVE_PERIODS = { + '24h': t('Last 24 hours'), + '7d': t('Last 7 days'), + '30d': t('Last 30 days'), }; -export interface DateSelectorProps - extends Omit< - SingleSelectProps, - 'disableSearchFilter' | 'onChange' | 'onClose' | 'options' | 'value' - > { +export interface DateSelectorProps { + onChange: (data: string) => void; /** - * Whether the current value is out of sync with the stored persistent value. + * Relative date value */ - desynced?: boolean; + relativeDate?: string | null; /** - * Custom width value for relative compact select + * Optional trigger for the assignee selector. If nothing passed in, + * the default trigger will be used */ - menuWidth?: string; - onChange?: (data: ChangeData) => void; - onClose?: () => void; - /** - * Relative date value - */ - relative?: string | null; + trigger?: ( + props: Omit, 'children'>, + isOpen: boolean + ) => React.ReactNode; } -export function DateSelector({ - relative, - onChange, - onClose, - trigger, - menuWidth, - desynced, - ...selectProps -}: DateSelectorProps) { - const getOptions = useCallback((items: Item[]): Array> => { - return items.map((item: Item): SelectOption => { - return { - value: item.value, - label: {item.label}, - textValue: item.searchKey, - }; - }); - }, []); - +export function DateSelector({relativeDate, onChange, trigger}: DateSelectorProps) { const handleChange = useCallback['onChange']>>( - option => { - onChange?.({relative: option.value}); + newSelectedPeriod => { + onChange(newSelectedPeriod.value); }, [onChange] ); - // Currently selected relative period - const arbitraryRelativePeriods = getArbitraryRelativePeriod(relative); - // Periods from default relative periods object - const restrictedDefaultPeriods = Object.fromEntries( - Object.entries(CODECOV_DEFAULT_RELATIVE_PERIODS).filter(([period]) => - parsePeriodToHours(period) - ) - ); - const defaultRelativePeriods = { - ...restrictedDefaultPeriods, - ...arbitraryRelativePeriods, - }; + const options = useMemo((): Array> => { + const currentAndDefaultCodecovPeriods = { + ...getArbitraryRelativePeriod(relativeDate), + ...CODECOV_DEFAULT_RELATIVE_PERIODS, + }; + + return Object.entries(currentAndDefaultCodecovPeriods).map( + ([key, value]): SelectOption => { + return { + value: key, + label: {value}, + textValue: value, + }; + } + ); + }, [relativeDate]); return ( - handleChange({value})} - > - {items => ( - { - onClose?.(); - }} - trigger={ - trigger ?? - ((triggerProps, isOpen) => { - const defaultLabel = items.some(item => item.value === relative) - ? relative?.toUpperCase() - : t('Invalid Period'); + { + const defaultLabel = options.some(item => item.value === relativeDate) + ? relativeDate?.toUpperCase() + : t('Invalid Period'); - return ( - - - - {selectProps.triggerLabel ?? defaultLabel} - - {desynced && } - - - ); - }) - } - /> - )} - + return ( + + + {defaultLabel} + + + ); + }) + } + /> ); } diff --git a/static/app/components/codecov/utils.spec.tsx b/static/app/components/codecov/utils.spec.tsx deleted file mode 100644 index 187470d6f4bf1e..00000000000000 --- a/static/app/components/codecov/utils.spec.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import {isValidCodecovRelativePeriod} from 'sentry/components/codecov/utils'; - -describe('isValidCodecovRelativePeriod', function () { - it('returns false for null relative periods', function () { - const period = null; - expect(isValidCodecovRelativePeriod(period)).toBe(false); - }); - it('returns false for periods not belonging to the Codecov default relative periods', function () { - const period = '123d'; - expect(isValidCodecovRelativePeriod(period)).toBe(false); - }); - it('returns true for a valid relative period', function () { - const period = '7d'; - expect(isValidCodecovRelativePeriod(period)).toBe(true); - }); -}); diff --git a/static/app/components/codecov/utils.tsx b/static/app/components/codecov/utils.tsx deleted file mode 100644 index 9c82455c27c47d..00000000000000 --- a/static/app/components/codecov/utils.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import {CODECOV_DEFAULT_RELATIVE_PERIODS} from './datePicker/datePicker'; - -// Date Picker Utils Start - -/** - * Determines if a period is valid for a Codecov DatePicker component. A period is invalid if - * it is null or if it doesn't belong to the list of Codecov default relative periods. - */ -export function isValidCodecovRelativePeriod(period: string | null): boolean { - if (period === null) { - return false; - } - - if (!Object.hasOwn(CODECOV_DEFAULT_RELATIVE_PERIODS, period)) { - return false; - } - - return true; -} - -// Date Picker Utils End diff --git a/static/app/views/codecov/tests/tests.tsx b/static/app/views/codecov/tests/tests.tsx index a32b07038ae1ab..e8572905e79b83 100644 --- a/static/app/views/codecov/tests/tests.tsx +++ b/static/app/views/codecov/tests/tests.tsx @@ -1,10 +1,9 @@ import styled from '@emotion/styled'; -import CodecovProvider from 'sentry/components/codecov/container/codecovParamsProvider'; +import CodecovQueryParamsProvider from 'sentry/components/codecov/container/codecovParamsProvider'; import {DatePicker} from 'sentry/components/codecov/datePicker/datePicker'; import {RepoPicker} from 'sentry/components/codecov/repoPicker/repoPicker'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; -import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; import {space} from 'sentry/styles/space'; import {decodeSorts} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; @@ -15,13 +14,6 @@ import TestAnalyticsTable, { isAValidSort, } from 'sentry/views/codecov/tests/testAnalyticsTable/testAnalyticsTable'; -const DEFAULT_CODECOV_DATETIME_SELECTION = { - start: null, - end: null, - utc: false, - period: '24h', -}; - // TODO: Sorting will only work once this is connected to the API const fakeApiResponse = { data: [ @@ -66,18 +58,12 @@ export default function TestsPage() { return (

Test Analytics

- + - - - - - + {/* TODO: Conditionally show these if the branch we're in is the main branch */}