Skip to content

Commit 3b02506

Browse files
benvinegarmattrobenolt
authored andcommitted
Merge pull request #2636 from getsentry/GH-2603
Fix comment HTTP updates canceled via race condition (fixes #2603)
1 parent 9cea56b commit 3b02506

File tree

3 files changed

+106
-36
lines changed

3 files changed

+106
-36
lines changed

src/sentry/static/sentry/app/components/activity/noteContainer.jsx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import React from 'react';
2-
import ApiMixin from '../../mixins/apiMixin';
3-
import IndicatorStore from '../../stores/indicatorStore';
4-
import GroupStore from '../../stores/groupStore';
52

63
import Note from './note';
74
import NoteInput from './noteInput';
8-
import {t} from '../../locale';
95

106
const NoteContainer = React.createClass({
11-
mixins: [
12-
ApiMixin
13-
],
14-
157
getInitialState() {
168
return {
179
editing: false
@@ -27,27 +19,7 @@ const NoteContainer = React.createClass({
2719
},
2820

2921
onDelete() {
30-
let {group, item} = this.props;
31-
// Optimistically remove from UI
32-
let index = GroupStore.removeActivity(group.id, item.id);
33-
if (index === -1) {
34-
// I dunno, the id wasn't found in the GroupStore
35-
return;
36-
}
37-
38-
let loadingIndicator = IndicatorStore.add(t('Removing comment..'));
39-
40-
this.api.request('/issues/' + group.id + '/comments/' + item.id + '/' , {
41-
method: 'DELETE',
42-
error: (error) => {
43-
// TODO(mattrobenolt): Show an actual error that this failed,
44-
// but just bring it back in place for now
45-
GroupStore.addActivity(group.id, item, index);
46-
},
47-
complete: () => {
48-
IndicatorStore.remove(loadingIndicator);
49-
}
50-
});
22+
this.props.onDelete(this.props.item);
5123
},
5224

5325
render() {

src/sentry/static/sentry/app/views/groupActivity/index.jsx

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
import React from 'react';
22

3+
import ApiMixin from '../../mixins/apiMixin';
4+
import GroupState from '../../mixins/groupState';
5+
36
import Duration from '../../components/duration';
47
import Gravatar from '../../components/gravatar';
5-
import GroupState from '../../mixins/groupState';
6-
import MemberListStore from '../../stores/memberListStore';
78
import TimeSince from '../../components/timeSince';
8-
import ConfigStore from '../../stores/configStore';
99
import Version from '../../components/version';
10-
1110
import NoteContainer from '../../components/activity/noteContainer';
1211
import NoteInput from '../../components/activity/noteInput';
13-
import {t, tn} from '../../locale';
1412

13+
import ConfigStore from '../../stores/configStore';
14+
import GroupStore from '../../stores/groupStore';
15+
import IndicatorStore from '../../stores/indicatorStore';
16+
import MemberListStore from '../../stores/memberListStore';
17+
18+
import {t, tn} from '../../locale';
1519

1620
const GroupActivity = React.createClass({
1721
// TODO(dcramer): only re-render on group/activity change
1822

19-
mixins: [GroupState],
23+
mixins: [
24+
GroupState,
25+
ApiMixin
26+
],
2027

2128
formatActivity(author, item, params) {
2229
let data = item.data;
@@ -98,6 +105,31 @@ const GroupActivity = React.createClass({
98105
}
99106
},
100107

108+
onNoteDelete(item) {
109+
let {group} = this.props;
110+
111+
// Optimistically remove from UI
112+
let index = GroupStore.removeActivity(group.id, item.id);
113+
if (index === -1) {
114+
// I dunno, the id wasn't found in the GroupStore
115+
return;
116+
}
117+
118+
let loadingIndicator = IndicatorStore.add(t('Removing comment..'));
119+
120+
this.api.request('/issues/' + group.id + '/comments/' + item.id + '/' , {
121+
method: 'DELETE',
122+
error: (error) => {
123+
// TODO(mattrobenolt): Show an actual error that this failed,
124+
// but just bring it back in place for now
125+
GroupStore.addActivity(group.id, item, index);
126+
},
127+
complete: () => {
128+
IndicatorStore.remove(loadingIndicator);
129+
}
130+
});
131+
},
132+
101133
render() {
102134
let group = this.props.group;
103135
let me = ConfigStore.get('user');
@@ -114,7 +146,7 @@ const GroupActivity = React.createClass({
114146

115147
if (item.type === 'note') {
116148
return (
117-
<NoteContainer group={group} item={item} key={itemIdx} author={author} />
149+
<NoteContainer group={group} item={item} key={itemIdx} author={author} onDelete={this.onNoteDelete} />
118150
);
119151
} else {
120152
return (
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import {shallow} from 'enzyme';
3+
4+
import GroupActivity from 'app/views/groupActivity';
5+
import NoteInput from 'app/components/activity/noteInput';
6+
import ConfigStore from 'app/stores/configStore';
7+
import GroupStore from 'app/stores/groupStore';
8+
9+
describe('GroupActivity', function() {
10+
beforeEach(function() {
11+
this.sandbox = sinon.sandbox.create();
12+
13+
this.sandbox.stub(ConfigStore, 'get').withArgs('user').returns({});
14+
});
15+
16+
afterEach(function () {
17+
this.sandbox.restore();
18+
});
19+
20+
it('renders a NoteInput', function () {
21+
let wrapper = shallow(<GroupActivity group={{id: '1337', activity: []}}/>, {
22+
context: {
23+
group: {id: '1337'},
24+
project: {id: 'foo'},
25+
team: {id: '1'},
26+
organization: {id:'bar'}
27+
}
28+
});
29+
expect(wrapper.find(NoteInput)).to.have.length(1);
30+
});
31+
32+
describe('onNoteDelete()', function () {
33+
beforeEach(function () {
34+
this.instance = shallow(<GroupActivity group={{id: '1337', activity: []}}/>, {
35+
context: {
36+
group: {id: '1337'},
37+
project: {id: 'foo'},
38+
team: {id: '1'},
39+
organization: {id:'bar'}
40+
}
41+
}).instance();
42+
});
43+
44+
it('should do nothing if not present in GroupStore', function () {
45+
let instance = this.instance;
46+
47+
this.sandbox.stub(GroupStore, 'removeActivity').returns(-1); // not found
48+
let request = this.sandbox.stub(instance.api, 'request');
49+
50+
instance.onNoteDelete({id: 1});
51+
expect(request.calledOnce).to.not.be.ok;
52+
});
53+
54+
it('should remove remove the item from the GroupStore make a DELETE API request', function () {
55+
let instance = this.instance;
56+
57+
this.sandbox.stub(GroupStore, 'removeActivity').returns(1);
58+
59+
let request = this.sandbox.stub(instance.api, 'request');
60+
instance.onNoteDelete({id: 1});
61+
expect(request.calledOnce).to.be.ok;
62+
expect(request.getCall(0).args[0]).to.equal('/issues/1337/comments/1/');
63+
expect(request.getCall(0).args[1]).to.have.property('method', 'DELETE');
64+
});
65+
});
66+
});

0 commit comments

Comments
 (0)