Skip to content
Snippets Groups Projects
Commit d34c620f authored by Filipa Lacerda's avatar Filipa Lacerda
Browse files

[ci skip] Add issue data and notes data provided through haml to the store to...

[ci skip] Add issue data and notes data provided through haml to the store to stop querying the DOM everywhere
parent 487ed06f
No related branches found
No related tags found
No related merge requests found
Showing with 154 additions and 110 deletions
<script>
/* global Flash */
 
import { mapActions } from 'vuex';
import { mapActions, mapGetters } from 'vuex';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
Loading
Loading
@@ -30,6 +30,10 @@
issueNoteSignedOutWidget,
},
computed: {
...mapGetters([
'getNotesDataByProp',
'getIssueDataByProp',
]),
isLoggedIn() {
return window.gon.current_user_id;
},
Loading
Loading
@@ -57,8 +61,7 @@
};
},
canUpdateIssue() {
const { issueData } = window.gl;
return issueData && issueData.current_user.can_update;
return this.getIssueDataByProp(current_user).can_update;
},
},
methods: {
Loading
Loading
@@ -146,11 +149,8 @@
},
},
mounted() {
const issuableDataEl = document.getElementById('js-issuable-app-initial-data');
const issueData = JSON.parse(issuableDataEl.innerHTML.replace(/&quot;/g, '"'));
this.markdownDocsUrl = issueData.markdownDocs;
this.quickActionsDocsUrl = issueData.quickActionsDocs;
this.markdownDocsUrl = this.getIssueDataByProp(markdownDocs);
this.quickActionsDocsUrl = this.getIssueDataByProp(quickActionsDocs);
 
eventHub.$on('issueStateChanged', (isClosed) => {
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
Loading
Loading
Loading
Loading
@@ -5,7 +5,6 @@
import { mapGetters, mapActions, mapMutations } from 'vuex';
import store from '../stores/';
import * as constants from '../constants'
import * as types from '../stores/mutation_types';
import eventHub from '../event_hub';
import issueNote from './issue_note.vue';
import issueDiscussion from './issue_discussion.vue';
Loading
Loading
@@ -17,6 +16,16 @@
 
export default {
name: 'IssueNotes',
props: {
issueData: {
type: Object,
required: true,
},
notesData: {
type: Object,
required: true,
},
},
store,
data() {
return {
Loading
Loading
@@ -36,20 +45,19 @@
...mapGetters([
'notes',
'notesById',
'getNotesData',
'getNotesDataByProp',
'setLastFetchedAt',
'setTargetNoteHash',
]),
},
methods: {
...mapActions({
actionFetchNotes: 'fetchNotes',
}),
...mapActions([
'poll',
'toggleAward',
'scrollToNoteIfNeeded',
]),
...mapMutations({
setLastFetchedAt: types.SET_LAST_FETCHED_AT,
setTargetNoteHash: types.SET_TARGET_NOTE_HASH,
poll: 'poll',
toggleAward: 'toggleAward',
scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
setNotesData: 'setNotesData'
}),
getComponentName(note) {
if (note.isPlaceholderNote) {
Loading
Loading
@@ -67,9 +75,7 @@
return note.individual_note ? note.notes[0] : note;
},
fetchNotes() {
const { discussionsPath } = this.$el.parentNode.dataset;
this.actionFetchNotes(discussionsPath)
this.actionFetchNotes(his.getNotesDataByProp('discussionsPath'))
.then(() => {
this.isLoading = false;
 
Loading
Loading
@@ -78,23 +84,19 @@
this.checkLocationHash();
});
})
.catch(() => {
Flash('Something went wrong while fetching issue comments. Please try again.');
});
.catch(() => Flash('Something went wrong while fetching issue comments. Please try again.'));
},
initPolling() {
const { lastFetchedAt } = $('.js-notes-wrapper')[0].dataset;
this.setLastFetchedAt(lastFetchedAt);
this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt'));
 
// FIXME: @fatihacet Implement real polling mechanism
// TODO: FILIPA: DEAL WITH THIS
setInterval(() => {
this.poll()
.then((res) => {
this.setLastFetchedAt(res.lastFetchedAt);
})
.catch(() => {
Flash('Something went wrong while fetching latest comments.');
});
.catch(() => Flash('Something went wrong while fetching latest comments.'));
}, 15000);
},
bindEventHubListeners() {
Loading
Loading
@@ -106,6 +108,7 @@
.catch(() => Flash('Something went wrong on our end.'));
});
 
//TODO: FILIPA: REMOVE JQUERY
$(document).on('issuable:change', (e, isClosed) => {
eventHub.$emit('issueStateChanged', isClosed);
});
Loading
Loading
@@ -120,6 +123,10 @@
}
},
},
created() {
this.setNotesData(this.notesData);
this.setIssueData(this.issueData);
},
mounted() {
this.fetchNotes();
this.initPolling();
Loading
Loading
@@ -135,10 +142,12 @@
class="loading">
<loading-icon />
</div>
<ul
v-if="!isLoading"
id="notes-list"
class="notes main-notes-list timeline">
<component
v-for="note in notes"
:is="getComponentName(note)"
Loading
Loading
@@ -146,6 +155,7 @@
:key="note.id"
/>
</ul>
<issue-comment-form v-if="!isLoading" />
<issue-comment-form />
</div>
</template>
Loading
Loading
@@ -38,8 +38,9 @@
:class="{ target: isTargetNote }"
class="note system-note timeline-entry">
<div class="timeline-entry-inner">
<div class="timeline-icon">
<span v-html="svg"></span>
<div
class="timeline-icon"
v-html="svg">
</div>
<div class="timeline-content">
<div class="note-header">
Loading
Loading
Loading
Loading
@@ -6,3 +6,5 @@ export const COMMENT = 'comment';
export const OPENED = 'opened';
export const REOPENED = 'reopened';
export const CLOSED = 'closed';
export const EMOJI_THUMBSUP = 'thumbsup';
export const EMOJI_THUMBSDOWN = 'thumbsdown';
import Vue from 'vue';
import issueNotes from './components/issue_notes.vue';
import '../vue_shared/vue_resource_interceptor';
import issueNotesApp from './components/issue_notes_app.vue';
 
document.addEventListener('DOMContentLoaded', () => {
const vm = new Vue({
el: '#js-notes',
components: {
issueNotes,
},
render(createElement) {
return createElement('issue-notes', {
attrs: {
ref: 'notes',
},
});
},
});
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-notes',
components: {
issueNotesApp,
},
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
 
window.issueNotes = {
refresh() {
vm.$refs.notes.$store.dispatch('poll');
},
};
});
return {
issueData: JSON.parse(notesDataset.issueData),
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: {
lastFetchedAt: notesDataset.lastFetchedAt,
discussionsPath: notesDataset.discussionsPath,
},
};
},
render(createElement) {
return createElement('issue-notes-app', {
attrs: {
ref: 'notes',
},
props: {
issueData: this.issueData,
notesData: this.notesData,
},
});
},
}));
// // TODO: FILIPA: FIX THIS
// window.issueNotes = {
// refresh() {
// vm.$refs.notes.$store.dispatch('poll');
// },
// };
Loading
Loading
@@ -7,6 +7,13 @@ import service from '../services/issue_notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
 
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
export const setIssueData = ({ commit }, data) => commit(types.SET_ISSUE_DATA, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
export const fetchNotes = ({ commit }, path) => service
.fetchNotes(path)
.then(res => res.json())
Loading
Loading
@@ -20,43 +27,31 @@ export const deleteNote = ({ commit }, note) => service
commit(types.DELETE_NOTE, note);
});
 
export const updateNote = ({ commit }, data) => {
const { endpoint, note } = data;
return service
.updateNote(endpoint, note)
.then(res => res.json())
.then((res) => {
commit(types.UPDATE_NOTE, res);
});
};
export const replyToDiscussion = ({ commit }, note) => {
const { endpoint, data } = note;
return service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then((res) => {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
export const updateNote = ({ commit }, { endpoint, note }) => service
.updateNote(endpoint, note)
.then(res => res.json())
.then((res) => {
commit(types.UPDATE_NOTE, res);
});
 
return res;
});
};
export const replyToDiscussion = ({ commit }, { endpoint, data }) => service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then((res) => {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
 
export const createNewNote = ({ commit }, note) => {
const { endpoint, data } = note;
return res;
});
 
return service
.createNewNote(endpoint, data)
.then(res => res.json())
.then((res) => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
}
return res;
});
};
export const createNewNote = ({ commit }, { endpoint, data }) => service
.createNewNote(endpoint, data)
.then(res => res.json())
.then((res) => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
}
return res;
});
 
export const saveNote = ({ commit, dispatch }, noteData) => {
const { note } = noteData.data.note;
Loading
Loading
@@ -91,6 +86,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
 
if (hasQuickActions && Object.keys(errors).length) {
dispatch('poll');
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
Flash('Commands applied', 'notice', $(noteData.flashContainer));
}
Loading
Loading
@@ -136,9 +132,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
};
 
export const poll = ({ commit, state, getters }) => {
const { notesPath } = $('.js-notes-wrapper')[0].dataset;
return service.poll(`${notesPath}?full_data=1`, state.lastFetchedAt)
return service.poll(state.notesData.notesPath, state.lastFetchedAt)
.then(res => res.json())
.then((res) => {
if (res.notes.length) {
Loading
Loading
@@ -160,7 +154,6 @@ export const poll = ({ commit, state, getters }) => {
}
});
}
return res;
});
};
Loading
Loading
@@ -175,20 +168,24 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => {
.then(() => {
commit(types.TOGGLE_AWARD, { awardName, note });
 
if (!skipMutalityCheck && (awardName === 'thumbsup' || awardName === 'thumbsdown')) {
const counterAward = awardName === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
if (!skipMutalityCheck &&
(awardName === constants.EMOJI_THUMBSUP || awardName === constants.EMOJI_THUMBSDOWN)) {
const counterAward = awardName === constants.EMOJI_THUMBSUP ?
constants.EMOJI_THUMBSDOWN :
constants.EMOJI_THUMBSUP;
const targetNote = getters.notesById[noteId];
let amIAwarded = false;
let noteHasAward = false;
 
targetNote.award_emoji.forEach((a) => {
if (a.name === counterAward && a.user.id === window.gon.current_user_id) {
amIAwarded = true;
noteHasAward = true;
}
});
 
if (amIAwarded) {
data.awardName = counterAward;
data.skipMutalityCheck = true;
if (noteHasAward) {
Object.assign(data, { awardName: counterAward });
Object.assign(data, { kipMutalityCheck: true });
 
dispatch(types.TOGGLE_AWARD, data);
}
Loading
Loading
@@ -197,9 +194,7 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => {
};
 
export const scrollToNoteIfNeeded = (context, el) => {
const isInViewport = gl.utils.isInViewport(el[0]);
if (!isInViewport) {
if (!gl.utils.isInViewport(el[0])) {
gl.utils.scrollToElement(el);
}
};
export const notes = state => state.notes;
export const targetNoteHash = state => state.targetNoteHash;
export const getNotesDataByProp = state => prop => state.notesData[prop];
export const getIssueDataByProp = state => prop => state.notesData[prop];
export const getUserDataByProp = state => prop => state.notesData[prop];
 
export const notesById = (state) => {
const notesByIdObject = {};
// TODO: FILIPA: TRANSFORM INTO A REDUCE
state.notes.forEach((note) => {
note.notes.forEach((n) => {
notesByIdObject[n.id] = n;
Loading
Loading
Loading
Loading
@@ -11,6 +11,11 @@ export default new Vuex.Store({
notes: [],
targetNoteHash: null,
lastFetchedAt: null,
// holds endpoints and permissions provided through haml
notesData: {},
userData: {},
issueData: {},
},
actions,
getters,
Loading
Loading
Loading
Loading
@@ -2,6 +2,9 @@ export const ADD_NEW_NOTE = 'ADD_NEW_NOTE';
export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
export const DELETE_NOTE = 'DELETE_NOTE';
export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
export const SET_NOTES_DATA = 'SET_NOTES_DATA';
export const SET_ISSUE_DATA = 'SET_ISSUE_DATA';
export const SET_USER_DATA = 'SET_USER_DATA';
export const SET_INITAL_NOTES = 'SET_INITIAL_NOTES';
export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT';
export const SET_TARGET_NOTE_HASH = 'SET_TARGET_NOTE_HASH';
Loading
Loading
Loading
Loading
@@ -58,6 +58,18 @@ export default {
}
},
 
[types.SET_NOTES_DATA](state, data) {
state.notesData = data;
},
[types.SET_ISSUE_DATA](state, data) {
state.issueData = data;
},
[types.SET_USER_DATA](state, data) {
state.userData = data;
},
[types.SET_INITAL_NOTES](state, notes) {
state.notes = notes;
},
Loading
Loading
Loading
Loading
@@ -3,16 +3,16 @@
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
 
%section.js-notes-wrapper{ data: { discussions_path: discussions_namespace_project_issue_path(@project.namespace, @project, @issue, format: :json), new_session_path: new_session_path(:user, redirect_to_referer: 'yes'), notes_path: notes_url, last_fetched_at: Time.now.to_i } }
#js-notes
%section
#js-vue-notes{ data: { discussions_path: discussions_namespace_project_issue_path(@project.namespace, @project, @issue, format: :json),
new_session_path: new_session_path(:user, redirect_to_referer: 'yes'),
notes_path: '#{notes_url}?full_data=1', last_fetched_at: Time.now.to_i,
issue_data: serialize_issuable(@issue),
current_user_data: UserSerializer.new.represent(current_user).to_json }}
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'notes'
 
/ #notes{style: "margin-top: 150px"}
/ = render 'shared/notes/notes_with_form', :autocomplete => true
= render "layouts/init_auto_complete"
 
:javascript
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment