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

stream: add new ignore actions

David Cramer 7 лет назад
Родитель
Сommit
56c82ee4bd

+ 2 - 0
CHANGES

@@ -7,6 +7,8 @@ Version 8.18 (Unreleased)
 - Removed support for global dsyms.
 - Moved Queue admin page to React.
 - Replaced usage of jQuery Flot library with internal graphs.
+- Expanded ignore actions to include deltas and rates for both occurances and
+  users imapcted.
 
 Schema Changes
 ~~~~~~~~~~~~~~

+ 9 - 2
src/sentry/static/sentry/app/components/customIgnoreDurationModal.jsx

@@ -7,7 +7,14 @@ export default React.createClass({
   propTypes: {
     onSelected: React.PropTypes.func,
     onCanceled: React.PropTypes.func,
-    show: React.PropTypes.bool
+    show: React.PropTypes.bool,
+    label: React.PropTypes.string
+  },
+
+  getDefaultProps() {
+    return {
+      label: t('Ignore this issue until it occurs after ..')
+    };
   },
 
   getInitialState() {
@@ -63,7 +70,7 @@ export default React.createClass({
     return (
       <Modal show={this.props.show} animation={false} onHide={this.props.onCanceled}>
         <div className="modal-header">
-          <h4>{t('Ignore this issue until it occurs after ..')}</h4>
+          <h4>{this.props.label}</h4>
         </div>
         <div className="modal-body">
           <form className="form-horizontal">

+ 245 - 37
src/sentry/static/sentry/app/views/stream/actions.jsx

@@ -3,12 +3,246 @@ import Reflux from 'reflux';
 import ApiMixin from '../../mixins/apiMixin';
 import ActionLink from './actionLink';
 import DropdownLink from '../../components/dropdownLink';
+import Duration from '../../components/duration';
 import IndicatorStore from '../../stores/indicatorStore';
 import MenuItem from '../../components/menuItem';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import SelectedGroupStore from '../../stores/selectedGroupStore';
 import {t, tn} from '../../locale';
 
+import CustomIgnoreCountModal from '../../components/customIgnoreCountModal';
+import CustomIgnoreDurationModal from '../../components/customIgnoreDurationModal';
+
+const IgnoreActions = React.createClass({
+  propTypes: {
+    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
+    };
+  },
+
+  getIgnoreDurations() {
+    return [30, 120, 360, 60 * 24, 60 * 24 * 7];
+  },
+
+  getIgnoreCounts() {
+    return [100, 1000, 10000, 100000];
+  },
+
+  getIgnoreWindows() {
+    return [[1, 'per hour'], [24, 'per day'], [24 * 7, 'per week']];
+  },
+
+  onCustomIgnore(statusDetails) {
+    this.setState({
+      modal: false
+    });
+    this.onIgnore(statusDetails);
+  },
+
+  onIgnore(statusDetails) {
+    return this.props.onUpdate({
+      status: 'ignored',
+      statusDetails: statusDetails || {}
+    });
+  },
+
+  render() {
+    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-ignore 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'}}>
+        <CustomIgnoreDurationModal
+          show={this.state.modal === 'duration'}
+          onSelected={this.onCustomIgnore}
+          onCanceled={() => this.setState({modal: null})}
+          label={t('Ignore the selected issue(s) until they occur after ..')}
+        />
+        <CustomIgnoreCountModal
+          show={this.state.modal === 'count'}
+          onSelected={this.onCustomIgnore}
+          onCanceled={() => this.setState({modal: null})}
+          label={t('Ignore the selected issue(s) until they occur again .. ')}
+          countLabel={t('Number of times')}
+          countName="ignoreCount"
+          windowName="ignoreWindow"
+          windowChoices={this.getIgnoreWindows()}
+        />
+        <CustomIgnoreCountModal
+          show={this.state.modal === 'users'}
+          onSelected={this.onCustomIgnore}
+          onCanceled={() => this.setState({modal: null})}
+          label={t('Ignore the selected issue(s) until they affect an additional .. ')}
+          countLabel={t('Numbers of users')}
+          countName="ignoreUserCount"
+          windowName="ignoreUserWindow"
+          windowChoices={this.getIgnoreWindows()}
+        />
+        <div className="btn-group">
+          <ActionLink
+            onAction={() => this.props.onUpdate({status: 'ignored'})}
+            className={linkClassName}
+            {...actionLinkProps}>
+            <span className="icon-ban" style={{marginRight: 5}} />
+            {t('Ignore')}
+          </ActionLink>
+          <DropdownLink
+            caret={true}
+            className={linkClassName}
+            title=""
+            disabled={!this.props.anySelected}>
+            <MenuItem header={true}>Ignore Until</MenuItem>
+            <li className="dropdown-submenu">
+              <DropdownLink title="This occurs again after .." caret={false}>
+                {this.getIgnoreDurations().map(duration => {
+                  return (
+                    <MenuItem noAnchor={true} key={duration}>
+                      <ActionLink
+                        onAction={this.onIgnore.bind(this, {
+                          ignoreDuration: duration
+                        })}
+                        {...actionLinkProps}>
+                        <Duration seconds={duration * 60} />
+                      </ActionLink>
+                    </MenuItem>
+                  );
+                })}
+                <MenuItem divider={true} />
+                <MenuItem noAnchor={true}>
+                  <a onClick={() => this.setState({modal: 'duration'})}>
+                    {t('Custom')}
+                  </a>
+                </MenuItem>
+              </DropdownLink>
+            </li>
+            <li className="dropdown-submenu">
+              <DropdownLink title="This occurs again .." caret={false}>
+                {this.getIgnoreCounts().map(count => {
+                  return (
+                    <li className="dropdown-submenu" key={count}>
+                      <DropdownLink
+                        title={t('%s times', count.toLocaleString())}
+                        caret={false}>
+                        <MenuItem noAnchor={true}>
+                          <ActionLink
+                            onAction={this.onIgnore.bind(this, {
+                              ignoreCount: count
+                            })}
+                            {...actionLinkProps}>
+                            {t('from now')}
+                          </ActionLink>
+                        </MenuItem>
+                        {this.getIgnoreWindows().map(([hours, label]) => {
+                          return (
+                            <MenuItem noAnchor={true} key={hours}>
+                              <ActionLink
+                                onAction={this.onIgnore.bind(this, {
+                                  ignoreCount: count,
+                                  ignoreWindow: hours
+                                })}
+                                {...actionLinkProps}>
+                                {label}
+                              </ActionLink>
+                            </MenuItem>
+                          );
+                        })}
+                      </DropdownLink>
+                    </li>
+                  );
+                })}
+                <MenuItem divider={true} />
+                <MenuItem noAnchor={true}>
+                  <a onClick={() => this.setState({modal: 'count'})}>
+                    {t('Custom')}
+                  </a>
+                </MenuItem>
+              </DropdownLink>
+            </li>
+            <li className="dropdown-submenu">
+              <DropdownLink title="This affects an additional .." caret={false}>
+                {this.getIgnoreCounts().map(count => {
+                  return (
+                    <li className="dropdown-submenu" key={count}>
+                      <DropdownLink
+                        title={t('%s users', count.toLocaleString())}
+                        caret={false}>
+                        <MenuItem noAnchor={true}>
+                          <ActionLink
+                            onAction={this.onIgnore.bind(this, {
+                              ignoreUserCount: count
+                            })}
+                            {...actionLinkProps}>
+                            {t('from now')}
+                          </ActionLink>
+                        </MenuItem>
+                        {this.getIgnoreWindows().map(([hours, label]) => {
+                          return (
+                            <MenuItem noAnchor={true} key={hours}>
+                              <ActionLink
+                                onAction={this.onIgnore.bind(this, {
+                                  ignoreUserCount: count,
+                                  ignoreUserWindow: hours
+                                })}
+                                {...actionLinkProps}>
+                                {label}
+                              </ActionLink>
+                            </MenuItem>
+                          );
+                        })}
+                      </DropdownLink>
+                    </li>
+                  );
+                })}
+                <MenuItem divider={true} />
+                <MenuItem noAnchor={true}>
+                  <a onClick={() => this.setState({modal: 'users'})}>
+                    {t('Custom')}
+                  </a>
+                </MenuItem>
+              </DropdownLink>
+            </li>
+          </DropdownLink>
+        </div>
+      </div>
+    );
+  }
+});
+
 const StreamActions = React.createClass({
   propTypes: {
     allResultsVisible: React.PropTypes.bool,
@@ -156,13 +390,13 @@ const StreamActions = React.createClass({
     let extraDescription = null;
     if (this.state.allInQuerySelected) {
       extraDescription = this.props.query
-        ? <div>
+        ? (<div>
             <p>{t('This will apply to the current search query:')}</p>
             <pre>{this.props.query}</pre>
-          </div>
-        : <p className="error">
+          </div>)
+        : (<p className="error">
             <strong>{t('This will apply to ALL issues in this project!')}</strong>
-          </p>;
+          </p>);
     }
 
     return (
@@ -275,6 +509,13 @@ const StreamActions = React.createClass({
                 </MenuItem>
               </DropdownLink>
             </div>
+            <IgnoreActions
+              anySelected={this.state.anySelected}
+              onUpdate={this.onUpdate}
+              allInQuerySelected={this.state.allInQuerySelected}
+              pageSelected={this.state.pageSelected}
+              query={this.props.query}
+            />
             <div className="btn-group">
               <ActionLink
                 className="btn btn-default btn-sm action-bookmark"
@@ -417,39 +658,6 @@ const StreamActions = React.createClass({
                     {t('Set status to: Unresolved')}
                   </ActionLink>
                 </MenuItem>
-                <MenuItem noAnchor={true}>
-                  <ActionLink
-                    className="action-ignore"
-                    disabled={!this.state.anySelected}
-                    onAction={this.onUpdate.bind(this, {status: 'ignored'})}
-                    extraDescription={extraDescription}
-                    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.state.allInQuerySelected
-                        ? t('Ignore all issues')
-                        : count =>
-                            tn(
-                              'Ignore %d selected issue',
-                              'Ignore %d selected issues',
-                              count
-                            )
-                    }
-                    onlyIfBulk={true}
-                    selectAllActive={this.state.pageSelected}>
-                    {t('Set status to: Ignored')}
-                  </ActionLink>
-                </MenuItem>
                 <MenuItem divider={true} />
                 <MenuItem noAnchor={true}>
                   <ActionLink