summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-07-27 21:24:05 +0100
committerFilipa Lacerda <filipa@gitlab.com>2017-07-27 21:25:02 +0100
commitd34c620fae23597e0130474fe20883f0718ded58 (patch)
treeda89ba9fc2dcbc731fb3fd1e98dc25ad063455f8
parent487ed06f444892abce47a6e21aaea79c9ca9c800 (diff)
downloadgitlab-ce-d34c620fae23597e0130474fe20883f0718ded58.tar.gz
[ci skip] Add issue data and notes data provided through haml to the store to stop querying the DOM everywhere
-rw-r--r--app/assets/javascripts/notes/components/issue_comment_form.vue16
-rw-r--r--app/assets/javascripts/notes/components/issue_notes_app.vue (renamed from app/assets/javascripts/notes/components/issue_notes.vue)54
-rw-r--r--app/assets/javascripts/notes/components/issue_system_note.vue5
-rw-r--r--app/assets/javascripts/notes/constants.js2
-rw-r--r--app/assets/javascripts/notes/index.js58
-rw-r--r--app/assets/javascripts/notes/stores/actions.js91
-rw-r--r--app/assets/javascripts/notes/stores/getters.js6
-rw-r--r--app/assets/javascripts/notes/stores/index.js5
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js12
-rw-r--r--app/views/projects/issues/_discussion.html.haml12
11 files changed, 154 insertions, 110 deletions
diff --git a/app/assets/javascripts/notes/components/issue_comment_form.vue b/app/assets/javascripts/notes/components/issue_comment_form.vue
index a8d8a22be76..b39dd2febe9 100644
--- a/app/assets/javascripts/notes/components/issue_comment_form.vue
+++ b/app/assets/javascripts/notes/components/issue_comment_form.vue
@@ -1,7 +1,7 @@
<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';
@@ -30,6 +30,10 @@
issueNoteSignedOutWidget,
},
computed: {
+ ...mapGetters([
+ 'getNotesDataByProp',
+ 'getIssueDataByProp',
+ ]),
isLoggedIn() {
return window.gon.current_user_id;
},
@@ -57,8 +61,7 @@
};
},
canUpdateIssue() {
- const { issueData } = window.gl;
- return issueData && issueData.current_user.can_update;
+ return this.getIssueDataByProp(current_user).can_update;
},
},
methods: {
@@ -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;
diff --git a/app/assets/javascripts/notes/components/issue_notes.vue b/app/assets/javascripts/notes/components/issue_notes_app.vue
index b1c8f495eb9..a3b6d928a51 100644
--- a/app/assets/javascripts/notes/components/issue_notes.vue
+++ b/app/assets/javascripts/notes/components/issue_notes_app.vue
@@ -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';
@@ -17,6 +16,16 @@
export default {
name: 'IssueNotes',
+ props: {
+ issueData: {
+ type: Object,
+ required: true,
+ },
+ notesData: {
+ type: Object,
+ required: true,
+ },
+ },
store,
data() {
return {
@@ -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) {
@@ -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;
@@ -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() {
@@ -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);
});
@@ -120,6 +123,10 @@
}
},
},
+ created() {
+ this.setNotesData(this.notesData);
+ this.setIssueData(this.issueData);
+ },
mounted() {
this.fetchNotes();
this.initPolling();
@@ -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)"
@@ -146,6 +155,7 @@
:key="note.id"
/>
</ul>
- <issue-comment-form v-if="!isLoading" />
+
+ <issue-comment-form />
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/issue_system_note.vue b/app/assets/javascripts/notes/components/issue_system_note.vue
index 91e1c62bba0..492ba58c6a6 100644
--- a/app/assets/javascripts/notes/components/issue_system_note.vue
+++ b/app/assets/javascripts/notes/components/issue_system_note.vue
@@ -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">
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 53fb03bab41..0ebde2dccb8 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -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';
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 914e08ff112..0bf52b551f4 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,25 +1,39 @@
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');
+ // },
+ // };
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 092049cb377..7f806fcd675 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -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())
@@ -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;
@@ -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));
}
@@ -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) {
@@ -160,7 +154,6 @@ export const poll = ({ commit, state, getters }) => {
}
});
}
-
return res;
});
};
@@ -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);
}
@@ -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);
}
};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index c3a9f0a5e89..2db3c3f02af 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -1,10 +1,12 @@
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;
diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js
index edca63fae67..8e0c8531bbc 100644
--- a/app/assets/javascripts/notes/stores/index.js
+++ b/app/assets/javascripts/notes/stores/index.js
@@ -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,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index f84f26684ca..4eccc2af56e 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -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';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index bb2ed91e570..4af8b0e6b9d 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -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;
},
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 90b97d36d2c..2b4fb6be327 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -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