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(issues): Add tag drawer #77880

Open
wants to merge 8 commits into
base: scttcper/group-tag-hooks
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
16 changes: 0 additions & 16 deletions static/app/actionCreators/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,22 +397,6 @@ export function mergeGroups(
);
}

export type GroupTagResponseItem = {
key: string;
name: string;
topValues: Array<{
count: number;
firstSeen: string;
lastSeen: string;
name: string;
value: string;
readable?: boolean;
}>;
totalValues: number;
};

export type GroupTagsResponse = GroupTagResponseItem[];

type FetchIssueTagValuesParameters = {
groupId: string;
orgSlug: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {TagFilter} from 'sentry/components/events/eventTags/util';
import {EventTagsAndScreenshot} from 'sentry/components/events/eventTagsAndScreenshot';
import GlobalModal from 'sentry/components/globalModal';
import ProjectsStore from 'sentry/stores/projectsStore';
import type {EventAttachment} from 'sentry/types/group';

describe('EventTagsAndScreenshot', function () {
Expand Down Expand Up @@ -123,7 +124,7 @@ describe('EventTagsAndScreenshot', function () {
},
];

let mockDetailedProject;
let mockDetailedProject: jest.Mock;
beforeEach(() => {
MockApiClient.clearMockResponses();
MockApiClient.addMockResponse({
Expand All @@ -140,6 +141,8 @@ describe('EventTagsAndScreenshot', function () {
url: '/organizations/org-slug/releases/io.sentry.sample.iOS-Swift%407.2.3%2B390/deploys/',
body: [],
});
ProjectsStore.init();
ProjectsStore.loadInitialData([project]);
mockDetailedProject = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/`,
body: project,
Expand Down
17 changes: 16 additions & 1 deletion static/app/components/events/eventTagsAndScreenshot/tags.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {forwardRef, useCallback, useMemo, useState} from 'react';
import {forwardRef, useCallback, useMemo, useRef, useState} from 'react';
import styled from '@emotion/styled';

import {Button} from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import {
getSentryDefaultTags,
Expand All @@ -14,8 +15,10 @@ import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Event} from 'sentry/types/event';
import type {Project} from 'sentry/types/project';
import {useGroupTagsDrawer} from 'sentry/views/issueDetails/groupTags/groupTagsDrawer';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';

import {EventTags} from '../eventTags';

Expand All @@ -27,6 +30,13 @@ type Props = {
export const EventTagsDataSection = forwardRef<HTMLElement, Props>(
function EventTagsDataSection({event, projectSlug}: Props, ref) {
const sentryTags = getSentryDefaultTags();
const hasStreamlinedUI = useHasStreamlinedUI();
const openButtonRef = useRef<HTMLButtonElement>(null);
const {openTagsDrawer} = useGroupTagsDrawer({
projectSlug: projectSlug,
groupId: event.groupID!,
openButtonRef: openButtonRef,
});

const [tagFilter, setTagFilter] = useState<TagFilter>(TagFilter.ALL);
const handleTagFilterChange = useCallback((value: TagFilter) => {
Expand All @@ -51,6 +61,11 @@ export const EventTagsDataSection = forwardRef<HTMLElement, Props>(

const actions = (
<ButtonBar gap={1}>
{hasStreamlinedUI && (
<Button onClick={openTagsDrawer} size="xs">
{t('View All Issue Tags')}
</Button>
)}
<SegmentedControl
size="xs"
aria-label={t('Filter tags')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {EventFixture} from 'sentry-fixture/event';
import {GroupFixture} from 'sentry-fixture/group';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';

Expand All @@ -11,11 +12,13 @@ import {
TEST_EVENT_CONTEXTS,
TEST_EVENT_TAGS,
} from 'sentry/components/events/highlights/util.spec';
import ProjectsStore from 'sentry/stores/projectsStore';
import * as analytics from 'sentry/utils/analytics';

describe('HighlightsDataSection', function () {
const organization = OrganizationFixture();
const project = ProjectFixture();
const group = GroupFixture();
const event = EventFixture({
contexts: TEST_EVENT_CONTEXTS,
tags: TEST_EVENT_TAGS,
Expand All @@ -35,6 +38,7 @@ describe('HighlightsDataSection', function () {

beforeEach(() => {
MockApiClient.clearMockResponses();
ProjectsStore.loadInitialData([project]);
jest.clearAllMocks();
});

Expand All @@ -53,6 +57,7 @@ describe('HighlightsDataSection', function () {
event={event}
project={project}
viewAllRef={{current: null}}
groupId={group.id}
/>,
{organization}
);
Expand Down Expand Up @@ -87,7 +92,7 @@ describe('HighlightsDataSection', function () {
body: {},
});

render(<HighlightsDataSection event={event} project={project} />, {
render(<HighlightsDataSection event={event} project={project} groupId={group.id} />, {
organization,
});
expect(screen.getByText('Event Highlights')).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ import theme from 'sentry/utils/theme';
import {useDetailedProject} from 'sentry/utils/useDetailedProject';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {useGroupTagsDrawer} from 'sentry/views/issueDetails/groupTags/groupTagsDrawer';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';

interface HighlightsDataSectionProps {
event: Event;
groupId: string;
project: Project;
viewAllRef?: React.RefObject<HTMLElement>;
}
Expand Down Expand Up @@ -253,12 +255,24 @@ function HighlightsData({
export default function HighlightsDataSection({
viewAllRef,
event,
groupId,
project,
}: HighlightsDataSectionProps) {
const organization = useOrganization();
const hasStreamlinedUI = useHasStreamlinedUI();
const openButtonRef = useRef<HTMLButtonElement>(null);
const {openTagsDrawer} = useGroupTagsDrawer({
groupId,
openButtonRef,
projectSlug: project.slug,
});

const viewAllButton = viewAllRef ? (
const viewAllButton = hasStreamlinedUI ? (
// Streamline details ui has "Jump to" feature, instead we'll show the drawer button
<Button ref={openButtonRef} size="xs" onClick={openTagsDrawer}>
{t('View All Issue Tags')}
</Button>
) : viewAllRef ? (
<Button
onClick={() => {
trackAnalytics('highlights.issue_details.view_all_clicked', {organization});
Expand Down
42 changes: 11 additions & 31 deletions static/app/components/group/tagFacets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import styled from '@emotion/styled';
import type {LocationDescriptor} from 'history';
import keyBy from 'lodash/keyBy';

import type {Tag} from 'sentry/actionCreators/events';
import type {GroupTagResponseItem} from 'sentry/actionCreators/group';
import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import LoadingError from 'sentry/components/loadingError';
import Placeholder from 'sentry/components/placeholder';
Expand All @@ -19,7 +17,10 @@ import {appendTagCondition} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {formatVersion} from 'sentry/utils/versions/formatVersion';
import {useGroupTagsReadable} from 'sentry/views/issueDetails/groupTags/useGroupTags';
import {
type GroupTag,
useGroupTagsReadable,
} from 'sentry/views/issueDetails/groupTags/useGroupTags';

import TagFacetsDistributionMeter from './tagFacetsDistributionMeter';

Expand All @@ -44,9 +45,9 @@ export const BACKEND_TAGS = [

export const DEFAULT_TAGS = ['transaction', 'environment', 'release'];

export function TAGS_FORMATTER(tagsData: Record<string, GroupTagResponseItem>) {
export function TAGS_FORMATTER(tagsData: Record<string, GroupTag>) {
// For "release" tag keys, format the release tag value to be more readable (ie removing version prefix)
const transformedTagsData = {};
const transformedTagsData: Record<string, GroupTag> = {};
Object.keys(tagsData).forEach(tagKey => {
if (tagKey === 'release') {
transformedTagsData[tagKey] = {
Expand Down Expand Up @@ -76,32 +77,12 @@ export function TAGS_FORMATTER(tagsData: Record<string, GroupTagResponseItem>) {
return transformedTagsData;
}

export function sumTagFacetsForTopValues(tag: Tag) {
return {
...tag,
name: tag.key,
totalValues: tag.topValues.reduce((acc, {count}) => acc + count, 0),
topValues: tag.topValues.map(({name, count}) => ({
key: tag.key,
name,
value: name,
count,

// These values aren't displayed in the sidebar
firstSeen: '',
lastSeen: '',
})),
};
}

type Props = {
environments: string[];
groupId: string;
project: Project;
tagKeys: string[];
tagFormatter?: (
tagsData: Record<string, GroupTagResponseItem>
) => Record<string, GroupTagResponseItem>;
tagFormatter?: (tagsData: Record<string, GroupTag>) => Record<string, GroupTag>;
};

export default function TagFacets({
Expand All @@ -118,16 +99,15 @@ export default function TagFacets({
environment: environments,
});

const tagsData = useMemo(() => {
const tagsData = useMemo((): Record<string, GroupTag> => {
if (!data) {
return {};
}

const keyed = keyBy(data, 'key');
const formatted =
tagFormatter?.(keyed as Record<string, GroupTagResponseItem>) ?? keyed;
const formatted = tagFormatter?.(keyed) ?? keyed;

return formatted as Record<string, GroupTagResponseItem>;
return formatted;
}, [data, tagFormatter]);

// filter out replayId since we no longer want to
Expand Down Expand Up @@ -228,7 +208,7 @@ function TagFacetsDistributionMeterWrapper({
organization: Organization;
project: Project;
tagKeys: string[];
tagsData: Record<string, GroupTagResponseItem>;
tagsData: Record<string, GroupTag>;
expandFirstTag?: boolean;
}) {
const location = useLocation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,12 @@ export function EventDetailsContent({
project={project}
/>
)}
<HighlightsDataSection event={event} project={project} viewAllRef={tagsRef} />
<HighlightsDataSection
groupId={group.id}
event={event}
project={project}
viewAllRef={tagsRef}
/>
{showPossibleSolutionsHigher && (
<ResourcesAndPossibleSolutionsIssueDetailsContent
event={event}
Expand Down
10 changes: 5 additions & 5 deletions static/app/views/issueDetails/groupTagValues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ type Props = {
const DEFAULT_SORT = 'count';

function useTagQueries({
group,
groupId,
tagKey,
environments,
sort,
cursor,
}: {
group: Group;
groupId: string;
sort: string | string[];
tagKey: string;
cursor?: string;
Expand All @@ -70,15 +70,15 @@ function useTagQueries({
getResponseHeader,
} = useFetchIssueTagValues({
orgSlug: organization.slug,
groupId: group.id,
groupId,
tagKey,
environment: environments,
sort,
cursor,
});
const {data: tag, isError: tagIsError} = useFetchIssueTag({
orgSlug: organization.slug,
groupId: group.id,
groupId,
tagKey,
});

Expand Down Expand Up @@ -108,7 +108,7 @@ function GroupTagValues({baseUrl, project, group, environments}: Props) {
const sortArrow = <IconArrow color="gray300" size="xs" direction="down" />;

const {tagValueList, tag, isLoading, isError, pageLinks} = useTagQueries({
group,
groupId: group.id,
sort,
tagKey,
environments,
Expand Down
Loading
Loading