Commit 76e772e7 authored by Valery Sizov's avatar Valery Sizov Committed by Alfredo Sumaran

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into ce_upstream

parents 0f455038 7df466f7
import Vue from 'vue'; import Vue from 'vue';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '../../pipelines/stores/pipelines_store';
import eventHub from '../../vue_pipelines_index/event_hub'; import eventHub from '../../pipelines/event_hub';
import EmptyState from '../../vue_pipelines_index/components/empty_state.vue'; import EmptyState from '../../pipelines/components/empty_state.vue';
import ErrorState from '../../vue_pipelines_index/components/error_state.vue'; import ErrorState from '../../pipelines/components/error_state.vue';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
......
...@@ -3,65 +3,63 @@ ...@@ -3,65 +3,63 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const CommentAndResolveBtn = Vue.extend({
const CommentAndResolveBtn = Vue.extend({ props: {
props: { discussionId: String,
discussionId: String, },
data() {
return {
textareaIsEmpty: true,
discussion: {},
};
},
computed: {
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
}, },
data() { isDiscussionResolved: function () {
return { return this.discussion.isResolved();
textareaIsEmpty: true,
discussion: {},
};
}, },
computed: { buttonText: function () {
showButton: function () { if (this.isDiscussionResolved) {
if (this.discussion) { if (this.textareaIsEmpty) {
return this.discussion.isResolvable(); return "Unresolve discussion";
} else { } else {
return false; return "Comment & unresolve discussion";
} }
}, } else {
isDiscussionResolved: function () { if (this.textareaIsEmpty) {
return this.discussion.isResolved(); return "Resolve discussion";
},
buttonText: function () {
if (this.isDiscussionResolved) {
if (this.textareaIsEmpty) {
return "Unresolve discussion";
} else {
return "Comment & unresolve discussion";
}
} else { } else {
if (this.textareaIsEmpty) { return "Comment & resolve discussion";
return "Resolve discussion";
} else {
return "Comment & resolve discussion";
}
} }
} }
}, }
created() { },
if (this.discussionId) { created() {
this.discussion = CommentsStore.state[this.discussionId]; if (this.discussionId) {
} this.discussion = CommentsStore.state[this.discussionId];
}, }
mounted: function () { },
if (!this.discussionId) return; mounted: function () {
if (!this.discussionId) return;
const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`); const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
$textarea.on('input.comment-and-resolve-btn', () => { $textarea.on('input.comment-and-resolve-btn', () => {
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
}); });
}, },
destroyed: function () { destroyed: function () {
if (!this.discussionId) return; if (!this.discussionId) return;
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
})(window);
...@@ -4,155 +4,153 @@ ...@@ -4,155 +4,153 @@
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
(() => { const DiffNoteAvatars = Vue.extend({
const DiffNoteAvatars = Vue.extend({ props: ['discussionId'],
props: ['discussionId'], data() {
data() { return {
return { isVisible: false,
isVisible: false, lineType: '',
lineType: '', storeState: CommentsStore.state,
storeState: CommentsStore.state, shownAvatars: 3,
shownAvatars: 3, collapseIcon,
collapseIcon, };
}; },
}, template: `
template: ` <div class="diff-comment-avatar-holders"
<div class="diff-comment-avatar-holders" v-show="notesCount !== 0">
v-show="notesCount !== 0"> <div v-if="!isVisible">
<div v-if="!isVisible"> <img v-for="note in notesSubset"
<img v-for="note in notesSubset" class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar" width="19"
width="19" height="19"
height="19" role="button"
role="button" data-container="body"
data-container="body" data-placement="top"
data-placement="top" data-html="true"
data-html="true" :data-line-type="lineType"
:data-line-type="lineType" :title="note.authorName + ': ' + note.noteTruncated"
:title="note.authorName + ': ' + note.noteTruncated" :src="note.authorAvatar"
:src="note.authorAvatar" @click="clickedAvatar($event)" />
@click="clickedAvatar($event)" /> <span v-if="notesCount > shownAvatars"
<span v-if="notesCount > shownAvatars" class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
class="diff-comments-more-count has-tooltip js-diff-comment-avatar" data-container="body"
data-container="body" data-placement="top"
data-placement="top" ref="extraComments"
ref="extraComments" role="button"
role="button"
:data-line-type="lineType"
:title="extraNotesTitle"
@click="clickedAvatar($event)">{{ moreText }}</span>
</div>
<button class="diff-notes-collapse js-diff-comment-avatar"
type="button"
aria-label="Show comments"
:data-line-type="lineType" :data-line-type="lineType"
@click="clickedAvatar($event)" :title="extraNotesTitle"
v-if="isVisible" @click="clickedAvatar($event)">{{ moreText }}</span>
v-html="collapseIcon">
</button>
</div> </div>
`, <button class="diff-notes-collapse js-diff-comment-avatar"
mounted() { type="button"
aria-label="Show comments"
:data-line-type="lineType"
@click="clickedAvatar($event)"
v-if="isVisible"
v-html="collapseIcon">
</button>
</div>
`,
mounted() {
this.$nextTick(() => {
this.addNoCommentClass();
this.setDiscussionVisible();
this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
});
$(document).on('toggle.comments', () => {
this.$nextTick(() => { this.$nextTick(() => {
this.addNoCommentClass();
this.setDiscussionVisible(); this.setDiscussionVisible();
this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
}); });
});
$(document).on('toggle.comments', () => { },
destroyed() {
$(document).off('toggle.comments');
},
watch: {
storeState: {
handler() {
this.$nextTick(() => { this.$nextTick(() => {
this.setDiscussionVisible(); $('.has-tooltip', this.$el).tooltip('fixTitle');
// We need to add/remove a class to an element that is outside the Vue instance
this.addNoCommentClass();
}); });
});
},
destroyed() {
$(document).off('toggle.comments');
},
watch: {
storeState: {
handler() {
this.$nextTick(() => {
$('.has-tooltip', this.$el).tooltip('fixTitle');
// We need to add/remove a class to an element that is outside the Vue instance
this.addNoCommentClass();
});
},
deep: true,
}, },
deep: true,
}, },
computed: { },
notesSubset() { computed: {
let notes = []; notesSubset() {
let notes = [];
if (this.discussion) {
notes = Object.keys(this.discussion.notes) if (this.discussion) {
.slice(0, this.shownAvatars) notes = Object.keys(this.discussion.notes)
.map(noteId => this.discussion.notes[noteId]); .slice(0, this.shownAvatars)
} .map(noteId => this.discussion.notes[noteId]);
}
return notes;
}, return notes;
extraNotesTitle() { },
if (this.discussion) { extraNotesTitle() {
const extra = this.discussion.notesCount() - this.shownAvatars; if (this.discussion) {
const extra = this.discussion.notesCount() - this.shownAvatars;
return `${extra} more comment${extra > 1 ? 's' : ''}`; return `${extra} more comment${extra > 1 ? 's' : ''}`;
} }
return ''; return '';
}, },
discussion() { discussion() {
return this.storeState[this.discussionId]; return this.storeState[this.discussionId];
}, },
notesCount() { notesCount() {
if (this.discussion) { if (this.discussion) {
return this.discussion.notesCount(); return this.discussion.notesCount();
} }
return 0; return 0;
}, },
moreText() { moreText() {
const plusSign = this.notesCount < 100 ? '+' : ''; const plusSign = this.notesCount < 100 ? '+' : '';
return `${plusSign}${this.notesCount - this.shownAvatars}`; return `${plusSign}${this.notesCount - this.shownAvatars}`;
},
}, },
methods: { },
clickedAvatar(e) { methods: {
notes.addDiffNote(e); clickedAvatar(e) {
notes.addDiffNote(e);
// Toggle the active state of the toggle all button // Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState(); this.toggleDiscussionsToggleState();
this.$nextTick(() => { this.$nextTick(() => {
this.setDiscussionVisible(); this.setDiscussionVisible();
$('.has-tooltip', this.$el).tooltip('fixTitle'); $('.has-tooltip', this.$el).tooltip('fixTitle');
$('.has-tooltip', this.$el).tooltip('hide'); $('.has-tooltip', this.$el).tooltip('hide');
}); });
}, },
addNoCommentClass() { addNoCommentClass() {
const notesCount = this.notesCount; const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container') $(this.$el).closest('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0) .toggleClass('js-no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container') .nextUntil('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0); .toggleClass('js-no-comment-btn', notesCount > 0);
}, },
toggleDiscussionsToggleState() { toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
const $visibleNotesHolders = $notesHolders.filter(':visible'); const $visibleNotesHolders = $notesHolders.filter(':visible');
const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments'); const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
$toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length); $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
}, },
setDiscussionVisible() { setDiscussionVisible() {
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
},
}, },
}); },
});
Vue.component('diff-note-avatars', DiffNoteAvatars); Vue.component('diff-note-avatars', DiffNoteAvatars);
})();
...@@ -2,29 +2,27 @@ ...@@ -2,29 +2,27 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const NewIssueForDiscussion = Vue.extend({
const NewIssueForDiscussion = Vue.extend({ props: {
props: { discussionId: {
discussionId: { type: String,
type: String, required: true,
required: true,
},
}, },
data() { },
return { data() {
discussions: CommentsStore.state, return {
}; discussions: CommentsStore.state,
};
},
computed: {
discussion() {
return this.discussions[this.discussionId];
}, },
computed: { showButton() {
discussion() { if (this.discussion) return !this.discussion.isResolved();
return this.discussions[this.discussionId]; return false;
},
showButton() {
if (this.discussion) return !this.discussion.isResolved();
return false;
},
}, },
}); },
});
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion); Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
...@@ -5,117 +5,115 @@ ...@@ -5,117 +5,115 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveBtn = Vue.extend({
const ResolveBtn = Vue.extend({ props: {
props: { noteId: Number,
noteId: Number, discussionId: String,
discussionId: String, resolved: Boolean,
resolved: Boolean, canResolve: Boolean,
canResolve: Boolean, resolvedBy: String,
resolvedBy: String, authorName: String,
authorName: String, authorAvatar: String,
authorAvatar: String, noteTruncated: String,
noteTruncated: String, },
data: function () {
return {
discussions: CommentsStore.state,
loading: false,
};
},
watch: {
'discussions': {
handler: 'updateTooltip',
deep: true
}
},
computed: {
discussion: function () {
return this.discussions[this.discussionId];
}, },
data: function () { note: function () {
return { return this.discussion ? this.discussion.getNote(this.noteId) : {};
discussions: CommentsStore.state,
loading: false
};
}, },
watch: { buttonText: function () {
'discussions': { if (this.isResolved) {
handler: 'updateTooltip', return `Resolved by ${this.resolvedByName}`;
deep: true } else if (this.canResolve) {
return 'Mark as resolved';
} else {
return 'Unable to resolve';
} }
}, },
computed: { isResolved: function () {
discussion: function () { if (this.note) {
return this.discussions[this.discussionId]; return this.note.resolved;
}, } else {
note: function () { return false;
return this.discussion ? this.discussion.getNote(this.noteId) : {}; }
}, },
buttonText: function () { resolvedByName: function () {
if (this.isResolved) { return this.note.resolved_by;
return `Resolved by ${this.resolvedByName}`;
} else if (this.canResolve) {
return 'Mark as resolved';
} else {
return 'Unable to resolve';
}
},
isResolved: function () {
if (this.note) {
return this.note.resolved;
} else {
return false;
}
},
resolvedByName: function () {
return this.note.resolved_by;
},
}, },
methods: { },
updateTooltip: function () { methods: {
this.$nextTick(() => { updateTooltip: function () {
$(this.$refs.button) this.$nextTick(() => {
.tooltip('hide') $(this.$refs.button)
.tooltip('fixTitle'); .tooltip('hide')
}); .tooltip('fixTitle');
}, });
resolve: function () { },
if (!this.canResolve) return; resolve: function () {
if (!this.canResolve) return;
let promise; let promise;
this.loading = true; this.loading = true;
if (this.isResolved) { if (this.isResolved) {
promise = ResolveService promise = ResolveService
.unresolve(this.noteId); .unresolve(this.noteId);
} else { } else {
promise = ResolveService promise = ResolveService
.resolve(this.noteId); .resolve(this.noteId);
} }
promise.then((response) => { promise.then((response) => {
this.loading = false; this.loading = false;
if (response.status === 200) { if (response.status === 200) {
const data = response.json(); const data = response.json();
const resolved_by = data ? data.resolved_by : null; const resolved_by = data ? data.resolved_by : null;
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
this.discussion.updateHeadline(data); this.discussion.updateHeadline(data);
} else { } else {
new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
} }
this.updateTooltip(); this.updateTooltip();
});
}
},
mounted: function () {
$(this.$refs.button).tooltip({
container: 'body'
});
},
beforeDestroy: function () {
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
CommentsStore.create({
discussionId: this.discussionId,
noteId: this.noteId,
canResolve: this.canResolve,
resolved: this.resolved,
resolvedBy: this.resolvedBy,
authorName: this.authorName,
authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated,
}); });
} }
}); },
mounted: function () {
$(this.$refs.button).tooltip({
container: 'body'
});
},
beforeDestroy: function () {
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
CommentsStore.create({
discussionId: this.discussionId,
noteId: this.noteId,
canResolve: this.canResolve,
resolved: this.resolved,
resolvedBy: this.resolvedBy,
authorName: this.authorName,
authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated,
});
}
});
Vue.component('resolve-btn', ResolveBtn); Vue.component('resolve-btn', ResolveBtn);
})();
...@@ -4,24 +4,22 @@ ...@@ -4,24 +4,22 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.ResolveCount = Vue.extend({
w.ResolveCount = Vue.extend({ mixins: [DiscussionMixins],
mixins: [DiscussionMixins], props: {
props: { loggedOut: Boolean
loggedOut: Boolean },
data: function () {
return {
discussions: CommentsStore.state
};
},
computed: {
allResolved: function () {
return this.resolvedDiscussionCount === this.discussionCount;
}, },
data: function () { resolvedCountText() {
return { return this.discussionCount === 1 ? 'discussion' : 'discussions';
discussions: CommentsStore.state
};
},
computed: {
allResolved: function () {
return this.resolvedDiscussionCount === this.discussionCount;
},
resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions';
}
} }
}); }
})(window); });
...@@ -4,59 +4,57 @@ ...@@ -4,59 +4,57 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveDiscussionBtn = Vue.extend({
const ResolveDiscussionBtn = Vue.extend({ props: {
props: { discussionId: String,
discussionId: String, mergeRequestId: Number,
mergeRequestId: Number, canResolve: Boolean,
canResolve: Boolean, },
}, data: function() {
data: function() { return {
return { discussion: {},
discussion: {}, };
}; },
computed: {
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
}, },
computed: { isDiscussionResolved: function () {
showButton: function () { if (this.discussion) {
if (this.discussion) { return this.discussion.isResolved();
return this.discussion.isResolvable(); } else {
} else { return false;
return false;
}
},
isDiscussionResolved: function () {
if (this.discussion) {
return this.discussion.isResolved();
} else {
return false;
}
},
buttonText: function () {
if (this.isDiscussionResolved) {
return "Unresolve discussion";
} else {
return "Resolve discussion";
}
},
loading: function () {
if (this.discussion) {
return this.discussion.loading;
} else {
return false;
}
} }
}, },
methods: { buttonText: function () {
resolve: function () { if (this.isDiscussionResolved) {
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId); return "Unresolve discussion";
} else {
return "Resolve discussion";
} }
}, },
created: function () { loading: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve); if (this.discussion) {
return this.discussion.loading;
this.discussion = CommentsStore.state[this.discussionId]; } else {
return false;
}
}
},
methods: {
resolve: function () {
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
} }
}); },
created: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
this.discussion = CommentsStore.state[this.discussionId];
}
});
Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
})();
/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */ /* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */
((w) => { window.DiscussionMixins = {
w.DiscussionMixins = { computed: {
computed: { discussionCount: function () {
discussionCount: function () { return Object.keys(this.discussions).length;
return Object.keys(this.discussions).length; },
}, resolvedDiscussionCount: function () {
resolvedDiscussionCount: function () { let resolvedCount = 0;
let resolvedCount = 0;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId]; const discussion = this.discussions[discussionId];
if (discussion.isResolved()) { if (discussion.isResolved()) {
resolvedCount += 1; resolvedCount += 1;
}
} }
}
return resolvedCount; return resolvedCount;
}, },
unresolvedDiscussionCount: function () { unresolvedDiscussionCount: function () {
let unresolvedCount = 0; let unresolvedCount = 0;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId]; const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) { if (!discussion.isResolved()) {
unresolvedCount += 1; unresolvedCount += 1;
}
} }
return unresolvedCount;
} }
return unresolvedCount;
} }
}; }
})(window); };
...@@ -9,76 +9,74 @@ require('../../vue_shared/vue_resource_interceptor'); ...@@ -9,76 +9,74 @@ require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource); Vue.use(VueResource);
(() => { window.gl = window.gl || {};
window.gl = window.gl || {};
class ResolveServiceClass { class ResolveServiceClass {
constructor(root) { constructor(root) {
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`); this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`); this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
} }
resolve(noteId) {
return this.noteResource.save({ noteId }, {});
}
unresolve(noteId) { resolve(noteId) {
return this.noteResource.delete({ noteId }, {}); return this.noteResource.save({ noteId }, {});
} }
toggleResolveForDiscussion(mergeRequestId, discussionId) { unresolve(noteId) {
const discussion = CommentsStore.state[discussionId]; return this.noteResource.delete({ noteId }, {});
const isResolved = discussion.isResolved(); }
let promise;
if (isResolved) { toggleResolveForDiscussion(mergeRequestId, discussionId) {
promise = this.unResolveAll(mergeRequestId, discussionId); const discussion = CommentsStore.state[discussionId];
} else { const isResolved = discussion.isResolved();
promise = this.resolveAll(mergeRequestId, discussionId); let promise;
}
promise.then((response) => { if (isResolved) {
discussion.loading = false; promise = this.unResolveAll(mergeRequestId, discussionId);
} else {
promise = this.resolveAll(mergeRequestId, discussionId);
}
if (response.status === 200) { promise.then((response) => {
const data = response.json(); discussion.loading = false;
const resolved_by = data ? data.resolved_by : null;
if (isResolved) { if (response.status === 200) {
discussion.unResolveAllNotes(); const data = response.json();
} else { const resolved_by = data ? data.resolved_by : null;
discussion.resolveAllNotes(resolved_by);
}
discussion.updateHeadline(data); if (isResolved) {
discussion.unResolveAllNotes();
} else { } else {
new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert'); discussion.resolveAllNotes(resolved_by);
} }
});
}
resolveAll(mergeRequestId, discussionId) { discussion.updateHeadline(data);
const discussion = CommentsStore.state[discussionId]; } else {
new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert');
}
});
}
discussion.loading = true; resolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
return this.discussionResource.save({ discussion.loading = true;
mergeRequestId,
discussionId return this.discussionResource.save({
}, {}); mergeRequestId,
} discussionId
}, {});
}
unResolveAll(mergeRequestId, discussionId) { unResolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
discussion.loading = true; discussion.loading = true;
return this.discussionResource.delete({ return this.discussionResource.delete({
mergeRequestId, mergeRequestId,
discussionId discussionId
}, {}); }, {});
}
} }
}
gl.DiffNotesResolveServiceClass = ResolveServiceClass; gl.DiffNotesResolveServiceClass = ResolveServiceClass;
})();
...@@ -3,56 +3,54 @@ ...@@ -3,56 +3,54 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.CommentsStore = {
w.CommentsStore = { state: {},
state: {}, get: function (discussionId, noteId) {
get: function (discussionId, noteId) { return this.state[discussionId].getNote(noteId);
return this.state[discussionId].getNote(noteId); },
}, createDiscussion: function (discussionId, canResolve) {
createDiscussion: function (discussionId, canResolve) { let discussion = this.state[discussionId];
let discussion = this.state[discussionId]; if (!this.state[discussionId]) {
if (!this.state[discussionId]) { discussion = new DiscussionModel(discussionId);
discussion = new DiscussionModel(discussionId); Vue.set(this.state, discussionId, discussion);
Vue.set(this.state, discussionId, discussion); }
}
if (canResolve !== undefined) { if (canResolve !== undefined) {
discussion.canResolve = canResolve; discussion.canResolve = canResolve;
} }
return discussion; return discussion;
}, },
create: function (noteObj) { create: function (noteObj) {
const discussion = this.createDiscussion(noteObj.discussionId); const discussion = this.createDiscussion(noteObj.discussionId);
discussion.createNote(noteObj);
},
update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
const note = discussion.getNote(noteId);
note.resolved = resolved;
note.resolved_by = resolved_by;
},
delete: function (discussionId, noteId) {
const discussion = this.state[discussionId];
discussion.deleteNote(noteId);
if (discussion.notesCount() === 0) {
Vue.delete(this.state, discussionId);
}
},
unresolvedDiscussionIds: function () {
const ids = [];
discussion.createNote(noteObj); for (const discussionId in this.state) {
},
update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
const note = discussion.getNote(noteId);
note.resolved = resolved;
note.resolved_by = resolved_by;
},
delete: function (discussionId, noteId) {
const discussion = this.state[discussionId]; const discussion = this.state[discussionId];
discussion.deleteNote(noteId);
if (discussion.notesCount() === 0) { if (!discussion.isResolved()) {
Vue.delete(this.state, discussionId); ids.push(discussion.id);
} }
},
unresolvedDiscussionIds: function () {
const ids = [];
for (const discussionId in this.state) {
const discussion = this.state[discussionId];
if (!discussion.isResolved()) {
ids.push(discussion.id);
}
}
return ids;
} }
};
})(window); return ids;
}
};
...@@ -130,13 +130,17 @@ window.DropzoneInput = (function() { ...@@ -130,13 +130,17 @@ window.DropzoneInput = (function() {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text; var formattedText = text;
if (shouldPad) formattedText += "\n\n"; if (shouldPad) formattedText += "\n\n";
caretStart = $(child)[0].selectionStart; const textarea = child.get(0);
caretEnd = $(child)[0].selectionEnd; caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
textEnd = $(child).val().length; textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart); beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd); afterSelection = $(child).val().substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection); $(child).val(beforeSelection + formattedText + afterSelection);
child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
return form_textarea.trigger("input"); return form_textarea.trigger("input");
}; };
getFilename = function(e) { getFilename = function(e) {
...@@ -180,7 +184,7 @@ window.DropzoneInput = (function() { ...@@ -180,7 +184,7 @@ window.DropzoneInput = (function() {
}; };
insertToTextArea = function(filename, url) { insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) { return $(child).val(function(index, val) {
return val.replace("{{" + filename + "}}", url + "\n"); return val.replace("{{" + filename + "}}", url);
}); });
}; };
appendToTextArea = function(url) { appendToTextArea = function(url) {
...@@ -215,6 +219,7 @@ window.DropzoneInput = (function() { ...@@ -215,6 +219,7 @@ window.DropzoneInput = (function() {
form.find(".markdown-selector").click(function(e) { form.find(".markdown-selector").click(function(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click(); $(this).closest('.gfm-form').find('.div-dropzone').click();
form_textarea.focus();
}); });
} }
......
...@@ -5,7 +5,6 @@ export default { ...@@ -5,7 +5,6 @@ export default {
avatarUrl: { avatarUrl: {
type: String, type: String,
required: false, required: false,
default: '/assets/no_avatar.png',
}, },
profileUrl: { profileUrl: {
type: String, type: String,
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button.vue'; import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../vue_pipelines_index/components/pipelines_actions'; import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../vue_pipelines_index/components/pipelines_artifacts'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
import PipelinesStatusComponent from '../../vue_pipelines_index/components/status'; import PipelinesStatusComponent from '../../pipelines/components/status';
import PipelinesStageComponent from '../../vue_pipelines_index/components/stage'; import PipelinesStageComponent from '../../pipelines/components/stage';
import PipelinesUrlComponent from '../../vue_pipelines_index/components/pipeline_url'; import PipelinesUrlComponent from '../../pipelines/components/pipeline_url';
import PipelinesTimeagoComponent from '../../vue_pipelines_index/components/time_ago'; import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
import CommitComponent from './commit'; import CommitComponent from './commit';
/** /**
......
...@@ -9,38 +9,32 @@ module Search ...@@ -9,38 +9,32 @@ module Search
end end
def execute def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new(current_user: current_user).execute
if group
projects = projects.inside_path(group.full_path)
end
if current_application_settings.elasticsearch_search? if current_application_settings.elasticsearch_search?
projects_spec = Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global)
if group
projects.pluck(:id)
else
if current_user && current_user.admin_or_auditor?
:any
elsif current_user
current_user.authorized_projects.pluck(:id)
else
[]
end
end
Gitlab::Elastic::SearchResults.new(
current_user,
params[:search],
projects_spec,
!group # Ignore public projects outside of the group if provided
)
else else
Gitlab::SearchResults.new(current_user, projects, params[:search]) Gitlab::SearchResults.new(current_user, projects, params[:search])
end end
end end
def projects
@projects ||= ProjectsFinder.new(current_user: current_user).execute
end
def elastic_projects
@elastic_projects ||=
if current_user.try(:admin_or_auditor?)
:any
elsif current_user
current_user.authorized_projects.pluck(:id)
else
[]
end
end
def elastic_global
true
end
def scope def scope
@scope ||= begin @scope ||= begin
allowed_scopes = %w[issues merge_requests milestones] allowed_scopes = %w[issues merge_requests milestones]
......
module Search
class GroupService < Search::GlobalService
attr_accessor :group
def initialize(user, group, params)
super(user, params)
@group = group
end
def projects
return Project.none unless group
return @projects if defined? @projects
@projects = super.inside_path(group.full_path)
end
def elastic_projects
@elastic_projects ||= projects.pluck(:id)
end
def elastic_global
false
end
end
end
...@@ -54,6 +54,8 @@ class SearchService ...@@ -54,6 +54,8 @@ class SearchService
Search::ProjectService.new(project, current_user, params) Search::ProjectService.new(project, current_user, params)
elsif show_snippets? elsif show_snippets?
Search::SnippetService.new(current_user, params) Search::SnippetService.new(current_user, params)
elsif group
Search::GroupService.new(current_user, group, params)
else else
Search::GlobalService.new(current_user, params) Search::GlobalService.new(current_user, params)
end end
......
...@@ -21,4 +21,4 @@ ...@@ -21,4 +21,4 @@
"ci-lint-path" => ci_lint_path } } "ci-lint-path" => ci_lint_path } }
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('vue_pipelines') = page_specific_javascript_bundle_tag('pipelines')
---
title: refocus textarea after attaching a file
merge_request:
author:
...@@ -19,13 +19,12 @@ var WEBPACK_REPORT = process.env.WEBPACK_REPORT; ...@@ -19,13 +19,12 @@ var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var config = { var config = {
context: path.join(ROOT_PATH, 'app/assets/javascripts'), context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: { entry: {
common: './commons/index.js',
common_vue: ['vue', './vue_shared/common_vue.js'],
common_d3: ['d3'],
main: './main.js',
blob: './blob_edit/blob_bundle.js', blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js', boards: './boards/boards_bundle.js',
burndown_chart: './burndown_chart/index.js', burndown_chart: './burndown_chart/index.js',
common: './commons/index.js',
common_vue: ['vue', './vue_shared/common_vue.js'],
common_d3: ['d3'],
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js', cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js', commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js', diff_notes: './diff_notes/diff_notes_bundle.js',
...@@ -33,28 +32,29 @@ var config = { ...@@ -33,28 +32,29 @@ var config = {
environments_folder: './environments/folder/environments_folder_bundle.js', environments_folder: './environments/folder/environments_folder_bundle.js',
filtered_search: './filtered_search/filtered_search_bundle.js', filtered_search: './filtered_search/filtered_search_bundle.js',
graphs: './graphs/graphs_bundle.js', graphs: './graphs/graphs_bundle.js',
group: './group.js',
groups_list: './groups_list.js', groups_list: './groups_list.js',
issues: './issues/issues_bundle.js', issues: './issues/issues_bundle.js',
issuable: './issuable/issuable_bundle.js', issuable: './issuable/issuable_bundle.js',
issue_show: './issue_show/index.js',
main: './main.js',
merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js', merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js',
merge_request_widget: './merge_request_widget/ci_bundle.js', merge_request_widget: './merge_request_widget/ci_bundle.js',
mr_widget_ee: './merge_request_widget/widget_bundle.js', mr_widget_ee: './merge_request_widget/widget_bundle.js',
monitoring: './monitoring/monitoring_bundle.js', monitoring: './monitoring/monitoring_bundle.js',
network: './network/network_bundle.js', network: './network/network_bundle.js',
notebook_viewer: './blob/notebook_viewer.js', notebook_viewer: './blob/notebook_viewer.js',
sketch_viewer: './blob/sketch_viewer.js',
pdf_viewer: './blob/pdf_viewer.js', pdf_viewer: './blob/pdf_viewer.js',
pipelines: './pipelines/index.js',
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js',
protected_tags: './protected_tags', protected_tags: './protected_tags',
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js', stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js', terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'], u2f: ['vendor/u2f'],
users: './users/users_bundle.js', users: './users/users_bundle.js',
vue_pipelines: './vue_pipelines_index/index.js',
issue_show: './issue_show/index.js',
group: './group.js',
}, },
output: { output: {
...@@ -124,10 +124,11 @@ var config = { ...@@ -124,10 +124,11 @@ var config = {
'environments', 'environments',
'environments_folder', 'environments_folder',
'issuable', 'issuable',
'issue_show',
'merge_conflicts', 'merge_conflicts',
'notebook_viewer', 'notebook_viewer',
'pdf_viewer', 'pdf_viewer',
'vue_pipelines', 'pipelines',
'mr_widget_ee', 'mr_widget_ee',
'issue_show' 'issue_show'
], ],
......
...@@ -5,129 +5,127 @@ require('~/diff_notes/models/discussion'); ...@@ -5,129 +5,127 @@ require('~/diff_notes/models/discussion');
require('~/diff_notes/models/note'); require('~/diff_notes/models/note');
require('~/diff_notes/stores/comments'); require('~/diff_notes/stores/comments');
(() => { function createDiscussion(noteId = 1, resolved = true) {
function createDiscussion(noteId = 1, resolved = true) { CommentsStore.create({
CommentsStore.create({ discussionId: 'a',
discussionId: 'a', noteId,
noteId, canResolve: true,
canResolve: true, resolved,
resolved, resolvedBy: 'test',
resolvedBy: 'test', authorName: 'test',
authorName: 'test', authorAvatar: 'test',
authorAvatar: 'test', noteTruncated: 'test...',
noteTruncated: 'test...',
});
}
beforeEach(() => {
CommentsStore.state = {};
}); });
}
describe('New discussion', () => { beforeEach(() => {
it('creates new discussion', () => { CommentsStore.state = {};
expect(Object.keys(CommentsStore.state).length).toBe(0); });
createDiscussion();
expect(Object.keys(CommentsStore.state).length).toBe(1);
});
it('creates new note in discussion', () => { describe('New discussion', () => {
createDiscussion(); it('creates new discussion', () => {
createDiscussion(2); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion();
expect(Object.keys(CommentsStore.state).length).toBe(1);
});
const discussion = CommentsStore.state['a']; it('creates new note in discussion', () => {
expect(Object.keys(discussion.notes).length).toBe(2); createDiscussion();
}); createDiscussion(2);
const discussion = CommentsStore.state['a'];
expect(Object.keys(discussion.notes).length).toBe(2);
}); });
});
describe('Get note', () => { describe('Get note', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('gets note by ID', () => { it('gets note by ID', () => {
const note = CommentsStore.get('a', 1); const note = CommentsStore.get('a', 1);
expect(note).toBeDefined(); expect(note).toBeDefined();
expect(note.id).toBe(1); expect(note.id).toBe(1);
});
}); });
});
describe('Delete discussion', () => { describe('Delete discussion', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('deletes discussion by ID', () => { it('deletes discussion by ID', () => {
CommentsStore.delete('a', 1); CommentsStore.delete('a', 1);
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
}); });
it('deletes discussion when no more notes', () => { it('deletes discussion when no more notes', () => {
createDiscussion(); createDiscussion();
createDiscussion(2); createDiscussion(2);
expect(Object.keys(CommentsStore.state).length).toBe(1); expect(Object.keys(CommentsStore.state).length).toBe(1);
expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2); expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2);
CommentsStore.delete('a', 1); CommentsStore.delete('a', 1);
CommentsStore.delete('a', 2); CommentsStore.delete('a', 2);
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
});
}); });
});
describe('Update note', () => { describe('Update note', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('updates note to be unresolved', () => { it('updates note to be unresolved', () => {
CommentsStore.update('a', 1, false, 'test'); CommentsStore.update('a', 1, false, 'test');
const note = CommentsStore.get('a', 1); const note = CommentsStore.get('a', 1);
expect(note.resolved).toBe(false); expect(note.resolved).toBe(false);
});
}); });
});
describe('Discussion resolved', () => { describe('Discussion resolved', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('is resolved with single note', () => { it('is resolved with single note', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
expect(discussion.isResolved()).toBe(true); expect(discussion.isResolved()).toBe(true);
}); });
it('is unresolved with 2 notes', () => { it('is unresolved with 2 notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2, false); createDiscussion(2, false);
expect(discussion.isResolved()).toBe(false); expect(discussion.isResolved()).toBe(false);
}); });
it('is resolved with 2 notes', () => { it('is resolved with 2 notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2); createDiscussion(2);
expect(discussion.isResolved()).toBe(true); expect(discussion.isResolved()).toBe(true);
}); });
it('resolve all notes', () => { it('resolve all notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2, false); createDiscussion(2, false);
discussion.resolveAllNotes(); discussion.resolveAllNotes();
expect(discussion.isResolved()).toBe(true); expect(discussion.isResolved()).toBe(true);
}); });
it('unresolve all notes', () => { it('unresolve all notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2); createDiscussion(2);
discussion.unResolveAllNotes(); discussion.unResolveAllNotes();
expect(discussion.isResolved()).toBe(false); expect(discussion.isResolved()).toBe(false);
});
}); });
})(); });
import Vue from 'vue'; import Vue from 'vue';
import asyncButtonComp from '~/vue_pipelines_index/components/async_button.vue'; import asyncButtonComp from '~/pipelines/components/async_button.vue';
describe('Pipelines Async Button', () => { describe('Pipelines Async Button', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import emptyStateComp from '~/vue_pipelines_index/components/empty_state.vue'; import emptyStateComp from '~/pipelines/components/empty_state.vue';
describe('Pipelines Empty State', () => { describe('Pipelines Empty State', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import errorStateComp from '~/vue_pipelines_index/components/error_state.vue'; import errorStateComp from '~/pipelines/components/error_state.vue';
describe('Pipelines Error State', () => { describe('Pipelines Error State', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import navControlsComp from '~/vue_pipelines_index/components/nav_controls'; import navControlsComp from '~/pipelines/components/nav_controls';
describe('Pipelines Nav Controls', () => { describe('Pipelines Nav Controls', () => {
let NavControlsComponent; let NavControlsComponent;
......
import Vue from 'vue'; import Vue from 'vue';
import pipelineUrlComp from '~/vue_pipelines_index/components/pipeline_url'; import pipelineUrlComp from '~/pipelines/components/pipeline_url';
describe('Pipeline Url Component', () => { describe('Pipeline Url Component', () => {
let PipelineUrlComponent; let PipelineUrlComponent;
......
import Vue from 'vue'; import Vue from 'vue';
import pipelinesActionsComp from '~/vue_pipelines_index/components/pipelines_actions'; import pipelinesActionsComp from '~/pipelines/components/pipelines_actions';
describe('Pipelines Actions dropdown', () => { describe('Pipelines Actions dropdown', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import artifactsComp from '~/vue_pipelines_index/components/pipelines_artifacts'; import artifactsComp from '~/pipelines/components/pipelines_artifacts';
describe('Pipelines Artifacts dropdown', () => { describe('Pipelines Artifacts dropdown', () => {
let component; let component;
......
import Vue from 'vue'; import Vue from 'vue';
import pipelinesComp from '~/vue_pipelines_index/pipelines'; import pipelinesComp from '~/pipelines/pipelines';
import Store from '~/vue_pipelines_index/stores/pipelines_store'; import Store from '~/pipelines/stores/pipelines_store';
import pipelinesData from './mock_data'; import pipelinesData from './mock_data';
describe('Pipelines', () => { describe('Pipelines', () => {
......
import PipelineStore from '~/vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '~/pipelines/stores/pipelines_store';
describe('Pipelines Store', () => { describe('Pipelines Store', () => {
let store; let store;
......
...@@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do ...@@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do
expect(results.objects('projects')).to match_array [found_project] expect(results.objects('projects')).to match_array [found_project]
end end
context 'nested group' do
let!(:nested_group) { create(:group, :nested) }
let!(:project) { create(:empty_project, namespace: nested_group) }
before do
project.add_master(user)
end
it 'returns result from nested group' do
results = Search::GlobalService.new(user, search: project.path).execute
expect(results.objects('projects')).to match_array [project]
end
it 'returns result from descendants when search inside group' do
results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute
expect(results.objects('projects')).to match_array [project]
end
end
end end
end end
end end
require 'spec_helper'
describe Search::GroupService, services: true do
shared_examples_for 'group search' do
context 'finding projects by name' do
let(:user) { create(:user) }
let(:term) { "Project Name" }
let(:nested_group) { create(:group, :nested) }
# These projects shouldn't be found
let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") }
let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )}
let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) }
# These projects should be found
let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") }
let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") }
let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") }
let(:results) { Search::GroupService.new(user, search_group, search: term).execute }
subject { results.objects('projects') }
context 'in parent group' do
let(:search_group) { nested_group.parent }
it { is_expected.to match_array([project1, project2, project3]) }
end
context 'in subgroup' do
let(:search_group) { nested_group }
it { is_expected.to match_array([project1, project2]) }
end
end
end
describe 'basic search' do
include_examples 'group search'
end
describe 'elasticsearch' do
before(:each) do
stub_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
Gitlab::Elastic::Helper.create_empty_index
# Ensure these are present when the index is refreshed
_ = [
outside_project, private_project, other_project,
project1, project2, project3,
]
Gitlab::Elastic::Helper.refresh_index
end
after(:each) do
Gitlab::Elastic::Helper.delete_index
end
include_examples 'group search'
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment