Browse Source

feat(issues): Add tag drawer (#77880)

Scott Cooper 5 months ago
parent
commit
d98c69116a

+ 0 - 16
static/app/actionCreators/group.tsx

@@ -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;

+ 4 - 1
static/app/components/events/eventTagsAndScreenshot/index.spec.tsx

@@ -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 () {
@@ -123,7 +124,7 @@ describe('EventTagsAndScreenshot', function () {
     },
   ];
 
-  let mockDetailedProject;
+  let mockDetailedProject: jest.Mock;
   beforeEach(() => {
     MockApiClient.clearMockResponses();
     MockApiClient.addMockResponse({
@@ -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,

+ 16 - 1
static/app/components/events/eventTagsAndScreenshot/tags.tsx

@@ -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,
@@ -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/useGroupTagsDrawer';
 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';
 
@@ -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) => {
@@ -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')}

+ 6 - 1
static/app/components/events/highlights/highlightsDataSection.spec.tsx

@@ -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';
 
@@ -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,
@@ -35,6 +38,7 @@ describe('HighlightsDataSection', function () {
 
   beforeEach(() => {
     MockApiClient.clearMockResponses();
+    ProjectsStore.loadInitialData([project]);
     jest.clearAllMocks();
   });
 
@@ -53,6 +57,7 @@ describe('HighlightsDataSection', function () {
         event={event}
         project={project}
         viewAllRef={{current: null}}
+        groupId={group.id}
       />,
       {organization}
     );
@@ -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();

+ 15 - 1
static/app/components/events/highlights/highlightsDataSection.tsx

@@ -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/useGroupTagsDrawer';
 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>;
 }
@@ -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});

+ 11 - 31
static/app/components/group/tagFacets/index.tsx

@@ -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';
@@ -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';
 
@@ -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] = {
@@ -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({
@@ -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
@@ -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();

+ 2 - 12
static/app/components/groupPreviewTooltip/utils.tsx

@@ -5,8 +5,8 @@ import {defined} from 'sentry/utils';
 import {useApiQuery} from 'sentry/utils/queryClient';
 import useOrganization from 'sentry/utils/useOrganization';
 import useTimeout from 'sentry/utils/useTimeout';
+import {useGroup} from 'sentry/views/issueDetails/useGroup';
 import {
-  getGroupDetailsQueryData,
   getGroupEventDetailsQueryData,
   useDefaultIssueEvent,
 } from 'sentry/views/issueDetails/utils';
@@ -63,17 +63,7 @@ export function usePreviewEvent<T = Event>({
   );
 
   // Prefetch the group as well, but don't use the result
-  useApiQuery(
-    [
-      `/organizations/${organization.slug}/issues/${groupId}/`,
-      {query: getGroupDetailsQueryData()},
-    ],
-    {
-      staleTime: 30000,
-      gcTime: 30000,
-      enabled: defined(groupId),
-    }
-  );
+  useGroup({groupId, options: {enabled: defined(groupId)}});
 
   return eventQuery;
 }

+ 2 - 26
static/app/views/issueDetails/groupDetails.tsx

@@ -60,8 +60,8 @@ import {ERROR_TYPES} from 'sentry/views/issueDetails/constants';
 import SampleEventAlert from 'sentry/views/issueDetails/sampleEventAlert';
 import StreamlinedGroupHeader from 'sentry/views/issueDetails/streamline/header';
 import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
+import {makeFetchGroupQueryKey, useGroup} from 'sentry/views/issueDetails/useGroup';
 import {
-  getGroupDetailsQueryData,
   getGroupEventDetailsQueryData,
   getGroupReprocessingStatus,
   markEventSeen,
@@ -285,23 +285,6 @@ function useEventApiQuery({
   return isLatestOrRecommendedEvent ? latestOrRecommendedEvent : otherEventQuery;
 }
 
-type FetchGroupQueryParameters = {
-  environments: string[];
-  groupId: string;
-  organizationSlug: string;
-};
-
-function makeFetchGroupQueryKey({
-  groupId,
-  organizationSlug,
-  environments,
-}: FetchGroupQueryParameters): ApiQueryKey {
-  return [
-    `/organizations/${organizationSlug}/issues/${groupId}/`,
-    {query: getGroupDetailsQueryData({environments})},
-  ];
-}
-
 /**
  * This is a temporary measure to ensure that the GroupStore and query cache
  * are both up to date while we are still using both in the issue details page.
@@ -366,14 +349,7 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
     isError: isGroupError,
     error: groupError,
     refetch: refetchGroupCall,
-  } = useApiQuery<Group>(
-    makeFetchGroupQueryKey({organizationSlug: organization.slug, groupId, environments}),
-    {
-      staleTime: 30000,
-      gcTime: 30000,
-      retry: false,
-    }
-  );
+  } = useGroup({groupId});
 
   /**
    * Allows the GroupEventHeader to display the previous event while the new event is loading.

+ 6 - 1
static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx

@@ -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}

+ 7 - 8
static/app/views/issueDetails/groupSidebar.tsx

@@ -42,10 +42,8 @@ import {getAnalyicsDataForProject} from 'sentry/utils/projects';
 import {useApiQuery} from 'sentry/utils/queryClient';
 import {useLocation} from 'sentry/utils/useLocation';
 import {ParticipantList} from 'sentry/views/issueDetails/participantList';
-import {
-  getGroupDetailsQueryData,
-  useHasStreamlinedUI,
-} from 'sentry/views/issueDetails/utils';
+import {makeFetchGroupQueryKey} from 'sentry/views/issueDetails/useGroup';
+import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
 
 type Props = {
   environments: string[];
@@ -57,10 +55,11 @@ type Props = {
 
 function useFetchAllEnvsGroupData(organization: OrganizationSummary, group: Group) {
   return useApiQuery<Group>(
-    [
-      `/organizations/${organization.slug}/issues/${group.id}/`,
-      {query: getGroupDetailsQueryData()},
-    ],
+    makeFetchGroupQueryKey({
+      organizationSlug: organization.slug,
+      groupId: group.id,
+      environments: [],
+    }),
     {
       staleTime: 30000,
       gcTime: 30000,

Some files were not shown because too many files changed in this diff