Skip to content

feat: (Codecov) add RepoPicker component #89910

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
9 changes: 9 additions & 0 deletions static/app/actionCreators/pageFilters.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ describe('PageFilters ActionCreators', function () {
expect.objectContaining({
environments: [],
projects: [],
repository: null,
}),
new Set(['projects']),
false
Expand Down Expand Up @@ -275,6 +276,7 @@ describe('PageFilters ActionCreators', function () {
organization,
queryParams: {
project: '1',
repository: 'repo-from-query',
},
memberProjects: projects,
nonMemberProjects: [],
Expand All @@ -292,6 +294,7 @@ describe('PageFilters ActionCreators', function () {
},
projects: [1],
environments: [],
repository: 'repo-from-query',
},
new Set(),
true
Expand All @@ -301,6 +304,7 @@ describe('PageFilters ActionCreators', function () {
query: {
environment: [],
project: ['1'],
repository: 'repo-from-query',
},
})
);
Expand All @@ -327,6 +331,7 @@ describe('PageFilters ActionCreators', function () {
},
projects: [-1],
environments: [],
repository: null,
},
new Set(),
true
Expand Down Expand Up @@ -355,6 +360,7 @@ describe('PageFilters ActionCreators', function () {
},
projects: [1],
environments: [],
repository: null,
},
new Set(),
true
Expand All @@ -373,6 +379,7 @@ describe('PageFilters ActionCreators', function () {
end: null,
period: '14d',
utc: null,
repository: null,
},
pinnedFilters: new Set(),
});
Expand Down Expand Up @@ -405,6 +412,7 @@ describe('PageFilters ActionCreators', function () {
end: null,
period: '7d',
utc: null,
repository: null,
},
pinnedFilters: new Set(['environments', 'datetime', 'projects']),
});
Expand Down Expand Up @@ -696,6 +704,7 @@ describe('PageFilters ActionCreators', function () {
end: null,
period: '14d',
utc: null,
repository: null,
},
pinnedFilters: new Set(['projects', 'environments', 'datetime']),
});
Expand Down
36 changes: 31 additions & 5 deletions static/app/actionCreators/pageFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type PageFiltersUpdate = {
environment?: string[] | null;
period?: string | null;
project?: number[] | null;
repository?: string | null;
start?: DateString;
utc?: boolean | null;
};
Expand Down Expand Up @@ -206,6 +207,8 @@ export function initializeUrlState({
const hasDatetimeInUrl = Object.keys(pick(queryParams, DATE_TIME_KEYS)).length > 0;
const hasProjectOrEnvironmentInUrl =
Object.keys(pick(queryParams, [URL_PARAM.PROJECT, URL_PARAM.ENVIRONMENT])).length > 0;
const hasRepositoryInUrl =
Object.keys(pick(queryParams, URL_PARAM.REPOSITORY)).length > 0;

// We should only check and update the desync state if the site has just been loaded
// (not counting route changes). To check this, we can use the `isReady` state: if it's
Expand Down Expand Up @@ -254,6 +257,12 @@ export function initializeUrlState({
}
}

// We want to update the pageFilter's object with a repository if it is in the URL
// or in local storage, in that order.
if (hasRepositoryInUrl) {
pageFilters.repository = parsed.repository ?? null;
}

const storedPageFilters = skipLoadLastUsed
? null
: getPageFilterStorage(orgSlug, storageNamespace);
Expand Down Expand Up @@ -293,9 +302,13 @@ export function initializeUrlState({
pageFilters.datetime = getDatetimeFromState(storedState);
shouldUsePinnedDatetime = true;
}

if (!hasRepositoryInUrl && pinnedFilters.has('repository')) {
pageFilters.repository = storedState.repository ?? null;
}
}

const {projects, environments: environment, datetime} = pageFilters;
const {projects, environments: environment, datetime, repository} = pageFilters;

let newProject: number[] | null = null;
let project = projects;
Expand Down Expand Up @@ -358,9 +371,8 @@ export function initializeUrlState({
}
}
}

const pinnedFilters = organization.features.includes('new-page-filter')
? new Set<PinnedPageFilter>(['projects', 'environments', 'datetime'])
? new Set<PinnedPageFilter>(['projects', 'environments', 'datetime', 'repository'])
: (storedPageFilters?.pinnedFilters ?? new Set());

PageFiltersStore.onInitializeUrlState(pageFilters, pinnedFilters, shouldPersist);
Expand Down Expand Up @@ -392,7 +404,7 @@ export function initializeUrlState({
};

if (!skipInitializeUrlParams) {
updateParams({project, environment, ...newDatetime}, router, {
updateParams({project, environment, ...newDatetime, repository}, router, {
replace: true,
keepCursor: true,
});
Expand Down Expand Up @@ -469,6 +481,19 @@ export function updateDateTime(
persistPageFilters('datetime', options);
}

/**
* Updates store and global repository selection URL param if `router` is supplied
*
* @param {Object} repository Object with repository key
* @param {Object} [router] Router object
* @param {Object} [options] Options object
*/
export function updateRepository(repository: string, router?: Router, options?: Options) {
PageFiltersStore.updateRepository(repository);
updateParams({repository}, router, options);
persistPageFilters('repository', options);
}

/**
* Pins a particular filter so that it is read out of local storage
*/
Expand Down Expand Up @@ -669,7 +694,7 @@ function getNewQueryParams(
const extraParams = omit(cleanCurrentQuery, omittedParameters);

// Override parameters
const {project, environment, start, end, utc} = {
const {project, environment, start, end, utc, repository} = {
...currentQueryState,
...obj,
};
Expand All @@ -684,6 +709,7 @@ function getNewQueryParams(
end: statsPeriod ? null : end instanceof Date ? getUtcDateString(end) : end,
utc: utc ? 'true' : null,
statsPeriod,
repository,
...extraParams,
};

Expand Down
34 changes: 34 additions & 0 deletions static/app/components/codecov/repoPicker/repoPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {updateRepository} from 'sentry/actionCreators/pageFilters';
import type {RepoSelectorProps} from 'sentry/components/codecov/repoPicker/repoSelector';
import {RepoSelector} from 'sentry/components/codecov/repoPicker/repoSelector';
import usePageFilters from 'sentry/utils/usePageFilters';
import useRouter from 'sentry/utils/useRouter';

export interface RepoPickerProps
extends Partial<
Partial<Omit<RepoSelectorProps, 'relative' | 'menuBody' | 'repository'>>
> {}

export function RepoPicker({
menuWidth,
triggerProps = {},
...selectProps
}: RepoPickerProps) {
const router = useRouter();
const {selection} = usePageFilters();
const repository = selection?.repository ?? null;

return (
<RepoSelector
{...selectProps}
repository={repository}
onChange={newRepository => {
updateRepository(newRepository, router, {
save: true,
});
}}
menuWidth={menuWidth ? '22em' : undefined}
triggerProps={triggerProps}
/>
);
}
114 changes: 114 additions & 0 deletions static/app/components/codecov/repoPicker/repoSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {useCallback, useMemo} from 'react';
import styled from '@emotion/styled';

import {
mapIndividualRepository,
mapRepositoryList,
} from 'sentry/components/codecov/utils';
import type {SelectOption, SingleSelectProps} from 'sentry/components/core/compactSelect';
import {CompactSelect} from 'sentry/components/core/compactSelect';
import DropdownButton from 'sentry/components/dropdownButton';
import {t} from 'sentry/locale';

const CODECOV_PLACEHOLDER_REPOS = ['test repo 1', 'test-repo-2', 'Test Repo 3'];

export interface RepoSelectorProps
extends Omit<
SingleSelectProps<string>,
'multiple' | 'hideOptions' | 'onChange' | 'onClose' | 'options' | 'value'
> {
/**
* Relative date value
*/
repository: string | null;
/**
* Custom width value for compact select
*/
menuWidth?: string;
onChange?: (data: string) => void;
}

export function RepoSelector({
menuWidth,
onChange,
trigger,
repository,
...selectProps
}: RepoSelectorProps) {
const options = useMemo((): Array<SelectOption<string>> => {
const selectedRepository = mapIndividualRepository(repository);
// TODO: When API is ready, fetch the options from API
const repositoryList = mapRepositoryList(CODECOV_PLACEHOLDER_REPOS);

const repositoriesMap = {
...selectedRepository,
...repositoryList,
};

// TODO: ensure list is sorted when API is implemented
const repositoriesList = Object.entries(repositoriesMap);

return repositoriesList.map(([value, label]): SelectOption<string> => {
return {
value,
label: <OptionLabel>{label}</OptionLabel>,
textValue: typeof label === 'string' ? label : value,
};
});
}, [repository]);

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

return (
<CompactSelect
{...selectProps}
options={options}
value={repository ?? ''}
onChange={handleChange}
menuWidth={menuWidth ?? '16rem'}
trigger={
trigger ??
((triggerProps, isOpen) => {
const defaultLabel = options.some(item => item.value === repository)
? repository?.toUpperCase()
: t('Select Repo');

return (
<DropdownButton
isOpen={isOpen}
size={selectProps.size}
data-test-id="page-filter-codecov-repository-selector"
{...triggerProps}
{...selectProps.triggerProps}
>
<TriggerLabelWrap>
<TriggerLabel>{selectProps.triggerLabel ?? defaultLabel}</TriggerLabel>
</TriggerLabelWrap>
</DropdownButton>
);
})
}
/>
);
}

const TriggerLabelWrap = styled('span')`
position: relative;
min-width: 0;
`;

const TriggerLabel = styled('span')`
${p => p.theme.overflowEllipsis}
width: auto;
`;

const OptionLabel = styled('span')`
div {
margin: 0;
}
`;
26 changes: 26 additions & 0 deletions static/app/components/codecov/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,29 @@ export function isValidCodecovRelativePeriod(period: string | null): boolean {
}

// Date Picker Utils End

// Repo Picker Utils Start
/**
* Creates a mapping of 'A:A' for the repository if it is not null
*/
export function mapIndividualRepository(
repository: string | null
): Record<string, string> {
if (repository === null) {
return {};
}

return {[repository]: repository};
}

/**
* Creates a mapping of 'A:A' for every repository in the repository list if it is not null
*/
export function mapRepositoryList(repositories: string[] | null): Record<string, string> {
if (!repositories || (repositories && repositories.length === 0)) {
return {};
}

return Object.fromEntries(repositories.map(repository => [repository, repository]));
}
// Repo Picker Utils End
Loading
Loading