Browse Source

Merge pull request #5653 from getsentry/workflow/resolve-stream-actions

workflow: add full resolution options to stream
David Cramer 7 years ago
parent
commit
b1dc3780c9

+ 2 - 0
CHANGES

@@ -14,6 +14,8 @@ Version 8.18 (Unreleased)
 - Expanded React Form components (Form, ApiForm).
 - Moved "create team" into React.
 - add Slack to supported auth backends in social auth (for plugins)
+- Expanded resolution actions (on stream) to include current release and explicit
+  release.
 
 Schema Changes
 ~~~~~~~~~~~~~~

+ 4 - 6
src/sentry/static/sentry/app/views/groupDetails/actions.jsx

@@ -101,7 +101,7 @@ const ResolveActions = React.createClass({
             caret={true}
             className={resolveClassName}
             title="">
-            <MenuItem header={true}>Resolved In</MenuItem>
+            <MenuItem header={true}>{t('Resolved In')}</MenuItem>
             <MenuItem noAnchor={true}>
               <a
                 onClick={() => {
@@ -133,11 +133,9 @@ const ResolveActions = React.createClass({
                 }}
                 className={actionClassName}
                 title={actionTitle}>
-                {latestRelease ?
-                  t('The current release (%s)', getShortVersion(latestRelease.version))
-                :
-                  t('The current release')
-                }
+                {latestRelease
+                  ? t('The current release (%s)', getShortVersion(latestRelease.version))
+                  : t('The current release')}
               </a>
               <a
                 onClick={() => hasRelease && this.setState({modal: true})}

+ 1 - 0
src/sentry/static/sentry/app/views/stream.jsx

@@ -727,6 +727,7 @@ const Stream = React.createClass({
                     orgId={params.orgId}
                     projectId={params.projectId}
                     hasReleases={projectFeatures.has('releases')}
+                    latestRelease={this.context.project.latestRelease}
                     query={this.state.query}
                     onSelectStatsPeriod={this.onSelectStatsPeriod}
                     onRealtimeChange={this.onRealtimeChange}

+ 138 - 101
src/sentry/static/sentry/app/views/stream/actions.jsx

@@ -9,9 +9,11 @@ import MenuItem from '../../components/menuItem';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import SelectedGroupStore from '../../stores/selectedGroupStore';
 import {t, tn} from '../../locale';
+import {getShortVersion} from '../../utils';
 
 import CustomIgnoreCountModal from '../../components/customIgnoreCountModal';
 import CustomIgnoreDurationModal from '../../components/customIgnoreDurationModal';
+import CustomResolutionModal from '../../components/customResolutionModal';
 
 const IgnoreActions = React.createClass({
   propTypes: {
@@ -243,6 +245,125 @@ const IgnoreActions = React.createClass({
   }
 });
 
+const ResolveActions = React.createClass({
+  propTypes: {
+    orgId: React.PropTypes.string.isRequired,
+    projectId: React.PropTypes.string.isRequired,
+    hasRelease: React.PropTypes.bool.isRequired,
+    latestRelease: React.PropTypes.object,
+    anySelected: React.PropTypes.bool.isRequired,
+    allInQuerySelected: React.PropTypes.bool.isRequired,
+    pageSelected: React.PropTypes.bool.isRequired,
+    onUpdate: React.PropTypes.func.isRequired,
+    query: React.PropTypes.string
+  },
+
+  getInitialState() {
+    return {
+      modal: false
+    };
+  },
+
+  onCustomResolution(statusDetails) {
+    this.setState({
+      modal: false
+    });
+    this.props.onUpdate({
+      status: 'resolved',
+      statusDetails: statusDetails
+    });
+  },
+
+  render() {
+    let {hasRelease, latestRelease, projectId, orgId} = this.props;
+    let extraDescription = null;
+    if (this.state.allInQuerySelected) {
+      extraDescription = this.props.query
+        ? (<div>
+            <p>{t('This will apply to the current search query:')}</p>
+            <pre>{this.props.query}</pre>
+          </div>)
+        : (<p className="error">
+            <strong>{t('This will apply to ALL issues in this project!')}</strong>
+          </p>);
+    }
+    let linkClassName = 'group-resolve btn btn-default btn-sm';
+    let actionLinkProps = {
+      onlyIfBulk: true,
+      disabled: !this.props.anySelected,
+      selectAllActive: this.props.pageSelected,
+      extraDescription: extraDescription,
+      buttonTitle: t('Ignore'),
+      confirmationQuestion: this.state.allInQuerySelected
+        ? t('Are you sure you want to ignore all issues matching this search query?')
+        : count =>
+            tn(
+              'Are you sure you want to ignore this %d issue?',
+              'Are you sure you want to ignore these %d issues?',
+              count
+            ),
+      confirmLabel: this.props.allInQuerySelected
+        ? t('Ignore all issues')
+        : count => tn('Ignore %d selected issue', 'Ignore %d selected issues', count)
+    };
+    return (
+      <div style={{display: 'inline-block'}}>
+        <CustomResolutionModal
+          show={this.state.modal}
+          onSelected={this.onCustomResolution}
+          onCanceled={() => this.setState({modal: false})}
+          orgId={orgId}
+          projectId={projectId}
+        />
+        <div className="btn-group">
+          <ActionLink
+            onAction={() => this.props.onUpdate({status: 'resolved'})}
+            className={linkClassName}
+            {...actionLinkProps}>
+            <span className="icon-checkmark" style={{marginRight: 5}} />
+            {t('Resolve')}
+          </ActionLink>
+          <DropdownLink
+            key="resolve-dropdown"
+            caret={true}
+            className={linkClassName}
+            title=""
+            disabled={!this.props.anySelected}>
+            <MenuItem header={true}>{t('Resolved In')}</MenuItem>
+            <MenuItem noAnchor={true}>
+              <ActionLink
+                onAction={() =>
+                  this.props.onUpdate({
+                    status: 'resolved',
+                    statusDetails: {inNextRelease: true}
+                  })}
+                {...actionLinkProps}>
+                {t('The next release')}
+              </ActionLink>
+              <ActionLink
+                onAction={() =>
+                  this.props.onUpdate({
+                    status: 'resolved',
+                    statusDetails: {
+                      inRelease: latestRelease ? latestRelease.version : 'latest'
+                    }
+                  })}
+                {...actionLinkProps}>
+                {latestRelease
+                  ? t('The current release (%s)', getShortVersion(latestRelease.version))
+                  : t('The current release')}
+              </ActionLink>
+              <a onClick={() => hasRelease && this.setState({modal: true})}>
+                {t('Another version ...')}
+              </a>
+            </MenuItem>
+          </DropdownLink>
+        </div>
+      </div>
+    );
+  }
+});
+
 const StreamActions = React.createClass({
   propTypes: {
     allResultsVisible: React.PropTypes.bool,
@@ -254,7 +375,8 @@ const StreamActions = React.createClass({
     realtimeActive: React.PropTypes.bool.isRequired,
     statsPeriod: React.PropTypes.string.isRequired,
     query: React.PropTypes.string.isRequired,
-    hasReleases: React.PropTypes.bool
+    hasReleases: React.PropTypes.bool,
+    latestRelease: React.PropTypes.object
   },
 
   mixins: [
@@ -263,6 +385,10 @@ const StreamActions = React.createClass({
     PureRenderMixin
   ],
 
+  getDefaultProps() {
+    return {hasReleases: false, latestRelease: null};
+  },
+
   getInitialState() {
     return {
       datePickerActive: false,
@@ -385,8 +511,6 @@ const StreamActions = React.createClass({
   render() {
     // TODO(mitsuhiko): very unclear how to translate this
     let numIssues = SelectedGroupStore.getSelectedIds().size;
-    let hasRelease = this.props.hasReleases;
-    let releaseTrackingUrl = `/${this.props.orgId}/${this.props.projectId}/settings/release-tracking/`;
     let extraDescription = null;
     if (this.state.allInQuerySelected) {
       extraDescription = this.props.query
@@ -411,104 +535,17 @@ const StreamActions = React.createClass({
                 checked={this.state.pageSelected}
               />
             </div>
-            <div className="btn-group">
-              <ActionLink
-                className="btn btn-default btn-sm action-resolve"
-                disabled={!this.state.anySelected}
-                onAction={this.onUpdate.bind(this, {status: 'resolved'})}
-                buttonTitle={t('Resolve')}
-                extraDescription={extraDescription}
-                confirmationQuestion={
-                  this.state.allInQuerySelected
-                    ? t(
-                        'Are you sure you want to resolve all issues matching this search query?'
-                      )
-                    : count =>
-                        tn(
-                          'Are you sure you want to resolve this %d issue?',
-                          'Are you sure you want to resolve these %d issues?',
-                          count
-                        )
-                }
-                confirmLabel={
-                  this.state.allInQuerySelected
-                    ? t('Resolve all issues')
-                    : count =>
-                        tn(
-                          'Resolve %d selected issue',
-                          'Resolve %d selected issues',
-                          count
-                        )
-                }
-                tooltip={t('Set Status to Resolved')}
-                onlyIfBulk={true}
-                selectAllActive={this.state.pageSelected}>
-                <span className="icon-checkmark" style={{marginRight: 5}} />
-                {t('Resolve')}
-              </ActionLink>
-              <DropdownLink
-                caret={true}
-                className="btn btn-default btn-sm action-resolve"
-                topLevelClasses="resolve-dropdown"
-                disabled={!this.state.anySelected}
-                title="">
-                <MenuItem noAnchor={true}>
-                  {hasRelease
-                    ? <ActionLink
-                        disabled={!this.state.anySelected}
-                        onAction={this.onUpdate.bind(this, {
-                          status: 'resolvedInNextRelease'
-                        })}
-                        buttonTitle={t('Resolve')}
-                        tooltip=""
-                        extraDescription={extraDescription}
-                        confirmationQuestion={
-                          this.state.allInQuerySelected
-                            ? t(
-                                'Are you sure you want to resolve all issues matching this search query?'
-                              )
-                            : count =>
-                                tn(
-                                  'Are you sure you want to resolve this %d issue?',
-                                  'Are you sure you want to resolve these %d issues?',
-                                  count
-                                )
-                        }
-                        confirmLabel={
-                          this.state.allInQuerySelected
-                            ? t('Resolve all issues')
-                            : count =>
-                                tn(
-                                  'Resolve %d selected issue',
-                                  'Resolve %d selected issues',
-                                  count
-                                )
-                        }
-                        onlyIfBulk={true}
-                        selectAllActive={this.state.pageSelected}>
-                        <strong>{t('Resolved in next release')}</strong>
-                        <div className="help-text">
-                          {t(
-                            'Snooze notifications until this issue reoccurs in a future release.'
-                          )}
-                        </div>
-                      </ActionLink>
-                    : <a
-                        href={releaseTrackingUrl}
-                        className="disabled tip"
-                        title={t(
-                          'Set up release tracking in order to use this feature.'
-                        )}>
-                        <strong>{t('Resolved in next release.')}</strong>
-                        <div className="help-text">
-                          {t(
-                            'Snooze notifications until this issue reoccurs in a future release.'
-                          )}
-                        </div>
-                      </a>}
-                </MenuItem>
-              </DropdownLink>
-            </div>
+            <ResolveActions
+              hasRelease={this.props.hasReleases}
+              latestRelease={this.props.latestRelease}
+              anySelected={this.state.anySelected}
+              onUpdate={this.onUpdate}
+              allInQuerySelected={this.state.allInQuerySelected}
+              pageSelected={this.state.pageSelected}
+              query={this.props.query}
+              orgId={this.props.orgId}
+              projectId={this.props.projectId}
+            />
             <IgnoreActions
               anySelected={this.state.anySelected}
               onUpdate={this.onUpdate}