Просмотр исходного кода

feat(workflow): Add toast to issue stream when taking an action on issue (#32954)

* feat(workflow): Add toast to issue stream when taking an action on issue

Add toast to the issue stream when taking an action on an issue (Resolve, Ignore, Mark Review, Merge, and Delete).

FIXES WOR-1605

* add check for flag

* update tests
Kelly Carino 2 лет назад
Родитель
Сommit
0fbdd2ebb9

+ 9 - 2
static/app/stores/groupStore.tsx

@@ -360,13 +360,20 @@ const storeConfig: Reflux.StoreDefinition & Internals & GroupStoreInterface = {
 
 
   onDeleteSuccess(_changeId, itemIds, _response) {
   onDeleteSuccess(_changeId, itemIds, _response) {
     itemIds = this._itemIdsOrAll(itemIds);
     itemIds = this._itemIdsOrAll(itemIds);
+
+    if (itemIds.length > 1) {
+      showAlert(t(`Deleted ${itemIds.length} Issues`), 'success');
+    } else {
+      const shortId = itemIds.map(item => GroupStore.get(item)?.shortId).join('');
+      showAlert(t(`Deleted ${shortId}`), 'success');
+    }
+
     const itemIdSet = new Set(itemIds);
     const itemIdSet = new Set(itemIds);
     itemIds.forEach(itemId => {
     itemIds.forEach(itemId => {
       delete this.statuses[itemId];
       delete this.statuses[itemId];
       this.clearStatus(itemId, 'delete');
       this.clearStatus(itemId, 'delete');
     });
     });
     this.items = this.items.filter(item => !itemIdSet.has(item.id));
     this.items = this.items.filter(item => !itemIdSet.has(item.id));
-    showAlert(t('The selected events have been scheduled for deletion.'), 'success');
     this.trigger(new Set(itemIds));
     this.trigger(new Set(itemIds));
   },
   },
 
 
@@ -428,7 +435,7 @@ const storeConfig: Reflux.StoreDefinition & Internals & GroupStoreInterface = {
         (response && response.merge && item.id === response.merge.parent)
         (response && response.merge && item.id === response.merge.parent)
     );
     );
 
 
-    showAlert(t('The selected events have been scheduled for merge.'), 'success');
+    showAlert(t(`Merged ${mergedIds.length} Issues`), 'success');
     this.trigger(new Set(mergedIds));
     this.trigger(new Set(mergedIds));
   },
   },
 
 

+ 11 - 12
static/app/views/issueList/actions/index.tsx

@@ -119,9 +119,15 @@ class IssueListActions extends React.Component<Props, State> {
   handleUpdate = (data?: any) => {
   handleUpdate = (data?: any) => {
     const {selection, api, organization, query, onMarkReviewed} = this.props;
     const {selection, api, organization, query, onMarkReviewed} = this.props;
     const orgId = organization.slug;
     const orgId = organization.slug;
+    const hasIssueListRemovalAction = organization.features.includes(
+      'issue-list-removal-action'
+    );
 
 
     this.actionSelectedGroups(itemIds => {
     this.actionSelectedGroups(itemIds => {
-      addLoadingMessage(t('Saving changes\u2026'));
+      // TODO(Kelly): remove once issue-list-removal-action feature is stable
+      if (!hasIssueListRemovalAction) {
+        addLoadingMessage(t('Saving changes\u2026'));
+      }
 
 
       if (data?.inbox === false) {
       if (data?.inbox === false) {
         onMarkReviewed?.(itemIds ?? []);
         onMarkReviewed?.(itemIds ?? []);
@@ -148,7 +154,9 @@ class IssueListActions extends React.Component<Props, State> {
         },
         },
         {
         {
           complete: () => {
           complete: () => {
-            clearIndicators();
+            if (!hasIssueListRemovalAction) {
+              clearIndicators();
+            }
           },
           },
         }
         }
       );
       );
@@ -159,8 +167,6 @@ class IssueListActions extends React.Component<Props, State> {
     const {selection, api, organization, query, onDelete} = this.props;
     const {selection, api, organization, query, onDelete} = this.props;
     const orgId = organization.slug;
     const orgId = organization.slug;
 
 
-    addLoadingMessage(t('Removing events\u2026'));
-
     this.actionSelectedGroups(itemIds => {
     this.actionSelectedGroups(itemIds => {
       bulkDelete(
       bulkDelete(
         api,
         api,
@@ -174,7 +180,6 @@ class IssueListActions extends React.Component<Props, State> {
         },
         },
         {
         {
           complete: () => {
           complete: () => {
-            clearIndicators();
             onDelete();
             onDelete();
           },
           },
         }
         }
@@ -186,8 +191,6 @@ class IssueListActions extends React.Component<Props, State> {
     const {selection, api, organization, query} = this.props;
     const {selection, api, organization, query} = this.props;
     const orgId = organization.slug;
     const orgId = organization.slug;
 
 
-    addLoadingMessage(t('Merging events\u2026'));
-
     this.actionSelectedGroups(itemIds => {
     this.actionSelectedGroups(itemIds => {
       mergeGroups(
       mergeGroups(
         api,
         api,
@@ -199,11 +202,7 @@ class IssueListActions extends React.Component<Props, State> {
           environment: selection.environments,
           environment: selection.environments,
           ...selection.datetime,
           ...selection.datetime,
         },
         },
-        {
-          complete: () => {
-            clearIndicators();
-          },
-        }
+        {}
       );
       );
     });
     });
   };
   };

+ 22 - 6
static/app/views/issueList/overview.tsx

@@ -11,6 +11,7 @@ import omit from 'lodash/omit';
 import pickBy from 'lodash/pickBy';
 import pickBy from 'lodash/pickBy';
 import * as qs from 'query-string';
 import * as qs from 'query-string';
 
 
+import {addMessage} from 'sentry/actionCreators/indicator';
 import {fetchOrgMembers, indexMembersByProject} from 'sentry/actionCreators/members';
 import {fetchOrgMembers, indexMembersByProject} from 'sentry/actionCreators/members';
 import {
 import {
   deleteSavedSearch,
   deleteSavedSearch,
@@ -30,7 +31,7 @@ import QueryCount from 'sentry/components/queryCount';
 import StreamGroup from 'sentry/components/stream/group';
 import StreamGroup from 'sentry/components/stream/group';
 import ProcessingIssueList from 'sentry/components/stream/processingIssueList';
 import ProcessingIssueList from 'sentry/components/stream/processingIssueList';
 import {DEFAULT_QUERY, DEFAULT_STATS_PERIOD} from 'sentry/constants';
 import {DEFAULT_QUERY, DEFAULT_STATS_PERIOD} from 'sentry/constants';
-import {tct} from 'sentry/locale';
+import {t, tct} from 'sentry/locale';
 import GroupStore from 'sentry/stores/groupStore';
 import GroupStore from 'sentry/stores/groupStore';
 import {PageContent} from 'sentry/styles/organization';
 import {PageContent} from 'sentry/styles/organization';
 import {
 import {
@@ -773,21 +774,21 @@ class IssueListOverview extends React.Component<Props, State> {
         .map(item => item.id);
         .map(item => item.id);
       const reviewedIds = this._streamManager
       const reviewedIds = this._streamManager
         .getAllItems()
         .getAllItems()
-        .filter(id => !id.inbox)
+        .filter(id => !id.inbox && id.status !== 'resolved' && id.status !== 'ignored')
         .map(item => item.id);
         .map(item => item.id);
       // Remove Ignored and Resolved group ids from the issue stream, but if you have a query
       // Remove Ignored and Resolved group ids from the issue stream, but if you have a query
       // that includes these statuses or there's no query/you want to see ALL issues,
       // that includes these statuses or there's no query/you want to see ALL issues,
       // don't trigger these group ids to be removed from the issue stream.
       // don't trigger these group ids to be removed from the issue stream.
       if (resolvedIds.length > 0 && !query.includes('is:resolved') && !!query) {
       if (resolvedIds.length > 0 && !query.includes('is:resolved') && !!query) {
-        this.onIssueAction(resolvedIds);
+        this.onIssueAction(resolvedIds, t('Resolved'));
       }
       }
       if (ignoredIds.length > 0 && !query.includes('is:ignored') && !!query) {
       if (ignoredIds.length > 0 && !query.includes('is:ignored') && !!query) {
-        this.onIssueAction(ignoredIds);
+        this.onIssueAction(ignoredIds, t('Ignored'));
       }
       }
       // Remove issues that are marked as Reviewed from the For Review tab, but still include the
       // Remove issues that are marked as Reviewed from the For Review tab, but still include the
       // issues if not on the For Review tab, or no query for ALL issues.
       // issues if not on the For Review tab, or no query for ALL issues.
       if (reviewedIds.length > 0 && isForReviewQuery(query) && !!query) {
       if (reviewedIds.length > 0 && isForReviewQuery(query) && !!query) {
-        this.onIssueAction(reviewedIds);
+        this.onIssueAction(reviewedIds, t('Reviewed'));
       }
       }
     }
     }
 
 
@@ -1048,6 +1049,12 @@ class IssueListOverview extends React.Component<Props, State> {
     );
     );
 
 
     if (!isForReviewQuery(query)) {
     if (!isForReviewQuery(query)) {
+      if (itemIds.length > 1) {
+        addMessage(t(`Reviewed ${itemIds.length} Issues`), 'success', {duration: 4000});
+      } else {
+        const shortId = itemIds.map(item => GroupStore.get(item)?.shortId).toString();
+        addMessage(t(`Reviewed ${shortId}`), 'success', {duration: 4000});
+      }
       return;
       return;
     }
     }
 
 
@@ -1073,7 +1080,16 @@ class IssueListOverview extends React.Component<Props, State> {
     }
     }
   };
   };
 
 
-  onIssueAction = (itemIds: string[]) => {
+  onIssueAction = (itemIds: string[], actionType: string) => {
+    if (itemIds.length > 1) {
+      addMessage(t(`${actionType} ${itemIds.length} Issues`), 'success', {
+        duration: 4000,
+      });
+    } else {
+      const shortId = itemIds.map(item => GroupStore.get(item)?.shortId).toString();
+      addMessage(t(`${actionType} ${shortId}`), 'success', {duration: 4000});
+    }
+
     GroupStore.remove(itemIds);
     GroupStore.remove(itemIds);
     this.setState({
     this.setState({
       actionTaken: true,
       actionTaken: true,

+ 2 - 1
tests/acceptance/page_objects/issue_list.py

@@ -36,7 +36,8 @@ class IssueListPage(BasePage):
         self.browser.wait_until('[data-test-id="resolved-issue"]')
         self.browser.wait_until('[data-test-id="resolved-issue"]')
 
 
     def wait_for_issue_removal(self):
     def wait_for_issue_removal(self):
-        self.browser.wait_until_not('[data-test-id="toast-loading"]')
+        self.browser.click_when_visible('[data-test-id="toast-success"]')
+        self.browser.wait_until_not('[data-test-id="toast-success"]')
 
 
     def wait_for_issue(self):
     def wait_for_issue(self):
         self.browser.wait_until('[data-test-id="group"]')
         self.browser.wait_until('[data-test-id="group"]')