Browse Source

fix(hybridcloud) Use organization slug scoped URL for user feedback (#55946)

In order to route requests across regions we need to organization slugs.
I aligned the new and existing methods on `orgSlug` instead of
`organizationSlug` 🤷 or `orgId` 🤮 and cleaned up some ellipsis usage as
well.

Refs HC-877
Mark Story 1 year ago
parent
commit
42138079e2

+ 43 - 21
static/app/actionCreators/group.tsx

@@ -15,6 +15,7 @@ type AssignToUserParams = {
    * Issue id
    */
   id: string;
+  orgSlug: string;
   user: User | Actor;
   member?: Member;
 };
@@ -22,7 +23,7 @@ type AssignToUserParams = {
 export function assignToUser(params: AssignToUserParams) {
   const api = new Client();
 
-  const endpoint = `/issues/${params.id}/`;
+  const endpoint = `/organizations/${params.orgSlug}/issues/${params.id}/`;
 
   const id = uniqueId();
 
@@ -52,10 +53,14 @@ export function assignToUser(params: AssignToUserParams) {
   return request;
 }
 
-export function clearAssignment(groupId: string, assignedBy: AssignedBy) {
+export function clearAssignment(
+  groupId: string,
+  orgSlug: string,
+  assignedBy: AssignedBy
+) {
   const api = new Client();
 
-  const endpoint = `/issues/${groupId}/`;
+  const endpoint = `/organizations/${orgSlug}/issues/${groupId}/`;
 
   const id = uniqueId();
 
@@ -90,12 +95,13 @@ type AssignToActorParams = {
    * Issue id
    */
   id: string;
+  orgSlug: string;
 };
 
-export function assignToActor({id, actor, assignedBy}: AssignToActorParams) {
+export function assignToActor({id, actor, assignedBy, orgSlug}: AssignToActorParams) {
   const api = new Client();
 
-  const endpoint = `/issues/${id}/`;
+  const endpoint = `/organizations/${orgSlug}/issues/${id}/`;
 
   const guid = uniqueId();
   let actorId = '';
@@ -131,7 +137,13 @@ export function assignToActor({id, actor, assignedBy}: AssignToActorParams) {
     });
 }
 
-export function deleteNote(api: Client, group: Group, id: string, _oldText: string) {
+export function deleteNote(
+  api: Client,
+  orgSlug: string,
+  group: Group,
+  id: string,
+  _oldText: string
+) {
   const restore = group.activity.find(activity => activity.id === id);
   const index = GroupStore.removeActivity(group.id, id);
 
@@ -140,20 +152,26 @@ export function deleteNote(api: Client, group: Group, id: string, _oldText: stri
     return Promise.reject(new Error('Group was not found in store'));
   }
 
-  const promise = api.requestPromise(`/issues/${group.id}/comments/${id}/`, {
-    method: 'DELETE',
-  });
+  const promise = api.requestPromise(
+    `/organizations/${orgSlug}/issues/${group.id}/comments/${id}/`,
+    {
+      method: 'DELETE',
+    }
+  );
 
   promise.catch(() => GroupStore.addActivity(group.id, restore, index));
 
   return promise;
 }
 
-export function createNote(api: Client, group: Group, note: Note) {
-  const promise = api.requestPromise(`/issues/${group.id}/comments/`, {
-    method: 'POST',
-    data: note,
-  });
+export function createNote(api: Client, orgSlug: string, group: Group, note: Note) {
+  const promise = api.requestPromise(
+    `/organizations/${orgSlug}/issues/${group.id}/comments/`,
+    {
+      method: 'POST',
+      data: note,
+    }
+  );
 
   promise.then(data => GroupStore.addActivity(group.id, data));
 
@@ -162,6 +180,7 @@ export function createNote(api: Client, group: Group, note: Note) {
 
 export function updateNote(
   api: Client,
+  orgSlug: string,
   group: Group,
   note: Note,
   id: string,
@@ -169,10 +188,13 @@ export function updateNote(
 ) {
   GroupStore.updateActivity(group.id, id, {data: {text: note.text}});
 
-  const promise = api.requestPromise(`/issues/${group.id}/comments/${id}/`, {
-    method: 'PUT',
-    data: note,
-  });
+  const promise = api.requestPromise(
+    `/organizations/${orgSlug}/issues/${group.id}/comments/${id}/`,
+    {
+      method: 'PUT',
+      data: note,
+    }
+  );
 
   promise.catch(() => GroupStore.updateActivity(group.id, id, {data: {text: oldText}}));
 
@@ -390,19 +412,19 @@ export type GroupTagsResponse = GroupTagResponseItem[];
 type FetchIssueTagsParameters = {
   environment: string[];
   limit: number;
-  organizationSlug: string;
+  orgSlug: string;
   readable: boolean;
   groupId?: string;
 };
 
 export const makeFetchIssueTagsQueryKey = ({
   groupId,
-  organizationSlug,
+  orgSlug,
   environment,
   readable,
   limit,
 }: FetchIssueTagsParameters): ApiQueryKey => [
-  `/organizations/${organizationSlug}/issues/${groupId}/tags/`,
+  `/organizations/${orgSlug}/issues/${groupId}/tags/`,
   {query: {environment, readable, limit}},
 ];
 

+ 10 - 10
static/app/components/assigneeSelector.spec.tsx

@@ -89,7 +89,7 @@ describe('AssigneeSelector', () => {
 
     assignMock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_1.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_1.id}/`,
       body: {
         ...GROUP_1,
         assignedTo: {...USER_1, type: 'user'},
@@ -98,7 +98,7 @@ describe('AssigneeSelector', () => {
 
     assignGroup2Mock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_2.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_2.id}/`,
       body: {
         ...GROUP_2,
         assignedTo: {...USER_1, type: 'user'},
@@ -204,7 +204,7 @@ describe('AssigneeSelector', () => {
     await userEvent.click(screen.getByText(`${USER_1.name} (You)`));
 
     expect(assignMock).toHaveBeenLastCalledWith(
-      '/issues/1337/',
+      '/organizations/org-slug/issues/1337/',
       expect.objectContaining({
         data: {assignedTo: 'user:1', assignedBy: 'assignee_selector'},
       })
@@ -219,7 +219,7 @@ describe('AssigneeSelector', () => {
     MockApiClient.clearMockResponses();
     assignMock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_1.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_1.id}/`,
       body: {
         ...GROUP_1,
         assignedTo: {...TEAM_1, type: 'team'},
@@ -234,7 +234,7 @@ describe('AssigneeSelector', () => {
 
     await waitFor(() =>
       expect(assignMock).toHaveBeenCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: 'team:3', assignedBy: 'assignee_selector'},
         })
@@ -256,7 +256,7 @@ describe('AssigneeSelector', () => {
 
     await waitFor(() =>
       expect(assignMock).toHaveBeenCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: 'team:3', assignedBy: 'assignee_selector'},
         })
@@ -269,7 +269,7 @@ describe('AssigneeSelector', () => {
     // api was called with empty string, clearing assignment
     await waitFor(() =>
       expect(assignMock).toHaveBeenLastCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: '', assignedBy: 'assignee_selector'},
         })
@@ -308,7 +308,7 @@ describe('AssigneeSelector', () => {
 
     await waitFor(() =>
       expect(assignGroup2Mock).toHaveBeenLastCalledWith(
-        '/issues/1338/',
+        '/organizations/org-slug/issues/1338/',
         expect.objectContaining({
           data: {assignedTo: `user:${USER_2.id}`, assignedBy: 'assignee_selector'},
         })
@@ -329,7 +329,7 @@ describe('AssigneeSelector', () => {
 
     assignMock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_2.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_2.id}/`,
       statusCode: 400,
       body: {detail: 'Cannot assign to non-team member'},
     });
@@ -377,7 +377,7 @@ describe('AssigneeSelector', () => {
 
     await waitFor(() =>
       expect(assignGroup2Mock).toHaveBeenCalledWith(
-        '/issues/1338/',
+        '/organizations/org-slug/issues/1338/',
         expect.objectContaining({
           data: {assignedTo: `user:${USER_1.id}`, assignedBy: 'assignee_selector'},
         })

+ 13 - 2
static/app/components/assigneeSelectorDropdown.tsx

@@ -186,14 +186,23 @@ export class AssigneeSelectorDropdown extends Component<
   }
 
   assignToUser(user: User | Actor) {
-    assignToUser({id: this.props.id, user, assignedBy: 'assignee_selector'});
+    const {organization} = this.props;
+    assignToUser({
+      id: this.props.id,
+      orgSlug: organization.slug,
+      user,
+      assignedBy: 'assignee_selector',
+    });
     this.setState({loading: true});
   }
 
   assignToTeam(team: Team) {
+    const {organization} = this.props;
+
     assignToActor({
       actor: {id: team.id, type: 'team'},
       id: this.props.id,
+      orgSlug: organization.slug,
       assignedBy: 'assignee_selector',
     });
     this.setState({loading: true});
@@ -225,8 +234,10 @@ export class AssigneeSelectorDropdown extends Component<
   };
 
   clearAssignTo = (e: React.MouseEvent<HTMLDivElement>) => {
+    const {organization} = this.props;
+
     // clears assignment
-    clearAssignment(this.props.id, 'assignee_selector');
+    clearAssignment(this.props.id, organization.slug, 'assignee_selector');
     this.setState({loading: true});
     e.stopPropagation();
   };

+ 7 - 7
static/app/components/group/assignedTo.spec.tsx

@@ -107,7 +107,7 @@ describe('Group > AssignedTo', () => {
     };
     const assignMock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_1.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_1.id}/`,
       body: assignedGroup,
     });
     const {rerender} = render(
@@ -124,7 +124,7 @@ describe('Group > AssignedTo', () => {
 
     await waitFor(() =>
       expect(assignMock).toHaveBeenCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: 'team:3', assignedBy: 'assignee_selector'},
         })
@@ -146,7 +146,7 @@ describe('Group > AssignedTo', () => {
     };
     const assignMock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_1.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_1.id}/`,
       body: assignedGroup,
     });
 
@@ -163,7 +163,7 @@ describe('Group > AssignedTo', () => {
 
     await waitFor(() =>
       expect(assignMock).toHaveBeenCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: 'team:3', assignedBy: 'assignee_selector'},
         })
@@ -178,7 +178,7 @@ describe('Group > AssignedTo', () => {
     // api was called with empty string, clearing assignment
     await waitFor(() =>
       expect(assignMock).toHaveBeenLastCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: '', assignedBy: 'assignee_selector'},
         })
@@ -213,7 +213,7 @@ describe('Group > AssignedTo', () => {
     };
     const assignMock = MockApiClient.addMockResponse({
       method: 'PUT',
-      url: `/issues/${GROUP_1.id}/`,
+      url: `/organizations/org-slug/issues/${GROUP_1.id}/`,
       body: assignedGroup,
     });
 
@@ -232,7 +232,7 @@ describe('Group > AssignedTo', () => {
 
     await waitFor(() =>
       expect(assignMock).toHaveBeenLastCalledWith(
-        '/issues/1337/',
+        '/organizations/org-slug/issues/1337/',
         expect.objectContaining({
           data: {assignedTo: 'user:2', assignedBy: 'assignee_selector'},
         })

+ 1 - 1
static/app/components/group/tagFacets/index.tsx

@@ -93,7 +93,7 @@ export default function TagFacets({
 
   const {isLoading, isError, data, refetch} = useFetchIssueTagsForDetailsPage({
     groupId,
-    organizationSlug: organization.slug,
+    orgSlug: organization.slug,
     environment: environments,
   });
 

+ 1 - 1
static/app/views/issueDetails/groupActivity.spec.tsx

@@ -331,7 +331,7 @@ describe('GroupActivity', function () {
 
     beforeEach(function () {
       deleteMock = MockApiClient.addMockResponse({
-        url: '/issues/1337/comments/note-1/',
+        url: '/organizations/org-slug/issues/1337/comments/note-1/',
         method: 'DELETE',
       });
       ConfigStore.set('user', TestStubs.User({id: '123', isSuperuser: true}));

+ 9 - 9
static/app/views/issueDetails/groupActivity.tsx

@@ -64,12 +64,12 @@ class GroupActivity extends Component<Props, State> {
   };
 
   handleNoteDelete = async ({noteId, text: oldText}) => {
-    const {api, group} = this.props;
+    const {api, group, organization} = this.props;
 
-    addLoadingMessage(t('Removing comment...'));
+    addLoadingMessage(t('Removing comment\u{2026}'));
 
     try {
-      await deleteNote(api, group, noteId, oldText);
+      await deleteNote(api, organization.slug, group, noteId, oldText);
       clearIndicators();
     } catch (_err) {
       addErrorMessage(t('Failed to delete comment'));
@@ -81,16 +81,16 @@ class GroupActivity extends Component<Props, State> {
    * This can be abstracted a bit if we create more objects that can have activities
    */
   handleNoteCreate = async note => {
-    const {api, group} = this.props;
+    const {api, group, organization} = this.props;
 
     this.setState({
       createBusy: true,
     });
 
-    addLoadingMessage(t('Posting comment...'));
+    addLoadingMessage(t('Posting comment\u{2026}'));
 
     try {
-      await createNote(api, group, note);
+      await createNote(api, organization.slug, group, note);
 
       this.setState({
         createBusy: false,
@@ -111,12 +111,12 @@ class GroupActivity extends Component<Props, State> {
   };
 
   handleNoteUpdate = async (note, {noteId, text: oldText}) => {
-    const {api, group} = this.props;
+    const {api, group, organization} = this.props;
 
-    addLoadingMessage(t('Updating comment...'));
+    addLoadingMessage(t('Updating comment\u{2026}'));
 
     try {
-      await updateNote(api, group, note, noteId, oldText);
+      await updateNote(api, organization.slug, group, note, noteId, oldText);
       clearIndicators();
     } catch (error) {
       this.setState({

+ 1 - 1
static/app/views/issueDetails/groupDetails.tsx

@@ -814,7 +814,7 @@ function GroupDetails(props: GroupDetailsProps) {
   const {data} = useFetchIssueTagsForDetailsPage(
     {
       groupId: router.params.groupId,
-      organizationSlug: organization.slug,
+      orgSlug: organization.slug,
       environment: environments,
     },
     // Don't want this query to take precedence over the main requests

+ 4 - 3
static/app/views/issueDetails/groupUserFeedback.tsx

@@ -57,14 +57,15 @@ class GroupUserFeedback extends Component<Props, State> {
   }
 
   fetchData = () => {
+    const {group, location, organization, params} = this.props;
     this.setState({
       loading: true,
       error: false,
     });
 
-    fetchGroupUserReports(this.props.group.id, {
-      ...this.props.params,
-      cursor: this.props.location.query.cursor || '',
+    fetchGroupUserReports(organization.slug, group.id, {
+      ...params,
+      cursor: location.query.cursor || '',
     })
       .then(([data, _, resp]) => {
         this.setState({

+ 9 - 5
static/app/views/issueDetails/utils.tsx

@@ -30,10 +30,14 @@ export function markEventSeen(
   );
 }
 
-export function fetchGroupUserReports(groupId: string, query: Record<string, string>) {
+export function fetchGroupUserReports(
+  orgSlug: string,
+  groupId: string,
+  query: Record<string, string>
+) {
   const api = new Client();
 
-  return api.requestPromise(`/issues/${groupId}/user-reports/`, {
+  return api.requestPromise(`/organizations/${orgSlug}/issues/${groupId}/user-reports/`, {
     includeAllArgs: true,
     query,
   });
@@ -148,11 +152,11 @@ export function getGroupReprocessingStatus(
 export const useFetchIssueTagsForDetailsPage = (
   {
     groupId,
-    organizationSlug,
+    orgSlug,
     environment = [],
   }: {
     environment: string[];
-    organizationSlug: string;
+    orgSlug: string;
     groupId?: string;
   },
   {enabled = true}: {enabled?: boolean} = {}
@@ -160,7 +164,7 @@ export const useFetchIssueTagsForDetailsPage = (
   return useFetchIssueTags(
     {
       groupId,
-      organizationSlug,
+      orgSlug,
       environment,
       readable: true,
       limit: 4,