Skip to content

Commit b3daf10

Browse files
author
Filipa Lacerda
committed
Merge branch 'fix-duplicate-notes' into 'master'
Fixed issue notes being duplicated Closes #44099 See merge request gitlab-org/gitlab-ce!17671
2 parents cc72e1b + 63d3581 commit b3daf10

File tree

7 files changed

+94
-17
lines changed

7 files changed

+94
-17
lines changed

app/assets/javascripts/notes/services/notes_service.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ export default {
2727
return Vue.http[method](endpoint);
2828
},
2929
poll(data = {}) {
30-
const { endpoint, lastFetchedAt } = data;
30+
const endpoint = data.notesData.notesPath;
31+
const lastFetchedAt = data.lastFetchedAt;
3132
const options = {
3233
headers: {
33-
'X-Last-Fetched-At': lastFetchedAt,
34+
'X-Last-Fetched-At': lastFetchedAt ? `${lastFetchedAt}` : undefined,
3435
},
3536
};
3637

app/assets/javascripts/notes/stores/actions.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,18 +198,16 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
198198
});
199199
}
200200

201-
commit(types.SET_LAST_FETCHED_AT, resp.lastFetchedAt);
201+
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
202202

203203
return resp;
204204
};
205205

206206
export const poll = ({ commit, state, getters }) => {
207-
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
208-
209207
eTagPoll = new Poll({
210208
resource: service,
211209
method: 'poll',
212-
data: requestData,
210+
data: state,
213211
successCallback: resp => resp.json()
214212
.then(data => pollSuccessCallBack(data, commit, state, getters)),
215213
errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
@@ -218,7 +216,7 @@ export const poll = ({ commit, state, getters }) => {
218216
if (!Visibility.hidden()) {
219217
eTagPoll.makeRequest();
220218
} else {
221-
service.poll(requestData);
219+
service.poll(state);
222220
}
223221

224222
Visibility.change(() => {

app/assets/javascripts/notes/stores/mutations.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,21 @@ export default {
9090
const notes = [];
9191

9292
notesData.forEach((note) => {
93-
const nn = Object.assign({}, note);
94-
9593
// To support legacy notes, should be very rare case.
9694
if (note.individual_note && note.notes.length > 1) {
9795
note.notes.forEach((n) => {
98-
nn.notes = [n]; // override notes array to only have one item to mimick individual_note
99-
notes.push(nn);
96+
notes.push({
97+
...note,
98+
notes: [n], // override notes array to only have one item to mimick individual_note
99+
});
100100
});
101101
} else {
102102
const oldNote = utils.findNoteObjectById(state.notes, note.id);
103-
nn.expanded = oldNote ? oldNote.expanded : note.expanded;
104103

105-
notes.push(nn);
104+
notes.push({
105+
...note,
106+
expanded: (oldNote ? oldNote.expanded : note.expanded),
107+
});
106108
}
107109
});
108110

app/helpers/notes_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def notes_data(issuable)
169169
reopenPath: reopen_issuable_path(issuable),
170170
notesPath: notes_url,
171171
totalNotes: issuable.discussions.length,
172-
lastFetchedAt: Time.now
172+
lastFetchedAt: Time.now.to_i
173173

174174
}.to_json
175175
end

spec/javascripts/notes/mock_data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable */
22
export const notesDataMock = {
33
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
4-
lastFetchedAt: '1501862675',
4+
lastFetchedAt: 1501862675,
55
markdownDocsPath: '/help/user/markdown',
66
newSessionPath: '/users/sign_in?redirect_to_referer=yes',
77
notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',

spec/javascripts/notes/stores/actions_spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Vue from 'vue';
22
import _ from 'underscore';
3+
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
34
import * as actions from '~/notes/stores/actions';
45
import store from '~/notes/stores';
56
import testAction from '../../helpers/vuex_action_helper';
@@ -129,4 +130,68 @@ describe('Actions Notes Store', () => {
129130
], done);
130131
});
131132
});
133+
134+
describe('poll', () => {
135+
beforeEach((done) => {
136+
jasmine.clock().install();
137+
138+
spyOn(Vue.http, 'get').and.callThrough();
139+
140+
store.dispatch('setNotesData', notesDataMock)
141+
.then(done)
142+
.catch(done.fail);
143+
});
144+
145+
afterEach(() => {
146+
jasmine.clock().uninstall();
147+
});
148+
149+
it('calls service with last fetched state', (done) => {
150+
const interceptor = (request, next) => {
151+
next(request.respondWith(JSON.stringify({
152+
notes: [],
153+
last_fetched_at: '123456',
154+
}), {
155+
status: 200,
156+
headers: {
157+
'poll-interval': '1000',
158+
},
159+
}));
160+
};
161+
162+
Vue.http.interceptors.push(interceptor);
163+
Vue.http.interceptors.push(headersInterceptor);
164+
165+
store.dispatch('poll')
166+
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
167+
.then(() => {
168+
expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), {
169+
url: jasmine.anything(),
170+
method: 'get',
171+
headers: {
172+
'X-Last-Fetched-At': undefined,
173+
},
174+
});
175+
expect(store.state.lastFetchedAt).toBe('123456');
176+
177+
jasmine.clock().tick(1500);
178+
})
179+
.then(() => new Promise((resolve) => {
180+
requestAnimationFrame(resolve);
181+
}))
182+
.then(() => {
183+
expect(Vue.http.get.calls.count()).toBe(2);
184+
expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({
185+
'X-Last-Fetched-At': '123456',
186+
});
187+
})
188+
.then(() => store.dispatch('stopPolling'))
189+
.then(() => {
190+
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
191+
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
192+
})
193+
.then(done)
194+
.catch(done.fail);
195+
});
196+
});
132197
});

spec/javascripts/notes/stores/mutation_spec.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,21 @@ describe('Notes Store mutations', () => {
101101
const state = {
102102
notes: [],
103103
};
104+
const legacyNote = {
105+
id: 2,
106+
individual_note: true,
107+
notes: [{
108+
note: '1',
109+
}, {
110+
note: '2',
111+
}],
112+
};
104113

105-
mutations.SET_INITIAL_NOTES(state, [note]);
114+
mutations.SET_INITIAL_NOTES(state, [note, legacyNote]);
106115
expect(state.notes[0].id).toEqual(note.id);
107-
expect(state.notes.length).toEqual(1);
116+
expect(state.notes[1].notes[0].note).toBe(legacyNote.notes[0].note);
117+
expect(state.notes[2].notes[0].note).toBe(legacyNote.notes[1].note);
118+
expect(state.notes.length).toEqual(3);
108119
});
109120
});
110121

0 commit comments

Comments
 (0)