Skip to content

Commit 3a35bd7

Browse files
committed
workflow: add full resolution options to stream
1 parent 9f4ac84 commit 3a35bd7

File tree

4 files changed

+145
-107
lines changed

4 files changed

+145
-107
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Version 8.18 (Unreleased)
1414
- Expanded React Form components (Form, ApiForm).
1515
- Moved "create team" into React.
1616
- add Slack to supported auth backends in social auth (for plugins)
17+
- Expanded resolution actions (on stream) to include current release and explicit
18+
release.
1719

1820
Schema Changes
1921
~~~~~~~~~~~~~~

src/sentry/static/sentry/app/views/groupDetails/actions.jsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const ResolveActions = React.createClass({
101101
caret={true}
102102
className={resolveClassName}
103103
title="">
104-
<MenuItem header={true}>Resolved In</MenuItem>
104+
<MenuItem header={true}>{t('Resolved In')}</MenuItem>
105105
<MenuItem noAnchor={true}>
106106
<a
107107
onClick={() => {
@@ -133,11 +133,9 @@ const ResolveActions = React.createClass({
133133
}}
134134
className={actionClassName}
135135
title={actionTitle}>
136-
{latestRelease ?
137-
t('The current release (%s)', getShortVersion(latestRelease.version))
138-
:
139-
t('The current release')
140-
}
136+
{latestRelease
137+
? t('The current release (%s)', getShortVersion(latestRelease.version))
138+
: t('The current release')}
141139
</a>
142140
<a
143141
onClick={() => hasRelease && this.setState({modal: true})}

src/sentry/static/sentry/app/views/stream.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ const Stream = React.createClass({
727727
orgId={params.orgId}
728728
projectId={params.projectId}
729729
hasReleases={projectFeatures.has('releases')}
730+
latestRelease={this.context.project.latestRelease}
730731
query={this.state.query}
731732
onSelectStatsPeriod={this.onSelectStatsPeriod}
732733
onRealtimeChange={this.onRealtimeChange}

src/sentry/static/sentry/app/views/stream/actions.jsx

Lines changed: 138 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import MenuItem from '../../components/menuItem';
99
import PureRenderMixin from 'react-addons-pure-render-mixin';
1010
import SelectedGroupStore from '../../stores/selectedGroupStore';
1111
import {t, tn} from '../../locale';
12+
import {getShortVersion} from '../../utils';
1213

1314
import CustomIgnoreCountModal from '../../components/customIgnoreCountModal';
1415
import CustomIgnoreDurationModal from '../../components/customIgnoreDurationModal';
16+
import CustomResolutionModal from '../../components/customResolutionModal';
1517

1618
const IgnoreActions = React.createClass({
1719
propTypes: {
@@ -243,6 +245,125 @@ const IgnoreActions = React.createClass({
243245
}
244246
});
245247

248+
const ResolveActions = React.createClass({
249+
propTypes: {
250+
orgId: React.PropTypes.string.isRequired,
251+
projectId: React.PropTypes.string.isRequired,
252+
hasRelease: React.PropTypes.bool.isRequired,
253+
latestRelease: React.PropTypes.object,
254+
anySelected: React.PropTypes.bool.isRequired,
255+
allInQuerySelected: React.PropTypes.bool.isRequired,
256+
pageSelected: React.PropTypes.bool.isRequired,
257+
onUpdate: React.PropTypes.func.isRequired,
258+
query: React.PropTypes.string
259+
},
260+
261+
getInitialState() {
262+
return {
263+
modal: false
264+
};
265+
},
266+
267+
onCustomResolution(statusDetails) {
268+
this.setState({
269+
modal: false
270+
});
271+
this.props.onUpdate({
272+
status: 'resolved',
273+
statusDetails: statusDetails
274+
});
275+
},
276+
277+
render() {
278+
let {hasRelease, latestRelease, projectId, orgId} = this.props;
279+
let extraDescription = null;
280+
if (this.state.allInQuerySelected) {
281+
extraDescription = this.props.query
282+
? (<div>
283+
<p>{t('This will apply to the current search query:')}</p>
284+
<pre>{this.props.query}</pre>
285+
</div>)
286+
: (<p className="error">
287+
<strong>{t('This will apply to ALL issues in this project!')}</strong>
288+
</p>);
289+
}
290+
let linkClassName = 'group-resolve btn btn-default btn-sm';
291+
let actionLinkProps = {
292+
onlyIfBulk: true,
293+
disabled: !this.props.anySelected,
294+
selectAllActive: this.props.pageSelected,
295+
extraDescription: extraDescription,
296+
buttonTitle: t('Ignore'),
297+
confirmationQuestion: this.state.allInQuerySelected
298+
? t('Are you sure you want to ignore all issues matching this search query?')
299+
: count =>
300+
tn(
301+
'Are you sure you want to ignore this %d issue?',
302+
'Are you sure you want to ignore these %d issues?',
303+
count
304+
),
305+
confirmLabel: this.props.allInQuerySelected
306+
? t('Ignore all issues')
307+
: count => tn('Ignore %d selected issue', 'Ignore %d selected issues', count)
308+
};
309+
return (
310+
<div style={{display: 'inline-block'}}>
311+
<CustomResolutionModal
312+
show={this.state.modal}
313+
onSelected={this.onCustomResolution}
314+
onCanceled={() => this.setState({modal: false})}
315+
orgId={orgId}
316+
projectId={projectId}
317+
/>
318+
<div className="btn-group">
319+
<ActionLink
320+
onAction={() => this.props.onUpdate({status: 'resolved'})}
321+
className={linkClassName}
322+
{...actionLinkProps}>
323+
<span className="icon-checkmark" style={{marginRight: 5}} />
324+
{t('Resolve')}
325+
</ActionLink>
326+
<DropdownLink
327+
key="resolve-dropdown"
328+
caret={true}
329+
className={linkClassName}
330+
title=""
331+
disabled={!this.props.anySelected}>
332+
<MenuItem header={true}>{t('Resolved In')}</MenuItem>
333+
<MenuItem noAnchor={true}>
334+
<ActionLink
335+
onAction={() =>
336+
this.props.onUpdate({
337+
status: 'resolved',
338+
statusDetails: {inNextRelease: true}
339+
})}
340+
{...actionLinkProps}>
341+
{t('The next release')}
342+
</ActionLink>
343+
<ActionLink
344+
onAction={() =>
345+
this.props.onUpdate({
346+
status: 'resolved',
347+
statusDetails: {
348+
inRelease: latestRelease ? latestRelease.version : 'latest'
349+
}
350+
})}
351+
{...actionLinkProps}>
352+
{latestRelease
353+
? t('The current release (%s)', getShortVersion(latestRelease.version))
354+
: t('The current release')}
355+
</ActionLink>
356+
<a onClick={() => hasRelease && this.setState({modal: true})}>
357+
{t('Another version ...')}
358+
</a>
359+
</MenuItem>
360+
</DropdownLink>
361+
</div>
362+
</div>
363+
);
364+
}
365+
});
366+
246367
const StreamActions = React.createClass({
247368
propTypes: {
248369
allResultsVisible: React.PropTypes.bool,
@@ -254,7 +375,8 @@ const StreamActions = React.createClass({
254375
realtimeActive: React.PropTypes.bool.isRequired,
255376
statsPeriod: React.PropTypes.string.isRequired,
256377
query: React.PropTypes.string.isRequired,
257-
hasReleases: React.PropTypes.bool
378+
hasReleases: React.PropTypes.bool,
379+
latestRelease: React.PropTypes.object
258380
},
259381

260382
mixins: [
@@ -263,6 +385,10 @@ const StreamActions = React.createClass({
263385
PureRenderMixin
264386
],
265387

388+
getDefaultProps() {
389+
return {hasReleases: false, latestRelease: null};
390+
},
391+
266392
getInitialState() {
267393
return {
268394
datePickerActive: false,
@@ -385,8 +511,6 @@ const StreamActions = React.createClass({
385511
render() {
386512
// TODO(mitsuhiko): very unclear how to translate this
387513
let numIssues = SelectedGroupStore.getSelectedIds().size;
388-
let hasRelease = this.props.hasReleases;
389-
let releaseTrackingUrl = `/${this.props.orgId}/${this.props.projectId}/settings/release-tracking/`;
390514
let extraDescription = null;
391515
if (this.state.allInQuerySelected) {
392516
extraDescription = this.props.query
@@ -411,104 +535,17 @@ const StreamActions = React.createClass({
411535
checked={this.state.pageSelected}
412536
/>
413537
</div>
414-
<div className="btn-group">
415-
<ActionLink
416-
className="btn btn-default btn-sm action-resolve"
417-
disabled={!this.state.anySelected}
418-
onAction={this.onUpdate.bind(this, {status: 'resolved'})}
419-
buttonTitle={t('Resolve')}
420-
extraDescription={extraDescription}
421-
confirmationQuestion={
422-
this.state.allInQuerySelected
423-
? t(
424-
'Are you sure you want to resolve all issues matching this search query?'
425-
)
426-
: count =>
427-
tn(
428-
'Are you sure you want to resolve this %d issue?',
429-
'Are you sure you want to resolve these %d issues?',
430-
count
431-
)
432-
}
433-
confirmLabel={
434-
this.state.allInQuerySelected
435-
? t('Resolve all issues')
436-
: count =>
437-
tn(
438-
'Resolve %d selected issue',
439-
'Resolve %d selected issues',
440-
count
441-
)
442-
}
443-
tooltip={t('Set Status to Resolved')}
444-
onlyIfBulk={true}
445-
selectAllActive={this.state.pageSelected}>
446-
<span className="icon-checkmark" style={{marginRight: 5}} />
447-
{t('Resolve')}
448-
</ActionLink>
449-
<DropdownLink
450-
caret={true}
451-
className="btn btn-default btn-sm action-resolve"
452-
topLevelClasses="resolve-dropdown"
453-
disabled={!this.state.anySelected}
454-
title="">
455-
<MenuItem noAnchor={true}>
456-
{hasRelease
457-
? <ActionLink
458-
disabled={!this.state.anySelected}
459-
onAction={this.onUpdate.bind(this, {
460-
status: 'resolvedInNextRelease'
461-
})}
462-
buttonTitle={t('Resolve')}
463-
tooltip=""
464-
extraDescription={extraDescription}
465-
confirmationQuestion={
466-
this.state.allInQuerySelected
467-
? t(
468-
'Are you sure you want to resolve all issues matching this search query?'
469-
)
470-
: count =>
471-
tn(
472-
'Are you sure you want to resolve this %d issue?',
473-
'Are you sure you want to resolve these %d issues?',
474-
count
475-
)
476-
}
477-
confirmLabel={
478-
this.state.allInQuerySelected
479-
? t('Resolve all issues')
480-
: count =>
481-
tn(
482-
'Resolve %d selected issue',
483-
'Resolve %d selected issues',
484-
count
485-
)
486-
}
487-
onlyIfBulk={true}
488-
selectAllActive={this.state.pageSelected}>
489-
<strong>{t('Resolved in next release')}</strong>
490-
<div className="help-text">
491-
{t(
492-
'Snooze notifications until this issue reoccurs in a future release.'
493-
)}
494-
</div>
495-
</ActionLink>
496-
: <a
497-
href={releaseTrackingUrl}
498-
className="disabled tip"
499-
title={t(
500-
'Set up release tracking in order to use this feature.'
501-
)}>
502-
<strong>{t('Resolved in next release.')}</strong>
503-
<div className="help-text">
504-
{t(
505-
'Snooze notifications until this issue reoccurs in a future release.'
506-
)}
507-
</div>
508-
</a>}
509-
</MenuItem>
510-
</DropdownLink>
511-
</div>
538+
<ResolveActions
539+
hasRelease={this.props.hasReleases}
540+
latestRelease={this.props.latestRelease}
541+
anySelected={this.state.anySelected}
542+
onUpdate={this.onUpdate}
543+
allInQuerySelected={this.state.allInQuerySelected}
544+
pageSelected={this.state.pageSelected}
545+
query={this.props.query}
546+
orgId={this.props.orgId}
547+
projectId={this.props.projectId}
548+
/>
512549
<IgnoreActions
513550
anySelected={this.state.anySelected}
514551
onUpdate={this.onUpdate}

0 commit comments

Comments
 (0)