Skip to content
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

feat(insights): Introduce sort by selector for releases #82302

Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions static/app/constants/releases.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum ReleasesSortOption {
Copy link
Member Author

Choose a reason for hiding this comment

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

this enum was just moved from

export enum ReleasesSortOption {
CRASH_FREE_USERS = 'crash_free_users',
CRASH_FREE_SESSIONS = 'crash_free_sessions',
USERS_24_HOURS = 'users_24h',
SESSIONS_24_HOURS = 'sessions_24h',
SESSIONS = 'sessions',
DATE = 'date',
BUILD = 'build',
SEMVER = 'semver',
ADOPTION = 'adoption',
}

CRASH_FREE_USERS = 'crash_free_users',
CRASH_FREE_SESSIONS = 'crash_free_sessions',
USERS_24_HOURS = 'users_24h',
SESSIONS_24_HOURS = 'sessions_24h',
SESSIONS = 'sessions',
DATE = 'date',
BUILD = 'build',
SEMVER = 'semver',
ADOPTION = 'adoption',
Comment on lines +2 to +10
Copy link
Member Author

Choose a reason for hiding this comment

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

@philipphofmann can you confirm that we would like to provide all these sort by options?

Copy link
Member

Choose a reason for hiding this comment

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

I would remove crash-free-users and crash-free-session, as we are filtering releases for performance data, and I don't think that is relevant. I would keep the rest.

}
99 changes: 91 additions & 8 deletions static/app/views/insights/common/components/releaseSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import {useState} from 'react';
import {useEffect, useState} from 'react';
import styled from '@emotion/styled';
import debounce from 'lodash/debounce';

import type {SelectOption} from 'sentry/components/compactSelect';
import {CompactSelect} from 'sentry/components/compactSelect';
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
import {ReleasesSortOption} from 'sentry/constants/releases';
import {IconReleases} from 'sentry/icons/iconReleases';
import {t, tn} from 'sentry/locale';
import {t, tct, tn} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {defined} from 'sentry/utils';
import {browserHistory} from 'sentry/utils/browserHistory';
import {getFormattedDate} from 'sentry/utils/dates';
import {decodeScalar} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import usePageFilters from 'sentry/utils/usePageFilters';
import {
ReleasesSort,
type ReleasesSortByOption,
SORT_BY_OPTIONS,
} from 'sentry/views/insights/common/components/releasesSort';
import {
useReleases,
useReleaseSelection,
Expand All @@ -24,18 +33,24 @@ export const SECONDARY_RELEASE_ALIAS = 'R2';

type Props = {
selectorKey: string;
sortBy: ReleasesSortByOption;
selectorName?: string;
selectorValue?: string;
triggerLabelPrefix?: string;
};

export function ReleaseSelector({selectorKey, selectorValue, triggerLabelPrefix}: Props) {
export function ReleaseSelector({
selectorKey,
selectorValue,
triggerLabelPrefix,
sortBy,
}: Props) {
const [searchTerm, setSearchTerm] = useState<string | undefined>(undefined);
const {data, isLoading} = useReleases(searchTerm);
const {data, isLoading} = useReleases({searchTerm, sortBy});
const {primaryRelease, secondaryRelease} = useReleaseSelection();
const location = useLocation();

const options: SelectOption<string>[] = [];
const options: (SelectOption<string> & {count?: number})[] = [];
if (defined(selectorValue)) {
const index = data?.findIndex(({version}) => version === selectorValue);
const selectedRelease = defined(index) ? data?.[index] : undefined;
Expand All @@ -48,6 +63,7 @@ export function ReleaseSelector({selectorKey, selectorValue, triggerLabelPrefix}

options.push({
value: selectorValue,
count: selectedReleaseSessionCount,
label: selectorValue,
details: (
<LabelDetails
Expand All @@ -57,12 +73,14 @@ export function ReleaseSelector({selectorKey, selectorValue, triggerLabelPrefix}
),
});
}

data
?.filter(({version}) => ![primaryRelease, secondaryRelease].includes(version))
.forEach(release => {
const option = {
value: release.version,
label: release.version,
count: release.count,
details: (
<LabelDetails screenCount={release.count} dateCreated={release.dateCreated} />
),
Expand All @@ -74,6 +92,11 @@ export function ReleaseSelector({selectorKey, selectorValue, triggerLabelPrefix}
? formatVersionAndCenterTruncate(selectorValue, 16)
: selectorValue;

const sortedOptions =
sortBy === 'number_events'
? options.sort((a, b) => (b.count ?? 0) - (a.count ?? 0))
: options;

return (
<StyledCompactSelect
triggerProps={{
Expand All @@ -89,8 +112,10 @@ export function ReleaseSelector({selectorKey, selectorValue, triggerLabelPrefix}
options={[
{
value: '_releases',
label: t('Sorted by date created'),
options,
label: tct('Sorted by [sortBy]', {
sortBy: SORT_BY_OPTIONS[sortBy].label,
}),
options: sortedOptions,
},
]}
onSearch={debounce(val => {
Expand Down Expand Up @@ -134,8 +159,48 @@ function LabelDetails(props: LabelDetailsProps) {
);
}

function getSortRealesesBy(
sort: ReleasesSortByOption,
priscilawebdev marked this conversation as resolved.
Show resolved Hide resolved
environments: string[]
): ReleasesSortByOption {
// Require 1 environment for date adopted
if (sort === ReleasesSortOption.ADOPTION && environments.length !== 1) {
return ReleasesSortOption.DATE;
}

const sortExists = Object.keys(SORT_BY_OPTIONS).includes(sort ?? '');

if (sortExists) {
return sort;
}

priscilawebdev marked this conversation as resolved.
Show resolved Hide resolved
return ReleasesSortOption.DATE;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

you can find the same logic here

export function ReleaseComparisonSelector() {
const {primaryRelease, secondaryRelease} = useReleaseSelection();
const location = useLocation();
const navigate = useNavigate();
const {selection} = usePageFilters();

const sort = decodeScalar(location.query.sortReleasesBy) as ReleasesSortByOption;
const sortReleasesBy = getSortRealesesBy(sort, selection.environments);

useEffect(() => {
if (sort !== sortReleasesBy) {
navigate(
{
...location,
query: {
...location.query,
sortReleasesBy,
},
},
{replace: true}
);
}
}, [location, sortReleasesBy, navigate, sort]);

return (
<StyledPageSelector condensed>
<ReleaseSelector
Expand All @@ -144,13 +209,28 @@ export function ReleaseComparisonSelector() {
selectorName={t('Release 1')}
key="primaryRelease"
triggerLabelPrefix={PRIMARY_RELEASE_ALIAS}
sortBy={sortReleasesBy}
/>
<ReleaseSelector
selectorKey="secondaryRelease"
selectorName={t('Release 2')}
selectorValue={secondaryRelease}
key="secondaryRelease"
triggerLabelPrefix={SECONDARY_RELEASE_ALIAS}
sortBy={sortReleasesBy}
/>
<ReleasesSort
sortBy={sortReleasesBy}
environments={selection.environments}
onChange={value =>
navigate({
...location,
query: {
...location.query,
sortReleasesBy: value,
},
})
}
/>
</StyledPageSelector>
);
Expand All @@ -166,7 +246,10 @@ const StyledPageSelector = styled(PageFilterBar)`
& > * {
min-width: 135px;
&:last-child {
min-width: 135px;
min-width: auto;
> button[aria-haspopup] {
padding-right: ${space(1.5)};
}
}
}
`;
Expand Down
63 changes: 63 additions & 0 deletions static/app/views/insights/common/components/releasesSort.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {Button} from 'sentry/components/button';
import {CompositeSelect} from 'sentry/components/compactSelect/composite';
import {ReleasesSortOption} from 'sentry/constants/releases';
import {IconSort} from 'sentry/icons';
import {t} from 'sentry/locale';

export const SORT_BY_OPTIONS = {
[ReleasesSortOption.SESSIONS_24_HOURS]: {label: t('Active Sessions')},
[ReleasesSortOption.USERS_24_HOURS]: {label: t('Active Users')},
[ReleasesSortOption.ADOPTION]: {label: t('Adoption')},
[ReleasesSortOption.BUILD]: {label: t('Build Number')},
[ReleasesSortOption.CRASH_FREE_SESSIONS]: {label: t('Crash Free Sessions')},
[ReleasesSortOption.CRASH_FREE_USERS]: {label: t('Crash Free Users')},
[ReleasesSortOption.DATE]: {label: t('Date Created')},
['number_events']: {label: t('Number of Events')},
[ReleasesSortOption.SEMVER]: {label: t('Semantic Version')},
[ReleasesSortOption.SESSIONS]: {label: t('Total Sessions')},
};

export type ReleasesSortByOption = keyof typeof SORT_BY_OPTIONS;

interface Props {
environments: string[];
onChange: (sortBy: string) => void;
sortBy: ReleasesSortByOption;
}

export function ReleasesSort({environments, sortBy, onChange}: Props) {
return (
<CompositeSelect
trigger={triggerProps => (
<Button
{...triggerProps}
size="xs"
icon={<IconSort />}
title={t('Sort Releases')}
aria-label={t('Sort Releases')}
/>
)}
>
<CompositeSelect.Region
label={t('Sort By')}
value={sortBy}
onChange={selection => {
onChange(selection.value);
}}
options={Object.entries(SORT_BY_OPTIONS).map(([name, filter]) => {
const disabled =
name === ReleasesSortOption.ADOPTION && environments.length !== 1;

return {
label: filter.label,
value: name,
disabled,
Copy link
Member

Choose a reason for hiding this comment

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

Unless we rename disabled to something that better expressed the intention, it's best to just inline it.

Suggested change
const disabled =
name === ReleasesSortOption.ADOPTION && environments.length !== 1;
return {
label: filter.label,
value: name,
disabled,
return {
label: filter.label,
value: name,
disabled: ReleasesSortOption.ADOPTION && environments.length !== 1,

Copy link
Member Author

Choose a reason for hiding this comment

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

but we are using disabled in two different places, both very close to where it’s being applied, so I don’t think it’s a significant issue

Copy link
Member Author

Choose a reason for hiding this comment

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

in any case, I have updated the code

tooltip: disabled
? t('Select one environment to use this sort option.')
: undefined,
};
})}
/>
</CompositeSelect>
);
}
14 changes: 12 additions & 2 deletions static/app/views/insights/common/queries/useReleases.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chunk from 'lodash/chunk';

import {ReleasesSortOption} from 'sentry/constants/releases';
import type {NewQuery} from 'sentry/types/organization';
import type {Release} from 'sentry/types/release';
import type {TableData} from 'sentry/utils/discover/discoverQuery';
Expand All @@ -13,14 +14,22 @@ import useApi from 'sentry/utils/useApi';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
import type {ReleasesSortByOption} from 'sentry/views/insights/common/components/releasesSort';

export function useReleases(searchTerm?: string) {
export function useReleases({
searchTerm,
sortBy,
}: {
searchTerm?: string;
sortBy?: ReleasesSortByOption;
} = {}) {
JonasBa marked this conversation as resolved.
Show resolved Hide resolved
const organization = useOrganization();
const location = useLocation();
const {selection, isReady} = usePageFilters();
const {environments, projects} = selection;
const api = useApi();

const activeSort = sortBy ?? ReleasesSortOption.DATE;
const releaseResults = useApiQuery<Release[]>(
[
`/organizations/${organization.slug}/releases/`,
Expand All @@ -30,7 +39,8 @@ export function useReleases(searchTerm?: string) {
per_page: 50,
environment: environments,
query: searchTerm,
sort: 'date',
sort: activeSort === 'number_events' ? 'date' : activeSort,
flatten: activeSort === 'date' || activeSort === 'number_events' ? 0 : 1,
priscilawebdev marked this conversation as resolved.
Show resolved Hide resolved
},
},
],
Expand Down
3 changes: 2 additions & 1 deletion static/app/views/releases/list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
import {getRelativeSummary} from 'sentry/components/timeRangeSelector/utils';
import {DEFAULT_STATS_PERIOD} from 'sentry/constants';
import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
import {ReleasesSortOption} from 'sentry/constants/releases';
import {releaseHealth} from 'sentry/data/platformCategories';
import {IconSearch} from 'sentry/icons';
import {t} from 'sentry/locale';
Expand Down Expand Up @@ -56,7 +57,7 @@ import ReleasesAdoptionChart from './releasesAdoptionChart';
import ReleasesDisplayOptions, {ReleasesDisplayOption} from './releasesDisplayOptions';
import ReleasesPromo from './releasesPromo';
import ReleasesRequest from './releasesRequest';
import ReleasesSortOptions, {ReleasesSortOption} from './releasesSortOptions';
import ReleasesSortOptions from './releasesSortOptions';
import ReleasesStatusOptions, {ReleasesStatusOption} from './releasesStatusOptions';

type RouteParams = {
Expand Down
13 changes: 1 addition & 12 deletions static/app/views/releases/list/releasesSortOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import styled from '@emotion/styled';

import {ReleasesSortOption} from 'sentry/constants/releases';
import {t} from 'sentry/locale';

import {ReleasesDisplayOption} from './releasesDisplayOptions';
import ReleasesDropdown from './releasesDropdown';

export enum ReleasesSortOption {
CRASH_FREE_USERS = 'crash_free_users',
CRASH_FREE_SESSIONS = 'crash_free_sessions',
USERS_24_HOURS = 'users_24h',
SESSIONS_24_HOURS = 'sessions_24h',
SESSIONS = 'sessions',
DATE = 'date',
BUILD = 'build',
SEMVER = 'semver',
ADOPTION = 'adoption',
}

type Props = {
environments: string[];
onSelect: (key: string) => void;
Expand Down
Loading