Skip to content
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
4 changes: 4 additions & 0 deletions static/app/components/events/eventDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export const EventDrawerBody = styled(DrawerBody)`
overscroll-behavior: contain;
/* Move the scrollbar to the left edge */
scroll-margin: 0 ${space(2)};
display: flex;
gap: ${space(2)};
flex-direction: column;
height: fit-content; /* makes drawer resize work better with flex */
direction: rtl;
* {
direction: ltr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import {Fragment, useState} from 'react';
import styled from '@emotion/styled';

import AnalyticsArea from 'sentry/components/analyticsArea';
import {Flex} from 'sentry/components/container/flex';
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
import {Checkbox} from 'sentry/components/core/checkbox';
import {EventDrawerBody, EventNavigator} from 'sentry/components/events/eventDrawer';
import FeatureFlagSort from 'sentry/components/events/featureFlags/featureFlagSort';
import {OrderBy, SortBy} from 'sentry/components/events/featureFlags/utils';
import {IconSentry} from 'sentry/icons/iconSentry';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Group} from 'sentry/types/group';
Expand Down Expand Up @@ -56,14 +58,14 @@ export default function Flags({group, organization, setTab}: Props) {

const sortByOptions = enableSuspectFlags
? [
{
label: t('Suspiciousness'),
value: SortBy.SUSPICION,
},
{
label: t('Alphabetical'),
value: SortBy.ALPHABETICAL,
},
{
label: t('Suspiciousness'),
value: SortBy.SUSPICION,
},
]
: [
{
Expand All @@ -73,10 +75,6 @@ export default function Flags({group, organization, setTab}: Props) {
];
const orderByOptions = enableSuspectFlags
? [
{
label: t('High to Low'),
value: OrderBy.HIGH_TO_LOW,
},
{
label: t('A-Z'),
value: OrderBy.A_TO_Z,
Expand All @@ -85,6 +83,10 @@ export default function Flags({group, organization, setTab}: Props) {
label: t('Z-A'),
value: OrderBy.Z_TO_A,
},
{
label: t('High to Low'),
value: OrderBy.HIGH_TO_LOW,
},
]
: [
{
Expand Down Expand Up @@ -145,26 +147,27 @@ export default function Flags({group, organization, setTab}: Props) {
</ButtonBar>
)}
</EventNavigator>
{!tagKey && showSuspectSandboxUI && (
<EventDrawerBody>
<Label>
{t('Debug')}{' '}
<Checkbox
checked={debugSuspectScores}
onChange={() => {
setDebugSuspectScores(debugSuspectScores ? '0' : '1');
}}
/>
</Label>
</EventDrawerBody>
)}
<EventDrawerBody>
{tagKey ? (
<AnalyticsArea name="feature_flag_details">
<FlagDetailsDrawerContent />
</AnalyticsArea>
) : (
<AnalyticsArea name="feature_flag_distributions">
{showSuspectSandboxUI && (
<Flex>
<Label>
<IconSentry size="xs" />
{t('Debug')}
<Checkbox
checked={debugSuspectScores}
onChange={() => {
setDebugSuspectScores(debugSuspectScores ? '0' : '1');
}}
/>
</Label>
</Flex>
)}
<FlagDrawerContent
debugSuspectScores={debugSuspectScores}
environments={environments}
Expand Down
164 changes: 164 additions & 0 deletions static/app/views/issueDetails/groupDistributions/suspectTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import styled from '@emotion/styled';
import Color from 'color';

import {Flex} from 'sentry/components/container/flex';
import {Alert} from 'sentry/components/core/alert';
import {NumberInput} from 'sentry/components/core/input/numberInput';
import {Tooltip} from 'sentry/components/core/tooltip';
import {OrderBy, SortBy} from 'sentry/components/events/featureFlags/utils';
import {IconSentry} from 'sentry/icons/iconSentry';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Group} from 'sentry/types/group';
import toRoundedPercent from 'sentry/utils/number/toRoundedPercent';
import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
import useGroupFlagDrawerData from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFlagDrawerData';
import {TagBar} from 'sentry/views/issueDetails/groupTags/tagDistribution';

interface Props {
debugSuspectScores: boolean;
environments: string[];
group: Group;
}

const SUSPECT_SCORE_LOCAL_STATE_KEY = 'flag-drawer-suspicion-score-threshold';
const SUSPECT_SCORE_THRESHOLD = 7;

export default function SuspectTable({debugSuspectScores, environments, group}: Props) {
const [threshold, setThreshold] = useLocalStorageState(
SUSPECT_SCORE_LOCAL_STATE_KEY,
SUSPECT_SCORE_THRESHOLD
);

const {displayFlags, isPending} = useGroupFlagDrawerData({
environments,
group,
orderBy: OrderBy.HIGH_TO_LOW,
search: '',
sortBy: SortBy.SUSPICION,
});

const debugThresholdInput = debugSuspectScores ? (
<Flex gap={space(0.5)} align="center">
<IconSentry size="xs" />
Threshold:
<NumberInput value={threshold} onChange={setThreshold} size="xs" />
</Flex>
) : null;

if (isPending) {
return (
<Alert type="muted">
<TagHeader>
{t('Suspect')}
{debugThresholdInput}
</TagHeader>
{t('Loading...')}
</Alert>
);
}

const susFlags = displayFlags.filter(flag => (flag.suspect.score ?? 0) > threshold);

if (!susFlags.length) {
return (
<Alert type="muted">
<TagHeader>
{t('Suspect')}
{debugThresholdInput}
</TagHeader>
{t('Nothing suspicious')}
</Alert>
);
}

return (
<Alert type="warning">
<TagHeader>
{t('Suspect')}
{debugThresholdInput}
</TagHeader>

<TagValueGrid>
{susFlags.map(flag => {
const topValue = flag.topValues[0];

const pct =
topValue?.value === 'true'
? (flag.suspect.baselinePercent ?? 0)
: 100 - (flag.suspect.baselinePercent ?? 0);
const projPercentage = Math.round(pct * 100);
const displayProjPercent =
projPercentage < 1 ? '<1%' : `${projPercentage.toFixed(0)}%`;

return (
<TagValueRow key={flag.key}>
{/* TODO: why is flag.name transformed to TitleCase? */}
<Tooltip title={flag.key} showOnlyOnOverflow>
<Name>{flag.key}</Name>
</Tooltip>
<TagBar percentage={((topValue?.count ?? 0) / flag.totalValues) * 100} />
<RightAligned>
{toRoundedPercent((topValue?.count ?? 0) / flag.totalValues)}
</RightAligned>
<span>{topValue?.value}</span>
<Subtext>vs</Subtext>
<RightAligned>
<Subtext>{t('%s in project', displayProjPercent)}</Subtext>
</RightAligned>
</TagValueRow>
);
})}
</TagValueGrid>
</Alert>
);
}

const TagHeader = styled('h5')`
display: flex;
justify-content: space-between;
align-items: center;

margin-bottom: ${space(0.5)};
font-size: ${p => p.theme.fontSizeMedium};
font-weight: ${p => p.theme.fontWeightBold};
`;

const progressBarWidth = '45px'; // Prevent percentages from overflowing
const TagValueGrid = styled('ul')`
display: grid;
grid-template-columns: 4fr ${progressBarWidth} auto auto max-content auto;

margin: 0;
padding: 0;
list-style: none;
`;

const TagValueRow = styled('li')`
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
grid-column-gap: ${space(1)};
align-items: center;
padding: ${space(0.25)} ${space(0.75)};
border-radius: ${p => p.theme.borderRadius};
color: ${p => p.theme.textColor};
font-variant-numeric: tabular-nums;

&:nth-child(2n) {
background-color: ${p => Color(p.theme.gray300).alpha(0.1).toString()};
}
`;

const Name = styled('div')`
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
`;

const Subtext = styled('span')`
color: ${p => p.theme.subText};
`;
const RightAligned = styled('span')`
text-align: right;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {Group} from 'sentry/types/group';
import {trackAnalytics} from 'sentry/utils/analytics';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import SuspectTable from 'sentry/views/issueDetails/groupDistributions/suspectTable';
import FlagDetailsLink from 'sentry/views/issueDetails/groupFeatureFlags/flagDetailsLink';
import FlagDrawerCTA from 'sentry/views/issueDetails/groupFeatureFlags/flagDrawerCTA';
import useGroupFlagDrawerData from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFlagDrawerData';
Expand Down Expand Up @@ -38,6 +39,9 @@ export default function FlagDrawerContent({
}: Props) {
const organization = useOrganization();

// If we're showing the suspect section at all
const enableSuspectFlags = organization.features.includes('feature-flag-suspect-flags');

const {displayFlags, allGroupFlagCount, isPending, isError, refetch} =
useGroupFlagDrawerData({
environments,
Expand Down Expand Up @@ -85,6 +89,13 @@ export default function FlagDrawerContent({
</StyledEmptyStateWarning>
) : (
<Fragment>
{enableSuspectFlags ? (
<SuspectTable
debugSuspectScores={debugSuspectScores}
environments={environments}
group={group}
/>
) : null}
<Container>
{displayFlags.map(flag => (
<div key={flag.key}>
Expand Down
18 changes: 6 additions & 12 deletions static/app/views/issueDetails/groupTags/tagDistribution.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function TagDistribution({tag}: {tag: GroupTag}) {
<TagPanel>
<TagHeader>
<Tooltip title={tag.key} showOnlyOnOverflow skipWrapper>
<TagTitle>{tag.key}</TagTitle>
{tag.key}
</Tooltip>
</TagHeader>
<TagValueContent>
Expand Down Expand Up @@ -100,28 +100,23 @@ export function TagBar({
}

const TagPanel = styled('div')`
display: block;
display: flex;
flex-direction: column;
gap: ${space(0.5)};
border-radius: ${p => p.theme.borderRadius};
border: 1px solid ${p => p.theme.border};
padding: ${space(1)};
`;

const TagHeader = styled('h5')`
grid-area: header;
display: flex;
justify-content: space-between;
margin-bottom: ${space(0.5)};
color: ${p => p.theme.textColor};
`;

const TagTitle = styled('div')`
font-size: ${p => p.theme.fontSizeMedium};
font-weight: ${p => p.theme.fontWeightBold};
margin: 0;
${p => p.theme.overflowEllipsis}
`;

// The 40px is a buffer to prevent percentages from overflowing
const progressBarWidth = '45px';
const progressBarWidth = '45px'; // Prevent percentages from overflowing
const TagValueContent = styled('div')`
display: grid;
grid-template-columns: 4fr auto ${progressBarWidth};
Expand All @@ -144,7 +139,6 @@ const TagValue = styled('div')`
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin-right: ${space(0.5)};
`;

const TagBarPlaceholder = styled('div')`
Expand Down
Loading