summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
authorAndré Luís <me@andr3.net>2018-07-20 15:24:46 +0000
committerFilipa Lacerda <filipa@gitlab.com>2018-07-20 15:24:46 +0000
commitf2f8ddf4ccc43c86d8432a63b11aba8b216afd41 (patch)
tree649bc3f17a91f969b3d5b8439825e3b6a32e2bec /app/assets/javascripts
parent9b01b293ce5ddbaeedaf014cdc804af2c5e86416 (diff)
downloadgitlab-ce-f2f8ddf4ccc43c86d8432a63b11aba8b216afd41.tar.gz
Resolve ""Jump to first/next unresolved discussion" jumps to resolved discussions"
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue1
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue28
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue39
-rw-r--r--app/assets/javascripts/notes/mixins/discussion_navigation.js29
-rw-r--r--app/assets/javascripts/notes/stores/getters.js85
5 files changed, 142 insertions, 40 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index 20483161033..e64d5511d78 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -30,6 +30,7 @@ export default {
:render-header="false"
:render-diff-file="false"
:always-expanded="true"
+ :discussions-by-diff-order="true"
/>
</ul>
</div>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 6385b75e557..ad6e7cf501d 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -5,19 +5,20 @@ import resolvedSvg from 'icons/_icon_status_success_solid.svg';
import mrIssueSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionSvg from 'icons/_next_discussion.svg';
import { pluralize } from '../../lib/utils/text_utility';
-import { scrollToElement } from '../../lib/utils/common_utils';
+import discussionNavigation from '../mixins/discussion_navigation';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
+ mixins: [discussionNavigation],
computed: {
...mapGetters([
'getUserData',
'getNoteableData',
'discussionCount',
- 'unresolvedDiscussions',
+ 'firstUnresolvedDiscussionId',
'resolvedDiscussionCount',
]),
isLoggedIn() {
@@ -35,11 +36,6 @@ export default {
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
- firstUnresolvedDiscussionId() {
- const item = this.unresolvedDiscussions[0] || {};
-
- return item.id;
- },
},
created() {
this.resolveSvg = resolveSvg;
@@ -50,22 +46,10 @@ export default {
methods: {
...mapActions(['expandDiscussion']),
jumpToFirstUnresolvedDiscussion() {
- const discussionId = this.firstUnresolvedDiscussionId;
- if (!discussionId) {
- return;
- }
-
- const el = document.querySelector(`[data-discussion-id="${discussionId}"]`);
- const activeTab = window.mrTabs.currentAction;
-
- if (activeTab === 'commits' || activeTab === 'pipelines') {
- window.mrTabs.activateTab('show');
- }
+ const diffTab = window.mrTabs.currentAction === 'diffs';
+ const discussionId = this.firstUnresolvedDiscussionId(diffTab);
- if (el) {
- this.expandDiscussion({ discussionId });
- scrollToElement(el);
- }
+ this.jumpToDiscussion(discussionId);
},
},
};
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 2f1a68731c7..0fe1c16854a 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,9 +1,8 @@
<script>
-import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex';
import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionsSvg from 'icons/_next_discussion.svg';
-import { convertObjectPropsToCamelCase, scrollToElement } from '~/lib/utils/common_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
import { s__ } from '~/locale';
@@ -21,6 +20,7 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder
import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
+import discussionNavigation from '../mixins/discussion_navigation';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
@@ -40,7 +40,7 @@ export default {
directives: {
tooltip,
},
- mixins: [autosave, noteable, resolvable],
+ mixins: [autosave, noteable, resolvable, discussionNavigation],
props: {
discussion: {
type: Object,
@@ -61,6 +61,11 @@ export default {
required: false,
default: false,
},
+ discussionsByDiffOrder: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -75,7 +80,12 @@ export default {
'discussionCount',
'resolvedDiscussionCount',
'allDiscussions',
+ 'unresolvedDiscussionsIdsByDiff',
+ 'unresolvedDiscussionsIdsByDate',
'unresolvedDiscussions',
+ 'unresolvedDiscussionsIdsOrdered',
+ 'nextUnresolvedDiscussionId',
+ 'isLastUnresolvedDiscussion',
]),
transformedDiscussion() {
return {
@@ -126,6 +136,10 @@ export default {
hasMultipleUnresolvedDiscussions() {
return this.unresolvedDiscussions.length > 1;
},
+ showJumpToNextDiscussion() {
+ return this.hasMultipleUnresolvedDiscussions &&
+ !this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder);
+ },
shouldRenderDiffs() {
const { diffDiscussion, diffFile } = this.transformedDiscussion;
@@ -242,21 +256,10 @@ Please check your network connection and try again.`;
});
},
jumpToNextDiscussion() {
- const discussionIds = this.allDiscussions.map(d => d.id);
- const unresolvedIds = this.unresolvedDiscussions.map(d => d.id);
- const currentIndex = discussionIds.indexOf(this.discussion.id);
- const remainingAfterCurrent = discussionIds.slice(currentIndex + 1);
- const nextIndex = _.findIndex(remainingAfterCurrent, id => unresolvedIds.indexOf(id) > -1);
-
- if (nextIndex > -1) {
- const nextId = remainingAfterCurrent[nextIndex];
- const el = document.querySelector(`[data-discussion-id="${nextId}"]`);
+ const nextId =
+ this.nextUnresolvedDiscussionId(this.discussion.id, this.discussionsByDiffOrder);
- if (el) {
- this.expandDiscussion({ discussionId: nextId });
- scrollToElement(el);
- }
- }
+ this.jumpToDiscussion(nextId);
},
},
};
@@ -398,7 +401,7 @@ Please check your network connection and try again.`;
</a>
</div>
<div
- v-if="hasMultipleUnresolvedDiscussions"
+ v-if="showJumpToNextDiscussion"
class="btn-group"
role="group">
<button
diff --git a/app/assets/javascripts/notes/mixins/discussion_navigation.js b/app/assets/javascripts/notes/mixins/discussion_navigation.js
new file mode 100644
index 00000000000..f7c4deee1f8
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/discussion_navigation.js
@@ -0,0 +1,29 @@
+import { scrollToElement } from '~/lib/utils/common_utils';
+
+export default {
+ methods: {
+ jumpToDiscussion(id) {
+ if (id) {
+ const activeTab = window.mrTabs.currentAction;
+ const selector =
+ activeTab === 'diffs'
+ ? `ul.notes[data-discussion-id="${id}"]`
+ : `div.discussion[data-discussion-id="${id}"]`;
+ const el = document.querySelector(selector);
+
+ if (activeTab === 'commits' || activeTab === 'pipelines') {
+ window.mrTabs.activateTab('show');
+ }
+
+ if (el) {
+ this.expandDiscussion({ discussionId: id });
+
+ scrollToElement(el);
+ return true;
+ }
+ }
+
+ return false;
+ },
+ },
+};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index e9e95dd4219..0d8d197bf71 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -70,6 +70,9 @@ export const allDiscussions = (state, getters) => {
return Object.values(resolved).concat(unresolved);
};
+export const allResolvableDiscussions = (state, getters) =>
+ getters.allDiscussions.filter(d => !d.individual_note && d.resolvable);
+
export const resolvedDiscussionsById = state => {
const map = {};
@@ -86,6 +89,51 @@ export const resolvedDiscussionsById = state => {
return map;
};
+// Gets Discussions IDs ordered by the date of their initial note
+export const unresolvedDiscussionsIdsByDate = (state, getters) =>
+ getters.allResolvableDiscussions
+ .filter(d => !d.resolved)
+ .sort((a, b) => {
+ const aDate = new Date(a.notes[0].created_at);
+ const bDate = new Date(b.notes[0].created_at);
+
+ if (aDate < bDate) {
+ return -1;
+ }
+
+ return aDate === bDate ? 0 : 1;
+ })
+ .map(d => d.id);
+
+// Gets Discussions IDs ordered by their position in the diff
+//
+// Sorts the array of resolvable yet unresolved discussions by
+// comparing file names first. If file names are the same, compares
+// line numbers.
+export const unresolvedDiscussionsIdsByDiff = (state, getters) =>
+ getters.allResolvableDiscussions
+ .filter(d => !d.resolved)
+ .sort((a, b) => {
+ if (!a.diff_file || !b.diff_file) {
+ return 0;
+ }
+
+ // Get file names comparison result
+ const filenameComparison = a.diff_file.file_path.localeCompare(b.diff_file.file_path);
+
+ // Get the line numbers, to compare within the same file
+ const aLines = [a.position.formatter.new_line, a.position.formatter.old_line];
+ const bLines = [b.position.formatter.new_line, b.position.formatter.old_line];
+
+ return filenameComparison < 0 ||
+ (filenameComparison === 0 &&
+ // .max() because one of them might be zero (if removed/added)
+ Math.max(aLines[0], aLines[1]) < Math.max(bLines[0], bLines[1]))
+ ? -1
+ : 1;
+ })
+ .map(d => d.id);
+
export const resolvedDiscussionCount = (state, getters) => {
const resolvedMap = getters.resolvedDiscussionsById;
@@ -102,5 +150,42 @@ export const discussionTabCounter = state => {
return all.length;
};
+// Returns the list of discussion IDs ordered according to given parameter
+// @param {Boolean} diffOrder - is ordered by diff?
+export const unresolvedDiscussionsIdsOrdered = (state, getters) => diffOrder => {
+ if (diffOrder) {
+ return getters.unresolvedDiscussionsIdsByDiff;
+ }
+ return getters.unresolvedDiscussionsIdsByDate;
+};
+
+// Checks if a given discussion is the last in the current order (diff or date)
+// @param {Boolean} discussionId - id of the discussion
+// @param {Boolean} diffOrder - is ordered by diff?
+export const isLastUnresolvedDiscussion = (state, getters) => (discussionId, diffOrder) => {
+ const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
+ const lastDiscussionId = idsOrdered[idsOrdered.length - 1];
+
+ return lastDiscussionId === discussionId;
+};
+
+// Gets the ID of the discussion following the one provided, respecting order (diff or date)
+// @param {Boolean} discussionId - id of the current discussion
+// @param {Boolean} diffOrder - is ordered by diff?
+export const nextUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) => {
+ const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
+ const currentIndex = idsOrdered.indexOf(discussionId);
+
+ return idsOrdered.slice(currentIndex + 1, currentIndex + 2)[0];
+};
+
+// @param {Boolean} diffOrder - is ordered by diff?
+export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
+ if (diffOrder) {
+ return getters.unresolvedDiscussionsIdsByDiff[0];
+ }
+ return getters.unresolvedDiscussionsIdsByDate[0];
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};