Browse Source

fix(feedback): Make sure to load in the AppComponentsData so all issue-trackers are available in Feedback (#61968)

Turns out that we weren't loading AppComponentsData, so I extracted the
two api calls into a new file calls `useSentryAppComponentsData`,
rewrote the internals to leverage useApiQuery, and called it from inside
Feedback

Now all the issue-trackers are visible, including Linear:
<img width="604" alt="SCR-20231215-ntqg"
src="https://github.com/getsentry/sentry/assets/187460/078015bd-3093-41d6-9692-613efad27624">


Fixes https://github.com/getsentry/sentry/issues/61552
Ryan Albrecht 1 year ago
parent
commit
5b6e99745d

+ 0 - 15
static/app/actionCreators/sentryAppComponents.tsx

@@ -1,15 +0,0 @@
-import {Client} from 'sentry/api';
-import SentryAppComponentsStore from 'sentry/stores/sentryAppComponentsStore';
-import {SentryAppComponent} from 'sentry/types';
-
-export async function fetchSentryAppComponents(
-  api: Client,
-  orgSlug: string,
-  projectId: string
-): Promise<SentryAppComponent[]> {
-  const componentsUri = `/organizations/${orgSlug}/sentry-app-components/?projectId=${projectId}`;
-
-  const res = await api.requestPromise(componentsUri);
-  SentryAppComponentsStore.loadComponents(res);
-  return res;
-}

+ 0 - 9
static/app/components/feedback/decodeFeedbackId.tsx

@@ -1,9 +0,0 @@
-import {decodeScalar} from 'sentry/utils/queryString';
-
-export default function decodeFeedbackId(val: string | string[] | null | undefined) {
-  const [, feedbackId] = decodeScalar(val, '').split(':');
-
-  // TypeScript thinks `feedbackId` is a string, but it could be undefined.
-  // See `noUncheckedIndexedAccess`
-  return feedbackId ?? '';
-}

+ 15 - 0
static/app/components/feedback/decodeFeedbackSlug.tsx

@@ -0,0 +1,15 @@
+import {decodeScalar} from 'sentry/utils/queryString';
+
+// TypeScript thinks the values are strings, but they could be undefined.
+// See `noUncheckedIndexedAccess`
+interface Return {
+  feedbackId: string | undefined;
+  projectSlug: string | undefined;
+}
+
+export default function decodeFeedbackSlug(
+  val: string | string[] | null | undefined
+): Return {
+  const [projectSlug, feedbackId] = decodeScalar(val, '').split(':');
+  return {projectSlug, feedbackId};
+}

+ 5 - 0
static/app/components/feedback/feedbackItem/feedbackItemLoader.tsx

@@ -2,14 +2,19 @@ import FeedbackEmptyDetails from 'sentry/components/feedback/details/feedbackEmp
 import FeedbackErrorDetails from 'sentry/components/feedback/details/feedbackErrorDetails';
 import FeedbackItem from 'sentry/components/feedback/feedbackItem/feedbackItem';
 import useCurrentFeedbackId from 'sentry/components/feedback/useCurrentFeedbackId';
+import useCurrentFeedbackProject from 'sentry/components/feedback/useCurrentFeedbackProject';
 import useFetchFeedbackData from 'sentry/components/feedback/useFetchFeedbackData';
 import Placeholder from 'sentry/components/placeholder';
 import {t} from 'sentry/locale';
+import useSentryAppComponentsData from 'sentry/stores/useSentryAppComponentsData';
 
 export default function FeedbackItemLoader() {
   const feedbackId = useCurrentFeedbackId();
   const {issueResult, issueData, tags, eventData} = useFetchFeedbackData({feedbackId});
 
+  const projectSlug = useCurrentFeedbackProject();
+  useSentryAppComponentsData({projectId: projectSlug});
+
   // There is a case where we are done loading, but we're fetching updates
   // This happens when the user has seen a feedback, clicks around a bit, then
   // lands on the same one again.

+ 2 - 2
static/app/components/feedback/useCurrentFeedbackId.tsx

@@ -1,10 +1,10 @@
-import decodeFeedbackId from 'sentry/components/feedback/decodeFeedbackId';
+import decodeFeedbackSlug from 'sentry/components/feedback/decodeFeedbackSlug';
 import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 
 export default function useCurrentFeedbackId() {
   const {feedbackSlug} = useLocationQuery({
     fields: {
-      feedbackSlug: decodeFeedbackId,
+      feedbackSlug: val => decodeFeedbackSlug(val).feedbackId ?? '',
     },
   });
   return feedbackSlug;

+ 11 - 0
static/app/components/feedback/useCurrentFeedbackProject.tsx

@@ -0,0 +1,11 @@
+import decodeFeedbackSlug from 'sentry/components/feedback/decodeFeedbackSlug';
+import useLocationQuery from 'sentry/utils/url/useLocationQuery';
+
+export default function useCurrentFeedbackProject() {
+  const {feedbackSlug} = useLocationQuery({
+    fields: {
+      feedbackSlug: val => decodeFeedbackSlug(val).projectSlug ?? '',
+    },
+  });
+  return feedbackSlug;
+}

+ 35 - 0
static/app/stores/useSentryAppComponentsData.tsx

@@ -0,0 +1,35 @@
+import {useEffect} from 'react';
+
+import SentryAppComponentsStore from 'sentry/stores/sentryAppComponentsStore';
+import SentryAppInstallationStore from 'sentry/stores/sentryAppInstallationsStore';
+import {SentryAppComponent, SentryAppInstallation} from 'sentry/types';
+import {useApiQuery} from 'sentry/utils/queryClient';
+import useOrganization from 'sentry/utils/useOrganization';
+
+interface Props {
+  projectId: string;
+}
+
+export default function useSentryAppComponentsData({projectId}: Props) {
+  const organization = useOrganization();
+
+  const {data: installs} = useApiQuery<SentryAppInstallation[]>(
+    [`/organizations/${organization.slug}/sentry-app-installations/`],
+    {staleTime: Infinity}
+  );
+  useEffect(() => {
+    if (installs) {
+      SentryAppInstallationStore.load(installs);
+    }
+  }, [installs]);
+
+  const {data: components} = useApiQuery<SentryAppComponent[]>(
+    [`/organizations/${organization.slug}/sentry-app-components/`, {query: {projectId}}],
+    {enabled: Boolean(projectId), staleTime: Infinity}
+  );
+  useEffect(() => {
+    if (components) {
+      SentryAppComponentsStore.loadComponents(components);
+    }
+  }, [components]);
+}

+ 0 - 12
static/app/utils/fetchSentryAppInstallations.tsx

@@ -1,12 +0,0 @@
-import {Client} from 'sentry/api';
-import SentryAppInstallationStore from 'sentry/stores/sentryAppInstallationsStore';
-import {SentryAppInstallation} from 'sentry/types';
-
-const fetchSentryAppInstallations = async (api: Client, orgSlug: string) => {
-  const installsUri = `/organizations/${orgSlug}/sentry-app-installations/`;
-
-  const installs: SentryAppInstallation[] = await api.requestPromise(installsUri);
-  SentryAppInstallationStore.load(installs);
-};
-
-export default fetchSentryAppInstallations;

+ 4 - 3
static/app/views/issueDetails/groupEventDetails/groupEventDetails.spec.tsx

@@ -76,7 +76,6 @@ function TestComponent(
   );
 
   const mergedProps: GroupEventDetailsProps = {
-    api: new MockApiClient(),
     group,
     event,
     project,
@@ -263,8 +262,9 @@ const mockGroupApis = (
   });
 
   MockApiClient.addMockResponse({
-    url: `/organizations/${organization.slug}/sentry-app-components/?projectId=${project.id}`,
+    url: `/organizations/${organization.slug}/sentry-app-components/`,
     body: [],
+    match: [MockApiClient.matchQuery({projectId: project.id})],
   });
 
   MockApiClient.addMockResponse({
@@ -557,8 +557,9 @@ describe('Platform Integrations', () => {
     });
 
     componentsRequest = MockApiClient.addMockResponse({
-      url: `/organizations/${props.organization.slug}/sentry-app-components/?projectId=${props.project.id}`,
+      url: `/organizations/${props.organization.slug}/sentry-app-components/`,
       body: [component],
+      match: [MockApiClient.matchQuery({projectId: props.project.id})],
     });
 
     render(<TestComponent />, {organization: props.organization});

+ 3 - 20
static/app/views/issueDetails/groupEventDetails/groupEventDetails.tsx

@@ -1,11 +1,8 @@
 import {Fragment, useEffect} from 'react';
 import {browserHistory, RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
-import * as Sentry from '@sentry/react';
 import isEqual from 'lodash/isEqual';
 
-import {fetchSentryAppComponents} from 'sentry/actionCreators/sentryAppComponents';
-import {Client} from 'sentry/api';
 import ArchivedBox from 'sentry/components/archivedBox';
 import GroupEventDetailsLoadingError from 'sentry/components/errors/groupEventDetailsLoadingError';
 import {withMeta} from 'sentry/components/events/meta/metaProxy';
@@ -15,6 +12,7 @@ import LoadingIndicator from 'sentry/components/loadingIndicator';
 import MutedBox from 'sentry/components/mutedBox';
 import {TransactionProfileIdProvider} from 'sentry/components/profiling/transactionProfileIdProvider';
 import ResolutionBox from 'sentry/components/resolutionBox';
+import useSentryAppComponentsData from 'sentry/stores/useSentryAppComponentsData';
 import {space} from 'sentry/styles/space';
 import {
   Group,
@@ -25,7 +23,6 @@ import {
 } from 'sentry/types';
 import {Event} from 'sentry/types/event';
 import {defined} from 'sentry/utils';
-import fetchSentryAppInstallations from 'sentry/utils/fetchSentryAppInstallations';
 import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
 import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
 import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
@@ -50,7 +47,6 @@ const EscalatingIssuesFeedback = HookOrDefault({
 
 export interface GroupEventDetailsProps
   extends RouteComponentProps<{groupId: string; eventId?: string}, {}> {
-  api: Client;
   eventError: boolean;
   group: Group;
   groupReprocessingStatus: ReprocessingStatus;
@@ -72,7 +68,6 @@ function GroupEventDetails(props: GroupEventDetailsProps) {
     loadingEvent,
     onRetry,
     eventError,
-    api,
     params,
   } = props;
   const eventWithMeta = withMeta(event);
@@ -81,26 +76,14 @@ function GroupEventDetails(props: GroupEventDetailsProps) {
   const hasReprocessingV2Feature = organization.features?.includes('reprocessing-v2');
   const {activity: activities} = group;
   const mostRecentActivity = getGroupMostRecentActivity(activities);
-  const orgSlug = organization.slug;
   const projectId = project.id;
   const environments = useEnvironmentsFromUrl();
   const prevEnvironment = usePrevious(environments);
   const prevEvent = usePrevious(event);
 
   // load the data
-  useEffect(() => {
-    fetchSentryAppInstallations(api, orgSlug);
-    // TODO(marcos): Sometimes PageFiltersStore cannot pick a project.
-    if (projectId) {
-      fetchSentryAppComponents(api, orgSlug, projectId);
-    } else {
-      Sentry.withScope(scope => {
-        scope.setExtra('orgSlug', orgSlug);
-        scope.setExtra('projectId', projectId);
-        Sentry.captureMessage('Project ID was not set');
-      });
-    }
-  }, [api, orgSlug, projectId]);
+  useSentryAppComponentsData({projectId});
+
   // If environments are being actively changed and will no longer contain the
   // current event's environment, redirect to latest
   useEffect(() => {

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