...
 
Commits (217)
This diff is collapsed.
......@@ -172,7 +172,7 @@ gem 'jira-ruby', '~> 1.1.2'
gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
# Gemnasium integration
gem 'gemnasium-gitlab-service', '~> 0.2'
gem "gemnasium-gitlab-service", git: "https://github.com/gemnasium/gemnasium-gitlab-service"
# Slack integration
gem 'slack-notifier', '~> 1.5.1'
......
GIT
remote: https://github.com/gemnasium/gemnasium-gitlab-service
revision: 4f2f3cb0003c8f5938286cb7161ff93d2804f934
specs:
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
GEM
remote: https://rubygems.org/
specs:
......@@ -240,8 +247,6 @@ GEM
fuubar (2.0.0)
rspec (~> 3.0)
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
gemojione (3.0.1)
json
get_process_mem (0.2.0)
......@@ -661,7 +666,7 @@ GEM
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
rubyzip (1.2.0)
rubyzip (1.2.1)
rufus-scheduler (3.1.10)
rugged (0.24.0)
safe_yaml (1.0.4)
......@@ -877,7 +882,7 @@ DEPENDENCIES
font-awesome-rails (~> 4.6.1)
foreman (~> 0.78.0)
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
gemnasium-gitlab-service!
gemojione (~> 3.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
......@@ -1011,4 +1016,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.14.2
1.14.5
......@@ -16,6 +16,7 @@ require('./components/board');
require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/modal/index');
const backlogHelp = require('./components/boards_backlog_help');
require('../vue_shared/vue_resource_interceptor');
$(() => {
......@@ -37,6 +38,7 @@ $(() => {
'board': gl.issueBoards.Board,
'board-sidebar': gl.issueBoards.BoardSidebar,
'board-add-issues-modal': gl.issueBoards.IssuesModal,
backlogHelp,
},
data: {
state: Store.state,
......@@ -53,6 +55,11 @@ $(() => {
detailIssueVisible () {
return Object.keys(this.detailIssue.issue).length;
},
hideHelp() {
if (this.loading) return false;
return !this.state.helpHidden;
},
},
created () {
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
......@@ -94,17 +101,53 @@ $(() => {
modal: ModalStore.store,
store: Store.state,
},
watch: {
disabled() {
this.updateTooltip();
},
},
computed: {
disabled() {
return Store.shouldAddBlankState();
return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length;
},
tooltipTitle() {
if (this.disabled) {
return 'Please add a list to your board first';
}
return '';
},
},
methods: {
updateTooltip() {
const $tooltip = $(this.$el);
this.$nextTick(() => {
if (this.disabled) {
$tooltip.tooltip();
} else {
$tooltip.tooltip('destroy');
}
});
},
openModal() {
if (!this.disabled) {
this.toggleModal(true);
}
},
},
mounted() {
this.updateTooltip();
},
template: `
<button
class="btn btn-create pull-right prepend-left-10 has-tooltip"
class="btn btn-create pull-right prepend-left-10"
type="button"
:disabled="disabled"
@click="toggleModal(true)">
data-placement="bottom"
:class="{ 'disabled': disabled }"
:title="tooltipTitle"
:aria-disabled="disabled"
@click="openModal">
Add issues
</button>
`,
......
const Vue = require('vue');
const checkmarkIcon = require('../icons/checkmark');
const Store = gl.issueBoards.BoardsStore;
const ModalStore = gl.issueBoards.ModalStore;
module.exports = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
},
computed: {
disabled() {
return !Store.state.lists
.filter(list => list.type !== 'blank' && list.type !== 'done').length;
},
},
template: `
<div class="boards-backlog-help text-center">
<h4>
We moved the Backlog
<button
type="button"
class="close"
aria-label="Close backlog help"
@click="toggleModal(false)">
<i
class="fa fa-times"
aria-hidden="true">
</i>
</button>
</h4>
<div class="backlog-help-icon">${checkmarkIcon}</div>
<p>
<a href="http://docs.gitlab.com/ce/user/project/issue_board.html">Read the docs</a> for more details
</p>
<p>
Populate the board using this button
</p>
<div class="text-center">
<button
class="btn btn-success"
type="button"
:disabled="disabled"
@click="toggleModal(true, false)">
Add issues
</button>
</div>
</div>
`,
});
......@@ -74,7 +74,7 @@ require('./lists_dropdown');
<button
class="btn btn-default pull-right"
type="button"
@click="toggleModal(false)">
@click="toggleModal(false, false)">
Cancel
</button>
</footer>
......
......@@ -57,7 +57,7 @@ const modalFilters = require('./filters');
class="close"
data-dismiss="modal"
aria-label="Close"
@click="toggleModal(false)">
@click="toggleModal(false, false)">
<span aria-hidden="true">×</span>
</button>
</h2>
......
/* global Vue */
/* global dateFormat */
Vue.filter('due-date', (value) => {
const date = new Date(value);
return $.datepicker.formatDate('M d, yy', date);
return dateFormat(date, 'mmm d, yyyy', true);
});
module.exports = '<svg viewBox="0 0 28 22" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="m45.4978994 24.9892046c0 .4726429-.1654225.8743833-.4962726 1.2052333l-15.2426565 15.2426565c-.33085.33085-.7325904.4962725-1.2052333.4962725-.4726428 0-.8743833-.1654225-1.2052333-.4962725l-8.8265615-8.8265616c-.33085-.33085-.4962726-.7325904-.4962726-1.2052333 0-.4726428.1654226-.8743833.4962726-1.2052333l2.4104666-2.4104666c.33085-.33085.7325904-.4962725 1.2052333-.4962725s.8743833.1654225 1.2052333.4962725l5.2108616 5.2285857 11.6269566-11.6446806c.33085-.33085.7325905-.4962726 1.2052333-.4962726.4726429 0 .8743833.1654226 1.2052333.4962726l2.4104666 2.4104666c.3308501.33085.4962726.7325904.4962726 1.2052333z"/><mask id="b" fill="#fff" height="21.0561348" width="27.4722297" x="0" y="0"><use xlink:href="#a"/></mask></defs><use fill="none" mask="url(#b)" stroke="#1a97d5" stroke-width="2" transform="translate(-18 -20)" xlink:href="#a"/></svg>';
/* global Cookies */
(() => {
const Store = gl.issueBoards.BoardsStore;
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalMixins = {
methods: {
toggleModal(toggle) {
ModalStore.store.showAddIssuesModal = toggle;
toggleModal(toggleModal, setCookie = true) {
if (setCookie) {
Cookies.set('boards_backlog_help_hidden', true);
Store.state.helpHidden = true;
}
ModalStore.store.showAddIssuesModal = toggleModal;
},
changeTab(tab) {
ModalStore.store.activeTab = tab;
......
......@@ -25,6 +25,7 @@
label_name: gl.utils.getParameterValues('label_name[]'),
search: ''
};
this.state.helpHidden = Cookies.get('boards_backlog_help_hidden') === 'true';
},
addList (listObj) {
const list = new List(listObj);
......
......@@ -7,7 +7,7 @@
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
Build.interval = null;
Build.timeout = null;
Build.state = null;
......@@ -31,7 +31,7 @@
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
clearInterval(Build.interval);
clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
......@@ -52,17 +52,7 @@
this.getInitialBuildTrace();
this.initScrollButtonAffix();
}
if (this.buildStatus === "running" || this.buildStatus === "pending") {
Build.interval = setInterval((function(_this) {
// Check for new build output if user still watching build page
// Only valid for runnig build when output changes during time
return function() {
if (_this.location() === _this.pageUrl) {
return _this.getBuildTrace();
}
};
})(this), 4000);
}
this.invokeBuildTrace();
}
Build.prototype.initSidebar = function() {
......@@ -83,6 +73,22 @@
return window.location.href.split("#")[0];
};
Build.prototype.invokeBuildTrace = function() {
var continueRefreshStatuses = ['running', 'pending'];
// Continue to update build trace when build is running or pending
if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
// Check for new build output if user still watching build page
// Only valid for runnig build when output changes during time
Build.timeout = setTimeout((function(_this) {
return function() {
if (_this.location() === _this.pageUrl) {
return _this.getBuildTrace();
}
};
})(this), 4000);
}
};
Build.prototype.getInitialBuildTrace = function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
......@@ -113,6 +119,7 @@
if (log.state) {
_this.state = log.state;
}
_this.invokeBuildTrace();
if (log.status === "running") {
if (log.append) {
$('.js-build-output').append(log.html);
......
......@@ -20,7 +20,10 @@ $(() => {
gl.commits.PipelinesTableBundle.$destroy(true);
}
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
}
});
......@@ -8,7 +8,22 @@
* Uses Vue.Resource
*/
class PipelinesService {
constructor(endpoint) {
/**
* FIXME: The url provided to request the pipelines in the new merge request
* page already has `.json`.
* This should be fixed when the endpoint is improved.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
} else {
endpoint = root;
}
this.pipelines = Vue.resource(endpoint);
}
......
......@@ -26,7 +26,7 @@ class PipelinesStore {
*/
startTimeAgoLoops() {
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(function timeloopInterval() {
this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
......
......@@ -56,15 +56,14 @@ require('./pipelines_store');
},
/**
* When the component is created the service to fetch the data will be
* initialized with the correct endpoint.
* When the component is about to be mounted, tell the service to fetch the data
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
created() {
beforeMount() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
......
......@@ -25,6 +25,9 @@ require('./lib/utils/common_utils');
},
},
ReferenceFilter: {
'.tooltip'(el, text) {
return '';
},
'a.gfm:not([data-link=true])'(el, text) {
return el.dataset.original || text;
},
......@@ -91,6 +94,9 @@ require('./lib/utils/common_utils');
},
},
SanitizationFilter: {
'a[name]:not([href]):empty'(el, text) {
return el.outerHTML;
},
'dl'(el, text) {
let lines = text.trim().split('\n');
// Add two spaces to the front of subsequent list items lines,
......
......@@ -97,7 +97,7 @@ $(() => {
}
this.isLoadingStage = true;
cycleAnalyticsStore.setStageEvents([]);
cycleAnalyticsStore.setStageEvents([], stage);
cycleAnalyticsStore.setActiveStage(stage);
cycleAnalyticsService
......@@ -107,7 +107,7 @@ $(() => {
})
.done((response) => {
this.isEmptyStage = !response.events.length;
cycleAnalyticsStore.setStageEvents(response.events);
cycleAnalyticsStore.setStageEvents(response.events, stage);
})
.error(() => {
this.isEmptyStage = true;
......
/* eslint-disable no-param-reassign */
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -34,11 +38,12 @@
});
newData.stages.forEach((item) => {
const stageName = item.title.toLowerCase();
const stageSlug = gl.text.dasherize(item.title.toLowerCase());
item.active = false;
item.isUserAllowed = data.permissions[stageName];
item.emptyStageText = EMPTY_STAGE_TEXTS[stageName];
item.component = `stage-${stageName}-component`;
item.isUserAllowed = data.permissions[stageSlug];
item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
item.component = `stage-${stageSlug}-component`;
item.slug = stageSlug;
});
newData.analytics = data;
return newData;
......@@ -58,31 +63,36 @@
this.deactivateAllStages();
stage.active = true;
},
setStageEvents(events) {
this.state.events = this.decorateEvents(events);
setStageEvents(events, stage) {
this.state.events = this.decorateEvents(events, stage);
},
decorateEvents(events) {
decorateEvents(events, stage) {
const newEvents = [];
events.forEach((item) => {
if (!item) return;
item.totalTime = item.total_time;
item.author.webUrl = item.author.web_url;
item.author.avatarUrl = item.author.avatar_url;
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
eventItem.totalTime = eventItem.total_time;
if (eventItem.author) {
eventItem.author.webUrl = eventItem.author.web_url;
eventItem.author.avatarUrl = eventItem.author.avatar_url;
}
if (item.created_at) item.createdAt = item.created_at;
if (item.short_sha) item.shortSha = item.short_sha;
if (item.commit_url) item.commitUrl = item.commit_url;
if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
delete item.author.web_url;
delete item.author.avatar_url;
delete item.total_time;
delete item.created_at;
delete item.short_sha;
delete item.commit_url;
delete eventItem.author.web_url;
delete eventItem.author.avatar_url;
delete eventItem.total_time;
delete eventItem.created_at;
delete eventItem.short_sha;
delete eventItem.commit_url;
newEvents.push(item);
newEvents.push(eventItem);
});
return newEvents;
......
module.exports = {
issue: {
created_at: '',
url: '',
iid: '',
title: '',
total_time: {},
author: {
avatar_url: '',
id: '',
name: '',
web_url: '',
},
},
plan: {
title: '',
commit_url: '',
short_sha: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
code: {
title: '',
iid: '',
created_at: '',
url: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
test: {
name: '',
id: '',
date: '',
url: '',
short_sha: '',
commit_url: '',
total_time: {},
branch: {
name: '',
url: '',
},
},
review: {
title: '',
iid: '',
created_at: '',
url: '',
state: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
staging: {
id: '',
short_sha: '',
date: '',
url: '',
commit_url: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
branch: {
name: '',
url: '',
},
},
production: {
title: '',
created_at: '',
url: '',
iid: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
};
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
/* global Vue */
/* global CommentsStore */
const Vue = require('vue');
(() => {
const CommentAndResolveBtn = Vue.extend({
......@@ -9,13 +9,11 @@
},
data() {
return {
textareaIsEmpty: true
textareaIsEmpty: true,
discussion: {},
};
},
computed: {
discussion: function () {
return CommentsStore.state[this.discussionId];
},
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
......@@ -42,6 +40,9 @@
}
}
},
created() {
this.discussion = CommentsStore.state[this.discussionId];
},
mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaIsEmpty = $textarea.val() === '';
......
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */
/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
const Vue = require('vue');
(() => {
const JumpToDiscussion = Vue.extend({
......@@ -12,12 +12,10 @@
data: function () {
return {
discussions: CommentsStore.state,
discussion: {},
};
},
computed: {
discussion: function () {
return this.discussions[this.discussionId];
},
allResolved: function () {
return this.unresolvedDiscussionCount === 0;
},
......@@ -186,7 +184,10 @@
offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
});
}
}
},
created() {
this.discussion = this.discussions[this.discussionId];
},
});
Vue.component('jump-to-discussion', JumpToDiscussion);
......
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */
/* global Vue */
/* global CommentsStore */
/* global ResolveService */
/* global Flash */
const Vue = require('vue');
(() => {
const ResolveBtn = Vue.extend({
......@@ -10,14 +10,14 @@
noteId: Number,
discussionId: String,
resolved: Boolean,
projectPath: String,
canResolve: Boolean,
resolvedBy: String
},
data: function () {
return {
discussions: CommentsStore.state,
loading: false
loading: false,
note: {},
};
},
watch: {
......@@ -30,13 +30,6 @@
discussion: function () {
return this.discussions[this.discussionId];
},
note: function () {
if (this.discussion) {
return this.discussion.getNote(this.noteId);
} else {
return undefined;
}
},
buttonText: function () {
if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`;
......@@ -73,10 +66,10 @@
if (this.isResolved) {
promise = ResolveService
.unresolve(this.projectPath, this.noteId);
.unresolve(this.noteId);
} else {
promise = ResolveService
.resolve(this.projectPath, this.noteId);
.resolve(this.noteId);
}
promise.then((response) => {
......@@ -106,6 +99,8 @@
},
created: function () {
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
this.note = this.discussion.getNote(this.noteId);
}
});
......
/* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */
/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
const Vue = require('vue');
((w) => {
w.ResolveCount = Vue.extend({
......
/* eslint-disable object-shorthand, func-names, space-before-function-paren, comma-dangle, no-else-return, quotes, max-len */
/* global Vue */
/* global CommentsStore */
/* global ResolveService */
const Vue = require('vue');
(() => {
const ResolveDiscussionBtn = Vue.extend({
props: {
discussionId: String,
mergeRequestId: Number,
projectPath: String,
canResolve: Boolean,
},
data: function() {
return {
discussions: CommentsStore.state
discussion: {},
};
},
computed: {
discussion: function () {
return this.discussions[this.discussionId];
},
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
......@@ -51,11 +48,13 @@
},
methods: {
resolve: function () {
ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
}
},
created: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
this.discussion = CommentsStore.state[this.discussionId];
}
});
......
......@@ -3,6 +3,7 @@
/* global ResolveCount */
function requireAll(context) { return context.keys().map(context); }
const Vue = require('vue');
requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
......@@ -10,11 +11,14 @@ requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/));
$(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
gl.diffNotesCompileComponents = () => {
const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1;
......
/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
/* global Vue */
/* global Flash */
/* global CommentsStore */
((w) => {
class ResolveServiceClass {
constructor() {
this.noteResource = Vue.resource('notes{/noteId}/resolve');
this.discussionResource = Vue.resource('merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve');
}
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('../../vue_shared/vue_resource_interceptor');
setCSRF() {
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
}
(() => {
window.gl = window.gl || {};
prepareRequest(root) {
this.setCSRF();
Vue.http.options.root = root;
class ResolveServiceClass {
constructor(root) {
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
}
resolve(projectPath, noteId) {
this.prepareRequest(projectPath);
resolve(noteId) {
return this.noteResource.save({ noteId }, {});
}
unresolve(projectPath, noteId) {
this.prepareRequest(projectPath);
unresolve(noteId) {
return this.noteResource.delete({ noteId }, {});
}
toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
toggleResolveForDiscussion(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
const isResolved = discussion.isResolved();
let promise;
if (isResolved) {
promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
promise = this.unResolveAll(mergeRequestId, discussionId);
} else {
promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
promise = this.resolveAll(mergeRequestId, discussionId);
}
promise.then((response) => {
......@@ -62,11 +54,9 @@
});
}
resolveAll(projectPath, mergeRequestId, discussionId) {
resolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
this.prepareRequest(projectPath);
discussion.loading = true;
return this.discussionResource.save({
......@@ -75,11 +65,9 @@
}, {});
}
unResolveAll(projectPath, mergeRequestId, discussionId) {
unResolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
this.prepareRequest(projectPath);
discussion.loading = true;
return this.discussionResource.delete({
......@@ -89,5 +77,5 @@
}
}
w.ResolveService = new ResolveServiceClass();
})(window);
gl.DiffNotesResolveServiceClass = ResolveServiceClass;
})();
......@@ -15,7 +15,7 @@ window.Vue = require('vue');
},
template: `
<a class="btn external_url" :href="externalUrl" target="_blank">
<a class="btn external_url" :href="externalUrl" target="_blank" rel="noopener noreferrer">
<i class="fa fa-external-link"></i>
</a>
`,
......
......@@ -4,7 +4,7 @@
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input.getAttribute('data-id');
this.hookId = input && input.getAttribute('data-id');
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
......
......@@ -2,7 +2,8 @@
(() => {
class FilteredSearchDropdownManager {
constructor() {
constructor(baseEndpoint = '') {
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchInput = document.querySelector('.filtered-search');
......@@ -38,13 +39,13 @@
milestone: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: ['milestones.json', '%'],
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: document.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: ['labels.json', '~'],
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: document.querySelector('#js-dropdown-label'),
},
hint: {
......