Commit f108f9d6 authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'ce-to-ee-2018-02-06' into 'master'

CE upstream - 2018-02-06 09:41 UTC

Closes gitlab-qa#175, #4803, and gitaly#991

See merge request gitlab-org/gitlab-ee!4395
parents c51b2c6e 04dc4bfc
...@@ -652,6 +652,8 @@ codequality: ...@@ -652,6 +652,8 @@ codequality:
sast: sast:
<<: *except-docs <<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest image: registry.gitlab.com/gitlab-org/gl-sast:latest
variables:
CONFIDENCE_LEVEL: 2
before_script: [] before_script: []
script: script:
- /app/bin/run . - /app/bin/run .
......
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
export default function initBroadcastMessagesForm() { export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() { $('input#broadcast_message_color').on('input', function onMessageColorInput() {
...@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() { ...@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() {
if (message === '') { if (message === '') {
$('.js-broadcast-message-preview').text('Your message here'); $('.js-broadcast-message-preview').text('Your message here');
} else { } else {
$.ajax({ axios.post(previewPath, {
url: previewPath, broadcast_message: {
type: 'POST', message,
data: {
broadcast_message: { message },
}, },
}); })
.then(({ data }) => {
$('.js-broadcast-message-preview').html(data.message);
})
.catch(() => flash(__('An error occurred while rendering preview broadcast message')));
} }
}, 250)); }, 250));
} }
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
const debounceTimeoutDuration = 1000; const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline'; const invalidInputClass = 'gl-field-error-outline';
...@@ -77,12 +80,9 @@ export default class UsernameValidator { ...@@ -77,12 +80,9 @@ export default class UsernameValidator {
this.state.pending = true; this.state.pending = true;
this.state.available = false; this.state.available = false;
this.renderState(); this.renderState();
return $.ajax({ axios.get(`${gon.relative_url_root}/users/${username}/exists`)
type: 'GET', .then(({ data }) => this.setAvailabilityState(data.exists))
url: `${gon.relative_url_root}/users/${username}/exists`, .catch(() => flash(__('An error occurred while validating username')));
dataType: 'json',
success: (res) => this.setAvailabilityState(res.exists)
});
} }
} }
......
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
// more than `x` users are referenced. // more than `x` users are referenced.
// //
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
var lastTextareaPreviewed; var lastTextareaPreviewed;
var lastTextareaHeight = null; var lastTextareaHeight = null;
var markdownPreview; var markdownPreview;
...@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { ...@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response); success(this.ajaxCache.response);
return; return;
} }
$.ajax({ axios.post(url, {
type: 'POST', text,
url: url, })
data: { .then(({ data }) => {
text: text this.ajaxCache = {
}, text: text,
dataType: 'json', response: data,
success: (function (response) { };
this.ajaxCache = { success(data);
text: text, })
response: response .catch(() => flash(__('An error occurred while fetching markdown preview')));
};
success(response);
}).bind(this)
});
}; };
MarkdownPreview.prototype.hideReferencedUsers = function ($form) { MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
......
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
export default class ProjectLabelSubscription { export default class ProjectLabelSubscription {
constructor(container) { constructor(container) {
this.$container = $(container); this.$container = $(container);
...@@ -17,10 +21,7 @@ export default class ProjectLabelSubscription { ...@@ -17,10 +21,7 @@ export default class ProjectLabelSubscription {
$btn.addClass('disabled'); $btn.addClass('disabled');
$span.toggleClass('hidden'); $span.toggleClass('hidden');
$.ajax({ axios.post(url).then(() => {
type: 'POST',
url,
}).done(() => {
let newStatus; let newStatus;
let newAction; let newAction;
...@@ -45,6 +46,6 @@ export default class ProjectLabelSubscription { ...@@ -45,6 +46,6 @@ export default class ProjectLabelSubscription {
return button; return button;
}); });
}); }).catch(() => flash(__('There was an error subscribing to this label.')));
} }
} }
import axios from '../lib/utils/axios_utils';
import PANEL_STATE from './constants'; import PANEL_STATE from './constants';
import { backOff } from '../lib/utils/common_utils'; import { backOff } from '../lib/utils/common_utils';
...@@ -81,24 +82,20 @@ export default class PrometheusMetrics { ...@@ -81,24 +82,20 @@ export default class PrometheusMetrics {
loadActiveMetrics() { loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING); this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
backOff((next, stop) => { backOff((next, stop) => {
$.ajax({ axios.get(this.activeMetricsEndpoint)
url: this.activeMetricsEndpoint, .then(({ data }) => {
dataType: 'json', if (data && data.success) {
global: false, stop(data);
})
.done((res) => {
if (res && res.success) {
stop(res);
} else { } else {
this.backOffRequestCounter = this.backOffRequestCounter += 1; this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < 3) { if (this.backOffRequestCounter < 3) {
next(); next();
} else { } else {
stop(res); stop(data);
} }
} }
}) })
.fail(stop); .catch(stop);
}) })
.then((res) => { .then((res) => {
if (res && res.data && res.data.length) { if (res && res.data && res.data.length) {
......
/* eslint-disable no-new */ import flash from '../flash';
import Flash from '../flash'; import axios from '../lib/utils/axios_utils';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
export default class ProtectedBranchEdit { export default class ProtectedBranchEdit {
...@@ -38,29 +38,25 @@ export default class ProtectedBranchEdit { ...@@ -38,29 +38,25 @@ export default class ProtectedBranchEdit {
this.$allowedToMergeDropdown.disable(); this.$allowedToMergeDropdown.disable();
this.$allowedToPushDropdown.disable(); this.$allowedToPushDropdown.disable();
$.ajax({ axios.patch(this.$wrap.data('url'), {
type: 'POST', protected_branch: {
url: this.$wrap.data('url'), merge_access_levels_attributes: [{
dataType: 'json', id: this.$allowedToMergeDropdown.data('access-level-id'),
data: { access_level: $allowedToMergeInput.val(),
_method: 'PATCH', }],
protected_branch: { push_access_levels_attributes: [{
merge_access_levels_attributes: [{ id: this.$allowedToPushDropdown.data('access-level-id'),
id: this.$allowedToMergeDropdown.data('access-level-id'), access_level: $allowedToPushInput.val(),
access_level: $allowedToMergeInput.val(), }],
}],
push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val(),
}],
},
}, },
error() { }).then(() => {
new Flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list')); this.$allowedToMergeDropdown.enable();
}, this.$allowedToPushDropdown.enable();
}).always(() => { }).catch(() => {
this.$allowedToMergeDropdown.enable(); this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable(); this.$allowedToPushDropdown.enable();
flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list'));
}); });
} }
} }
/* eslint-disable no-new */ import flash from '../flash';
import Flash from '../flash'; import axios from '../lib/utils/axios_utils';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
export default class ProtectedTagEdit { export default class ProtectedTagEdit {
...@@ -28,24 +28,19 @@ export default class ProtectedTagEdit { ...@@ -28,24 +28,19 @@ export default class ProtectedTagEdit {
this.$allowedToCreateDropdownButton.disable(); this.$allowedToCreateDropdownButton.disable();
$.ajax({ axios.patch(this.$wrap.data('url'), {
type: 'POST', protected_tag: {
url: this.$wrap.data('url'), create_access_levels_attributes: [{
dataType: 'json', id: this.$allowedToCreateDropdownButton.data('access-level-id'),
data: { access_level: $allowedToCreateInput.val(),
_method: 'PATCH', }],
protected_tag: {
create_access_levels_attributes: [{
id: this.$allowedToCreateDropdownButton.data('access-level-id'),
access_level: $allowedToCreateInput.val(),
}],
},
}, },
error() { }).then(() => {
new Flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list')); this.$allowedToCreateDropdownButton.enable();
}, }).catch(() => {
}).always(() => {
this.$allowedToCreateDropdownButton.enable(); this.$allowedToCreateDropdownButton.enable();
flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list'));
}); });
} }
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import flash from './flash';
import axios from './lib/utils/axios_utils';
function Sidebar(currentUser) { function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this); this.toggleTodo = this.toggleTodo.bind(this);
...@@ -62,7 +64,7 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { ...@@ -62,7 +64,7 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url; var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget); $this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST'; ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post';
if ($this.attr('data-delete-path')) { if ($this.attr('data-delete-path')) {
url = "" + ($this.attr('data-delete-path')); url = "" + ($this.attr('data-delete-path'));
} else { } else {
...@@ -71,25 +73,14 @@ Sidebar.prototype.toggleTodo = function(e) { ...@@ -71,25 +73,14 @@ Sidebar.prototype.toggleTodo = function(e) {
$this.tooltip('hide'); $this.tooltip('hide');
return $.ajax({ $('.js-issuable-todo').disable().addClass('is-loading');
url: url,
type: ajaxType, axios[ajaxType](url, {
dataType: 'json', issuable_id: $this.data('issuable-id'),
data: { issuable_type: $this.data('issuable-type'),
issuable_id: $this.data('issuable-id'), }).then(({ data }) => {
issuable_type: $this.data('issuable-type') this.todoUpdateDone(data);
}, }).catch(() => flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`));
beforeSend: (function(_this) {
return function() {
$('.js-issuable-todo').disable()
.addClass('is-loading');
};
})(this)
}).done((function(_this) {
return function(data) {
return _this.todoUpdateDone(data);
};
})(this));
}; };
Sidebar.prototype.todoUpdateDone = function(data) { Sidebar.prototype.todoUpdateDone = function(data) {
......
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import axios from './lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility'; import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
...@@ -85,21 +86,21 @@ export default class Shortcuts { ...@@ -85,21 +86,21 @@ export default class Shortcuts {
$modal.modal('toggle'); $modal.modal('toggle');
} }
$.ajax({ return axios.get(gon.shortcuts_path, {
url: gon.shortcuts_path, responseType: 'text',
dataType: 'script', }).then(({ data }) => {
success() { $.globalEval(data);
if (location && location.length > 0) {
const results = []; if (location && location.length > 0) {
for (let i = 0, len = location.length; i < len; i += 1) { const results = [];
results.push($(location[i]).show()); for (let i = 0, len = location.length; i < len; i += 1) {
} results.push($(location[i]).show());
return results;
} }
return results;
}
$('.hidden-shortcut').show(); $('.hidden-shortcut').show();
return $('.js-more-help-button').remove(); return $('.js-more-help-button').remove();
},
}); });
} }
......
import 'deckar01-task_list'; import 'deckar01-task_list';
import axios from './lib/utils/axios_utils';
import Flash from './flash'; import Flash from './flash';
export default class TaskList { export default class TaskList {
...@@ -7,11 +8,11 @@ export default class TaskList { ...@@ -7,11 +8,11 @@ export default class TaskList {
this.dataType = options.dataType; this.dataType = options.dataType;
this.fieldName = options.fieldName; this.fieldName = options.fieldName;
this.onSuccess = options.onSuccess || (() => {}); this.onSuccess = options.onSuccess || (() => {});
this.onError = function showFlash(response) { this.onError = function showFlash(e) {
let errorMessages = ''; let errorMessages = '';
if (response.responseJSON) { if (e.response.data && typeof e.response.data === 'object') {
errorMessages = response.responseJSON.errors.join(' '); errorMessages = e.response.data.errors.join(' ');
} }
return new Flash(errorMessages || 'Update failed', 'alert'); return new Flash(errorMessages || 'Update failed', 'alert');
...@@ -38,12 +39,9 @@ export default class TaskList { ...@@ -38,12 +39,9 @@ export default class TaskList {
patchData[this.dataType] = { patchData[this.dataType] = {
[this.fieldName]: $target.val(), [this.fieldName]: $target.val(),
}; };
return $.ajax({
type: 'PATCH', return axios.patch($target.data('update-url') || $('form.js-issuable-update').attr('action'), patchData)
url: $target.data('update-url') || $('form.js-issuable-update').attr('action'), .then(({ data }) => this.onSuccess(data))
data: patchData, .catch(err => this.onError(err));
success: this.onSuccess,
error: this.onError,
});
} }
} }
import axios from '../lib/utils/axios_utils';
import Activities from '../activities'; import Activities from '../activities';
import ActivityCalendar from './activity_calendar'; import ActivityCalendar from './activity_calendar';
import { localTimeAgo } from '../lib/utils/datetime_utility'; import { localTimeAgo } from '../lib/utils/datetime_utility';
import { __ } from '../locale';
import flash from '../flash';
/** /**
* UserTabs * UserTabs
...@@ -131,18 +134,20 @@ export default class UserTabs { ...@@ -131,18 +134,20 @@ export default class UserTabs {
} }
loadTab(action, endpoint) { loadTab(action, endpoint) {
return $.ajax({ this.toggleLoading(true);
beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false), return axios.get(endpoint)
dataType: 'json', .then(({ data }) => {
url: endpoint,
success: (data) => {
const tabSelector = `div#${action}`; const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html); this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true; this.loaded[action] = true;
localTimeAgo($('.js-timeago', tabSelector)); localTimeAgo($('.js-timeago', tabSelector));
},
}); this.toggleLoading(false);
})
.catch(() => {
this.toggleLoading(false);
});
} }
loadActivities() { loadActivities() {
...@@ -158,17 +163,15 @@ export default class UserTabs { ...@@ -158,17 +163,15 @@ export default class UserTabs {
utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`; utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`;
} }
$.ajax({ axios.get(calendarPath)
dataType: 'json', .then(({ data }) => {
url: calendarPath,
success: (activityData) => {
$calendarWrap.html(CALENDAR_TEMPLATE); $calendarWrap.html(CALENDAR_TEMPLATE);
$calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset); new ActivityCalendar('.js-contrib-calendar', data, calendarActivitiesPath, utcOffset);
}, })
}); .catch(() => flash(__('There was an error loading users activity calendar.')));
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Activities(); new Activities();
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Issuable */ /* global Issuable */
/* global emitSidebarEvent */ /* global emitSidebarEvent */
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils';
// TODO: remove eventHub hack after code splitting refactor // TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop; window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
...@@ -177,32 +178,28 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -177,32 +178,28 @@ function UsersSelect(currentUser, els, options = {}) {
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
return $.ajax({ return axios.put(issueURL, data)
type: 'PUT', .then(({ data }) => {
dataType: 'json', var user;
url: issueURL, $dropdown.trigger('loaded.gl.dropdown');
data: data $loading.fadeOut();
}).done(function(data) { if (data.assignee) {
var user; user = {
$dropdown.trigger('loaded.gl.dropdown'); name: data.assignee.name,
$loading.fadeOut(); username: data.assignee.username,
if (data.assignee) { avatar: data.assignee.avatar_url
user = { };
name: data.assignee.name, } else {
username: data.assignee.username, user = {
avatar: data.assignee.avatar_url name: 'Unassigned',
}; username: '',
} else { avatar: ''
user = { };
name: 'Unassigned', }
username: '', $value.html(assigneeTemplate(user));
avatar: '' $collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
}; return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
} });
$value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
});
}; };
collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>'); collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>'); assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
...@@ -660,38 +657,33 @@ UsersSelect.prototype.user = function(user_id, callback) { ...@@ -660,38 +657,33 @@ UsersSelect.prototype.user = function(user_id, callback) {
var url; var url;
url = this.buildUrl(this.userPath); url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id); url = url.replace(':id', user_id);
return $.ajax({ return axios.get(url)
url: url, .then(({ data }) => {
dataType: "json" callback(data);
}).done(function(user) { });
return callback(user);
});
}; };
// Return users list. Filtered by query // Return users list. Filtered by query
// Only active users retrieved // Only active users retrieved
UsersSelect.prototype.users = function(query, options, callback) { UsersSelect.prototype.users = function(query, options, callback) {
var url; const url = this.buildUrl(this.usersPath);
url = this.buildUrl(this.usersPath); const params = {
return $.ajax({ search: query,
url: url, per_page: options.perPage || 20,
data: { active: true,
search: query, project_id: options.projectId || null,
per_page: options.perPage || 20, group_id: options.groupId || null,
active: true, skip_ldap: options.skipLdap || null,
project_id: options.projectId || null, todo_filter: options.todoFilter || null,
group_id: options.groupId || null, todo_state_filter: options.todoStateFilter || null,
skip_ldap: options.skipLdap || null, current_user: options.showCurrentUser || null,
todo_filter: options.todoFilter || null, author_id: options.authorId || null,
todo_state_filter: options.todoStateFilter || null, skip_users: options.skipUsers || null
current_user: options.showCurrentUser || null, };
author_id: options.authorId || null, return axios.get(url, { params })
skip_users: options.skipUsers || null .then(({ data }) => {
}, callback(data);
dataType: "json" });
}).done(function(users) {
return callback(users);
});
}; };
UsersSelect.prototype.buildUrl = function(url) { UsersSelect.prototype.buildUrl = function(url) {
......
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
<template> <template>
<div class="branch-commit"> <div class="branch-commit">
<template v-if="hasCommitRef && showBranch"> <template v-if="hasCommitRef && showBranch">
<div class="icon-container hidden-xs"> <div class="icon-container">
<i <i
v-if="tag" v-if="tag"
class="fa fa-tag" class="fa fa-tag"
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
</div> </div>
<a <a
class="ref-name hidden-xs" class="ref-name"
:href="commitRef.ref_url" :href="commitRef.ref_url"
v-tooltip v-tooltip
data-container="body" data-container="body"
......
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
.ref-name { .ref-name {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
max-width: 120px; max-width: 100px;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
white-space: nowrap; white-space: nowrap;
......
class Admin::BroadcastMessagesController < Admin::ApplicationController class Admin::BroadcastMessagesController < Admin::ApplicationController
include BroadcastMessagesHelper
before_action :finder, only: [:edit, :update, :destroy] before_action :finder, only: [:edit, :update, :destroy]
def index def index
...@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end end
def preview def preview
@broadcast_message = BroadcastMessage.new(broadcast_message_params) broadcast_message = BroadcastMessage.new(broadcast_message_params)
render json: { message: render_broadcast_message(broadcast_message) }
end end
protected protected
......
...@@ -4,6 +4,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -4,6 +4,8 @@ class RegistrationsController < Devise::RegistrationsController
before_action :whitelist_query_limiting, only: [:destroy] before_action :whitelist_query_limiting, only: [:destroy]
before_action :whitelist_query_limiting, only: [:destroy]
def new def new
redirect_to(new_user_session_path) redirect_to(new_user_session_path)
end end
......
...@@ -17,8 +17,12 @@ module Clusters ...@@ -17,8 +17,12 @@ module Clusters
'stable/nginx-ingress' 'stable/nginx-ingress'
end end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart) Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end end
end end
end end
......
...@@ -230,6 +230,7 @@ class Namespace < ActiveRecord::Base ...@@ -230,6 +230,7 @@ class Namespace < ActiveRecord::Base
has_parent? has_parent?
end end
## EE only
def multiple_issue_boards_available?(user = nil) def multiple_issue_boards_available?(user = nil)
feature_available?(:multiple_issue_boards) feature_available?(:multiple_issue_boards)
end end
......
...@@ -239,8 +239,8 @@ class User < ActiveRecord::Base ...@@ -239,8 +239,8 @@ class User < ActiveRecord::Base
joins(:identities).where(identities: { provider: provider }) joins(:identities).where(identities: { provider: provider })
end end
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) } scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
def self.with_two_factor def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
......
...@@ -213,7 +213,7 @@ class WikiPage ...@@ -213,7 +213,7 @@ class WikiPage
last_commit_sha = attrs.delete(:last_commit_sha) last_commit_sha = attrs.delete(:last_commit_sha)
if last_commit_sha && last_commit_sha != self.last_commit_sha if last_commit_sha && last_commit_sha != self.last_commit_sha
raise PageChangedError.new("WikiEdit|You are attempting to update a page that has changed since you started editing it.") raise PageChangedError
end end
update_attributes(attrs) update_attributes(attrs)
...@@ -223,7 +223,7 @@ class WikiPage ...@@ -223,7 +223,7 @@ class WikiPage
if wiki.find_page(page_details).present? if wiki.find_page(page_details).present?
@attributes[:title] = @page.url_path @attributes[:title] = @page.url_path
raise PageRenameError.new("WikiEdit|There is already a page with the same title in that path.") raise PageRenameError
end end
else else
page_details = @page.url_path page_details = @page.url_path
......
...@@ -22,8 +22,7 @@ module SystemNoteService ...@@ -22,8 +22,7 @@ module SystemNoteService
commits_text = "#{total_count} commit".pluralize(total_count) commits_text = "#{total_count} commit".pluralize(total_count)
body = "added #{commits_text}\n\n" body = "added #{commits_text}\n\n"
body << existing_commit_summary(noteable, existing_commits, oldrev) body << commits_list(noteable, new_commits, existing_commits, oldrev)
body << new_commit_summary(new_commits).join("\n")
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count)) create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
...@@ -481,7 +480,7 @@ module SystemNoteService ...@@ -481,7 +480,7 @@ module SystemNoteService
# Returns an Array of Strings # Returns an Array of Strings
def new_commit_summary(new_commits) def new_commit_summary(new_commits)
new_commits.collect do |commit| new_commits.collect do |commit|
"* #{commit.short_id} - #{escape_html(commit.title)}" content_tag('li', "#{commit.short_id} - #{commit.title}")
end end
end end
...@@ -709,6 +708,16 @@ module SystemNoteService ...@@ -709,6 +708,16 @@ module SystemNoteService
"#{cross_reference_note_prefix}#{gfm_reference}" "#{cross_reference_note_prefix}#{gfm_reference}"
end end
# Builds a list of existing and new commits according to existing_commits and
# new_commits methods.
# Returns a String wrapped in `ul` and `li` tags.
def commits_list(noteable, new_commits, existing_commits, oldrev)
existing_commit_summary = existing_commit_summary(noteable, existing_commits, oldrev)
new_commit_summary = new_commit_summary(new_commits).join
content_tag('ul', "#{existing_commit_summary}#{new_commit_summary}".html_safe)
end
# Build a single line summarizing existing commits being added in a merge # Build a single line summarizing existing commits being added in a merge
# request # request
# #
...@@ -745,11 +754,8 @@ module SystemNoteService ...@@ -745,11 +754,8 @@ module SystemNoteService
branch = noteable.target_branch branch = noteable.target_branch
branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork? branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?
"* #{commit_ids} - #{commits_text} from branch `#{branch}`\n" branch_name = content_tag('code', branch)
end content_tag('li', "#{commit_ids} - #{commits_text} from branch #{branch_name}".html_safe)
def escape_html(text)
Rack::Utils.escape_html(text)
end end
def url_helpers def url_helpers
...@@ -766,4 +772,8 @@ module SystemNoteService ...@@ -766,4 +772,8 @@ module SystemNoteService
start_sha: oldrev start_sha: oldrev
) )
end end
def content_tag(*args)
ActionController::Base.helpers.content_tag(*args)
end
end end
$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@broadcast_message))}");
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
- if @repository.gitlab_ci_yml - if @repository.gitlab_ci_yml
%li %li
= link_to _('CI configuration'), ci_configuration_path(@project) = link_to _('CI/CD configuration'), ci_configuration_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch) - if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog - unless @repository.changelog
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
#{ _('Set up CI') } #{ _('Set up CI/CD') }
- if koding_enabled? && @repository.koding_yml.blank? - if koding_enabled? && @repository.koding_yml.blank?
%li.missing %li.missing
= link_to _('Set up Koding'), add_koding_stack_path(@project) = link_to _('Set up Koding'), add_koding_stack_path(@project)
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.wiki .wiki
= markdown_field(release, :description) = markdown_field(release, :description)
.row-fixed-content.controls .row-fixed-content.controls.flex-row
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name] = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
......
---
title: Fix Sort by Recent Sign-in in Admin Area
merge_request: 13852
author: Poornima M
---
title: Enable Prometheus metrics for deployed Ingresses
merge_request: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16866
author: joshlambert
type: changed
---
title: Fixes different margins between buttons in tag list
merge_request: 16927
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Rename button to enable CI/CD configuration to "Set up CI/CD"
merge_request: 16870
author:
type: changed
---
title: Bypass commits title markdown on notes
merge_request:
author:
type: fixed
---
title: Include branch in mobile view for pipelines
merge_request: 16910
author: George Tsiolis
type: other
...@@ -18,12 +18,21 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration ...@@ -18,12 +18,21 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
Gitlab::BackgroundMigration.steal('CopyColumn') Gitlab::BackgroundMigration.steal('CopyColumn')
Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange') Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange')
# It's possible the cleanup job was killed which means we need to manually if migrate_column_type?
# migrate any remaining rows. if closed_at_for_type_change_exists?
migrate_remaining_rows if migrate_column_type? migrate_remaining_rows
else
# Due to some EE merge problems some environments may not have the
# "closed_at_for_type_change" column. If this is the case we have no
# other option than to migrate the data _right now_.
change_column_type_concurrently(:issues, :closed_at, :datetime_with_timezone)
cleanup_concurrent_column_type_change(:issues, :closed_at)
end
end
end end
def down def down
# Previous migrations already revert the changes made here.
end end
def migrate_remaining_rows def migrate_remaining_rows
...@@ -39,4 +48,8 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration ...@@ -39,4 +48,8 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
# migration, thus we don't need to migrate those environments again. # migration, thus we don't need to migrate those environments again.
column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime
end end
def closed_at_for_type_change_exists?
columns('issues').any? { |col| col.name == 'closed_at_for_type_change' }
end
end end
...@@ -396,7 +396,7 @@ If you want to modify the CI/CD pipeline used by Auto DevOps, you can copy the ...@@ -396,7 +396,7 @@ If you want to modify the CI/CD pipeline used by Auto DevOps, you can copy the
Assuming that your project is new or it doesn't have a `.gitlab-ci.yml` file Assuming that your project is new or it doesn't have a `.gitlab-ci.yml` file
present: present:
1. From your project home page, either click on the "Set up CI" button, or click 1. From your project home page, either click on the "Set up CI/CD" button, or click
on the plus button and (`+`), then "New file" on the plus button and (`+`), then "New file"
1. Pick `.gitlab-ci.yml` as the template type 1. Pick `.gitlab-ci.yml` as the template type
1. Select "Auto-DevOps" from the template dropdown 1. Select "Auto-DevOps" from the template dropdown
......
...@@ -155,11 +155,11 @@ There are two different ways to add a new project to a group: ...@@ -155,11 +155,11 @@ There are two different ways to add a new project to a group:
### (EEP) Default project creation level ### (EEP) Default project creation level
This feature allows groups to define a default project creation level. This feature allows groups to define a default project creation level.
By default, `Developers` and `Masters` are allowed to create projects, but By default, `Developers` and `Masters` are allowed to create projects, but
this can be changed within the group settings for a group, or the default setting this can be changed within the group settings for a group, or the default setting
changed within the Admin area (`Settings`, `Visibility and Access Controls`). This changed within the Admin area (`Settings`, `Visibility and Access Controls`). This
can be `None`, `Masters`, or `Developers + Masters`. can be `None`, `Masters`, or `Developers + Masters`.
It is available only in [GitLab Enterprise Edition Premium][eep]. It is available only in [GitLab Enterprise Edition Premium][eep].
......
...@@ -77,7 +77,7 @@ is useful for submitting merge requests to the upstream. ...@@ -77,7 +77,7 @@ is useful for submitting merge requests to the upstream.
> >
> 2. Why do I need to enable Shared Runners? > 2. Why do I need to enable Shared Runners?
> >
> Shared Runners will run the script set by your GitLab CI > Shared Runners will run the script set by your GitLab CI/CD
configuration file. They're enabled by default to new projects, configuration file. They're enabled by default to new projects,
but not to forks. but not to forks.
...@@ -88,9 +88,9 @@ click **New project**, and name it considering the ...@@ -88,9 +88,9 @@ click **New project**, and name it considering the
[practical examples](getting_started_part_one.md#practical-examples). [practical examples](getting_started_part_one.md#practical-examples).
1. Clone it to your local computer, add your website 1. Clone it to your local computer, add your website
files to your project, add, commit and push to GitLab. files to your project, add, commit and push to GitLab.
1. From the your **Project**'s page, click **Set up CI**: 1. From the your **Project**'s page, click **Set up CI/CD**:
![setup GitLab CI](img/setup_ci.png) ![setup GitLab CI/CD](img/setup_ci.png)
1. Choose one of the templates from the dropbox menu. 1. Choose one of the templates from the dropbox menu.
Pick up the template corresponding to the SSG you're using (or plain HTML). Pick up the template corresponding to the SSG you're using (or plain HTML).
...@@ -98,7 +98,7 @@ Pick up the template corresponding to the SSG you're using (or plain HTML). ...@@ -98,7 +98,7 @@ Pick up the template corresponding to the SSG you're using (or plain HTML).
![gitlab-ci templates](img/choose_ci_template.png) ![gitlab-ci templates](img/choose_ci_template.png)
Once you have both site files and `.gitlab-ci.yml` in your project's Once you have both site files and `.gitlab-ci.yml` in your project's
root, GitLab CI will build your site and deploy it with Pages. root, GitLab CI/CD will build your site and deploy it with Pages.
Once the first build passes, you see your site is live by Once the first build passes, you see your site is live by
navigating to your **Project**'s **Settings** > **Pages**, navigating to your **Project**'s **Settings** > **Pages**,
where you'll find its default URL. where you'll find its default URL.
......
...@@ -45,7 +45,7 @@ has already been created, which creates a link to the license itself. ...@@ -45,7 +45,7 @@ has already been created, which creates a link to the license itself.
![New file button](img/web_editor_template_dropdown_buttons.png) ![New file button](img/web_editor_template_dropdown_buttons.png)
>**Note:** >**Note:**
The **Set up CI** button will not appear on an empty repository. You have to at The **Set up CI/CD** button will not appear on an empty repository. You have to at
least add a file in order for the button to show up. least add a file in order for the button to show up.
## Upload a file ## Upload a file
......
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
ATTRS = %i(title format url_path path name historical raw_data).freeze ATTRS = %i(title format url_path path name historical raw_data).freeze
include AttributesBag include AttributesBag
include Gitlab::EncodingHelper
def initialize(params) def initialize(params)
super super
...@@ -11,6 +12,10 @@ module Gitlab ...@@ -11,6 +12,10 @@ module Gitlab
# All gRPC strings in a response are frozen, so we get an unfrozen # All gRPC strings in a response are frozen, so we get an unfrozen
# version here so appending to `raw_data` doesn't blow up. # version here so appending to `raw_data` doesn't blow up.
@raw_data = @raw_data.dup @raw_data = @raw_data.dup
@title = encode_utf8(@title)
@path = encode_utf8(@path)
@name = encode_utf8(@name)
end end
def historical? def historical?
......
...@@ -64,7 +64,7 @@ module Gitlab ...@@ -64,7 +64,7 @@ module Gitlab
{ {
name: 'configuration-volume', name: 'configuration-volume',
configMap: { configMap: {
name: 'values-content-configuration', name: "values-content-configuration-#{command.name}",
items: [{ key: 'values', path: 'values.yaml' }] items: [{ key: 'values', path: 'values.yaml' }]
} }
} }
...@@ -81,7 +81,11 @@ module Gitlab ...@@ -81,7 +81,11 @@ module Gitlab
def create_config_map def create_config_map
resource = ::Kubeclient::Resource.new resource = ::Kubeclient::Resource.new
resource.metadata = { name: 'values-content-configuration', namespace: namespace_name, labels: { name: 'values-content-configuration' } } resource.metadata = {
name: "values-content-configuration-#{command.name}",
namespace: namespace_name,
labels: { name: "values-content-configuration-#{command.name}" }
}
resource.data = { values: File.read(command.chart_values_file) } resource.data = { values: File.read(command.chart_values_file) }
kubeclient.create_config_map(resource) kubeclient.create_config_map(resource)
end end
......
...@@ -467,7 +467,7 @@ msgstr "" ...@@ -467,7 +467,7 @@ msgstr ""
msgid "CI / CD" msgid "CI / CD"
msgstr "" msgstr ""
msgid "CI configuration" msgid "CI/CD configuration"
msgstr "" msgstr ""
msgid "CICD|Jobs" msgid "CICD|Jobs"
...@@ -2574,7 +2574,7 @@ msgstr "" ...@@ -2574,7 +2574,7 @@ msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}." msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "" msgstr ""
msgid "Set up CI" msgid "Set up CI/CD"
msgstr "" msgstr ""
msgid "Set up Koding" msgid "Set up Koding"
......
...@@ -3,7 +3,6 @@ module QA ...@@ -3,7 +3,6 @@ module QA
module Project module Project
class Show < Page::Base class Show < Page::Base
view 'app/views/shared/_clone_panel.html.haml' do view 'app/views/shared/_clone_panel.html.haml' do
element :clone_holder, '.git-clone-holder'
element :clone_dropdown element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown' element :clone_options_dropdown, '.clone-options-dropdown'
element :project_repository_location, 'text_field_tag :project_clone' element :project_repository_location, 'text_field_tag :project_clone'
...@@ -31,7 +30,7 @@ module QA ...@@ -31,7 +30,7 @@ module QA
end end
# Ensure git clone textbox was updated to http URI # Ensure git clone textbox was updated to http URI
page.has_css?('.git-clone-holder input#project_clone[value*="http"]') repository_location.include?('http')
end end
end end
......
...@@ -16,15 +16,15 @@ FactoryBot.define do ...@@ -16,15 +16,15 @@ FactoryBot.define do
path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') } path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') }
trait :personal_snippet_upload do trait :personal_snippet_upload do
model { build(:personal_snippet) }
path { File.join(secret, filename) }
uploader "PersonalFileUploader" uploader "PersonalFileUploader"
path { File.join(secret, filename) }
model { build(:personal_snippet) }
secret SecureRandom.hex secret SecureRandom.hex
end end
trait :issuable_upload do trait :issuable_upload do
path { File.join(secret, filename) }
uploader "FileUploader" uploader "FileUploader"
path { File.join(secret, filename) }
secret SecureRandom.hex secret SecureRandom.hex
end end
......
/* global BoardService */ /* global BoardService */
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue'; import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import AssigneeSelect from '~/boards/components/assignee_select.vue'; import AssigneeSelect from '~/boards/components/assignee_select.vue';
import '~/boards/services/board_service'; import '~/boards/services/board_service';
import '~/boards/stores/boards_store'; import '~/boards/stores/boards_store';
...@@ -86,12 +87,18 @@ describe('Assignee select component', () => { ...@@ -86,12 +87,18 @@ describe('Assignee select component', () => {
}); });
describe('clicking dropdown items', () => { describe('clicking dropdown items', () => {
let mock;
beforeEach(() => { beforeEach(() => {
const deferred = new jQuery.Deferred(); mock = new MockAdapter(axios);
spyOn($, 'ajax').and.returnValue(deferred.resolve([ mock.onGet('/autocomplete/users.json').reply(200, [
assignee, assignee,
assignee2, assignee2,
])); ]);
});
afterEach(() => {
mock.restore();
}); });
it('sets assignee', (done) => { it('sets assignee', (done) => {
...@@ -99,12 +106,12 @@ describe('Assignee select component', () => { ...@@ -99,12 +106,12 @@ describe('Assignee select component', () => {
setTimeout(() => { setTimeout(() => {
vm.$el.querySelectorAll('li a')[2].click(); vm.$el.querySelectorAll('li a')[2].click();
});
setTimeout(() => { setTimeout(() => {
expect(activeDropdownItem(0)).toEqual('second assignee'); expect(activeDropdownItem(0)).toEqual('second assignee');
expect(vm.board.assignee).toEqual(assignee2); expect(vm.board.assignee).toEqual(assignee2);
done(); done();
});
}); });
}); });
}); });
......
/* eslint-disable no-new */ /* eslint-disable no-new */
import _ from 'underscore'; import _ from 'underscore';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar'; import Sidebar from '~/right_sidebar';
import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Issuable right sidebar collapsed todo toggle', () => { describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html.raw'; const fixtureName = 'issues/open-issue.html.raw';
const jsonFixtureName = 'todos/todos.json'; const jsonFixtureName = 'todos/todos.json';
let mock;
preloadFixtures(fixtureName); preloadFixtures(fixtureName);
preloadFixtures(jsonFixtureName); preloadFixtures(jsonFixtureName);
...@@ -19,19 +23,26 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -19,19 +23,26 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document.querySelector('.js-right-sidebar') document.querySelector('.js-right-sidebar')
.classList.toggle('right-sidebar-collapsed'); .classList.toggle('right-sidebar-collapsed');
spyOn(jQuery, 'ajax').and.callFake((res) => { mock = new MockAdapter(axios);
const d = $.Deferred();
mock.onPost(`${gl.TEST_HOST}/frontend-fixtures/issues-project/todos`).reply(() => {
const response = _.clone(todoData); const response = _.clone(todoData);
if (res.type === 'DELETE') { return [200, response];
delete response.delete_path; });
}
d.resolve(response); mock.onDelete(/(.*)\/dashboard\/todos\/\d+$/).reply(() => {
return d.promise(); const response = _.clone(todoData);
delete response.delete_path;
return [200, response];
}); });
}); });
afterEach(() => {
mock.restore();
});
it('shows add todo button', () => { it('shows add todo button', () => {
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon'), document.querySelector('.js-issuable-todo.sidebar-collapsed-icon'),
...@@ -52,71 +63,101 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -52,71 +63,101 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
).toBe('Add todo'); ).toBe('Add todo');
}); });
it('toggle todo state', () => { it('toggle todo state', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( setTimeout(() => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), expect(
).not.toBeNull(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).not.toBeNull();
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'), document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'),
).not.toBeNull(); ).not.toBeNull();
});
it('toggle todo state of expanded todo toggle', () => { done();
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); });
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
).toBe('Mark done');
}); });
it('toggles todo button tooltip', () => { it('toggle todo state of expanded todo toggle', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( setTimeout(() => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'), expect(
).toBe('Mark done'); document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
}); ).toBe('Mark done');
it('marks todo as done', () => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( done();
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), });
).not.toBeNull(); });
it('toggles todo button tooltip', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( setTimeout(() => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), expect(
).toBeNull(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'),
).toBe('Mark done');
expect( done();
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(), });
).toBe('Add todo');
}); });
it('updates aria-label to mark done', () => { it('marks todo as done', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( timeoutPromise()
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), .then(() => {
).toBe('Mark done'); expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).not.toBeNull();
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
})
.then(timeoutPromise)
.then(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).toBeNull();
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
).toBe('Add todo');
})
.then(done)
.catch(done.fail);
}); });
it('updates aria-label to add todo', () => { it('updates aria-label to mark done', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( setTimeout(() => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), expect(
).toBe('Mark done'); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
).toBe('Mark done');
done();
});
});
it('updates aria-label to add todo', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect( timeoutPromise()
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), .then(() => {
).toBe('Add todo'); expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
).toBe('Mark done');
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
})
.then(timeoutPromise)
.then(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
).toBe('Add todo');
})
.then(done)
.catch(done.fail);
}); });
}); });
...@@ -70,23 +70,6 @@ describe('Issue', function() { ...@@ -70,23 +70,6 @@ describe('Issue', function() {
expect($btn).toHaveText(isIssueInitiallyOpen ? 'Close issue' : 'Reopen issue'); expect($btn).toHaveText(isIssueInitiallyOpen ? 'Close issue' : 'Reopen issue');
} }
describe('task lists', function() {
beforeEach(function() {
loadFixtures('issues/issue-with-task-list.html.raw');
this.issue = new Issue();
});
it('submits an ajax request on tasklist:changed', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) {
expect(req.type).toBe('PATCH');
expect(req.url).toBe(gl.TEST_HOST + '/frontend-fixtures/issues-project/issues/1.json'); // eslint-disable-line prefer-template
expect(req.data.issue.description).not.toBe(null);
});
$('.js-task-list-field').trigger('tasklist:changed');
});
});
[true, false].forEach((isIssueInitiallyOpen) => { [true, false].forEach((isIssueInitiallyOpen) => {
describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() { describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
const action = isIssueInitiallyOpen ? 'close' : 'reopen'; const action = isIssueInitiallyOpen ? 'close' : 'reopen';
......
/* eslint-disable space-before-function-paren, no-return-assign */ /* eslint-disable space-before-function-paren, no-return-assign */
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import MergeRequest from '~/merge_request'; import MergeRequest from '~/merge_request';
import CloseReopenReportToggle from '~/close_reopen_report_toggle'; import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper'; import IssuablesHelper from '~/helpers/issuables_helper';
...@@ -7,11 +8,24 @@ import IssuablesHelper from '~/helpers/issuables_helper'; ...@@ -7,11 +8,24 @@ import IssuablesHelper from '~/helpers/issuables_helper';
(function() { (function() {
describe('MergeRequest', function() { describe('MergeRequest', function() {
describe('task lists', function() { describe('task lists', function() {
let mock;
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() { beforeEach(function() {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
spyOn(axios, 'patch').and.callThrough();
mock = new MockAdapter(axios);
mock.onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`).reply(200, {});
return this.merge = new MergeRequest(); return this.merge = new MergeRequest();
}); });
afterEach(() => {
mock.restore();
});
it('modifies the Markdown field', function() { it('modifies the Markdown field', function() {
spyOn(jQuery, 'ajax').and.stub(); spyOn(jQuery, 'ajax').and.stub();
const changeEvent = document.createEvent('HTMLEvents'); const changeEvent = document.createEvent('HTMLEvents');
...@@ -21,14 +35,14 @@ import IssuablesHelper from '~/helpers/issuables_helper'; ...@@ -21,14 +35,14 @@ import IssuablesHelper from '~/helpers/issuables_helper';
}); });
it('submits an ajax request on tasklist:changed', (done) => { it('submits an ajax request on tasklist:changed', (done) => {
spyOn(jQuery, 'ajax').and.callFake((req) => { $('.js-task-list-field').trigger('tasklist:changed');
expect(req.type).toBe('PATCH');
expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`); setTimeout(() => {
expect(req.data.merge_request.description).not.toBe(null); expect(axios.patch).toHaveBeenCalledWith(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`, {
merge_request: { description: '- [ ] Task List Item' },
});
done(); done();
}); });
$('.js-task-list-field').trigger('tasklist:changed');
}); });
}); });
......
...@@ -50,13 +50,24 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ...@@ -50,13 +50,24 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
}); });
describe('task lists', function() { describe('task lists', function() {
let mock;
beforeEach(function() { beforeEach(function() {
spyOn(axios, 'patch').and.callThrough();
mock = new MockAdapter(axios);
mock.onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`).reply(200, {});
$('.js-comment-button').on('click', function(e) { $('.js-comment-button').on('click', function(e) {
e.preventDefault(); e.preventDefault();
}); });
this.notes = new Notes('', []); this.notes = new Notes('', []);
}); });
afterEach(() => {
mock.restore();
});
it('modifies the Markdown field', function() { it('modifies the Markdown field', function() {
const changeEvent = document.createEvent('HTMLEvents'); const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true); changeEvent.initEvent('change', true, true);
...@@ -65,14 +76,15 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ...@@ -65,14 +76,15 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item'); expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item');
}); });
it('submits an ajax request on tasklist:changed', function() { it('submits an ajax request on tasklist:changed', function(done) {
spyOn(jQuery, 'ajax').and.callFake(function(req) { $('.js-task-list-container').trigger('tasklist:changed');
expect(req.type).toBe('PATCH');
expect(req.url).toBe('http://test.host/frontend-fixtures/merge-requests-project/merge_requests/1.json');
return expect(req.data.note).not.toBe(null);
});
$('.js-task-list-field.js-note-text').trigger('tasklist:changed'); setTimeout(() => {
expect(axios.patch).toHaveBeenCalledWith(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`, {
note: { note: '' },
});
done();
});
}); });
}); });
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
import PANEL_STATE from '~/prometheus_metrics/constants'; import PANEL_STATE from '~/prometheus_metrics/constants';
import { metrics, missingVarMetrics } from './mock_data'; import { metrics, missingVarMetrics } from './mock_data';
...@@ -102,25 +104,38 @@ describe('PrometheusMetrics', () => { ...@@ -102,25 +104,38 @@ describe('PrometheusMetrics', () => {
describe('loadActiveMetrics', () => { describe('loadActiveMetrics', () => {
let prometheusMetrics; let prometheusMetrics;
let mock;
function mockSuccess() {
mock.onGet(prometheusMetrics.activeMetricsEndpoint).reply(200, {
data: metrics,
success: true,
});
}
function mockError() {
mock.onGet(prometheusMetrics.activeMetricsEndpoint).networkError();
}
beforeEach(() => { beforeEach(() => {
spyOn(axios, 'get').and.callThrough();
prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
}); });
it('should show loader animation while response is being loaded and hide it when request is complete', (done) => { it('should show loader animation while response is being loaded and hide it when request is complete', (done) => {
const deferred = $.Deferred(); mockSuccess();
spyOn($, 'ajax').and.returnValue(deferred.promise());
prometheusMetrics.loadActiveMetrics(); prometheusMetrics.loadActiveMetrics();
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy(); expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy();
expect($.ajax).toHaveBeenCalledWith({ expect(axios.get).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint);
url: prometheusMetrics.activeMetricsEndpoint,
dataType: 'json',
global: false,
});
deferred.resolve({ data: metrics, success: true });
setTimeout(() => { setTimeout(() => {
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy(); expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
...@@ -129,14 +144,10 @@ describe('PrometheusMetrics', () => { ...@@ -129,14 +144,10 @@ describe('PrometheusMetrics', () => {
}); });
it('should show empty state if response failed to load', (done) => { it('should show empty state if response failed to load', (done) => {
const deferred = $.Deferred(); mockError();
spyOn($, 'ajax').and.returnValue(deferred.promise());
spyOn(prometheusMetrics, 'populateActiveMetrics');
prometheusMetrics.loadActiveMetrics(); prometheusMetrics.loadActiveMetrics();
deferred.reject();
setTimeout(() => { setTimeout(() => {
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy(); expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy(); expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
...@@ -145,14 +156,11 @@ describe('PrometheusMetrics', () => { ...@@ -145,14 +156,11 @@ describe('PrometheusMetrics', () => {
}); });
it('should populate metrics list once response is loaded', (done) => { it('should populate metrics list once response is loaded', (done) => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
spyOn(prometheusMetrics, 'populateActiveMetrics'); spyOn(prometheusMetrics, 'populateActiveMetrics');
mockSuccess();
prometheusMetrics.loadActiveMetrics(); prometheusMetrics.loadActiveMetrics();
deferred.resolve({ data: metrics, success: true });
setTimeout(() => { setTimeout(() => {
expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics); expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
done(); done();
......
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */ /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
import MockAdapter from 'axios-mock-adapter';
import '~/commons/bootstrap'; import '~/commons/bootstrap';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar'; import Sidebar from '~/right_sidebar';
(function() { (function() {
...@@ -35,16 +37,23 @@ import Sidebar from '~/right_sidebar'; ...@@ -35,16 +37,23 @@ import Sidebar from '~/right_sidebar';
var fixtureName = 'issues/open-issue.html.raw'; var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName); preloadFixtures(fixtureName);
loadJSONFixtures('todos/todos.json'); loadJSONFixtures('todos/todos.json');
let mock;
beforeEach(function() { beforeEach(function() {
loadFixtures(fixtureName); loadFixtures(fixtureName);
this.sidebar = new Sidebar; mock = new MockAdapter(axios);
this.sidebar = new Sidebar();
$aside = $('.right-sidebar'); $aside = $('.right-sidebar');
$page = $('.layout-page'); $page = $('.layout-page');
$icon = $aside.find('i'); $icon = $aside.find('i');
$toggle = $aside.find('.js-sidebar-toggle'); $toggle = $aside.find('.js-sidebar-toggle');
return $labelsIcon = $aside.find('.sidebar-collapsed-icon'); return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
}); });
afterEach(() => {
mock.restore();
});
it('should expand/collapse the sidebar when arrow is clicked', function() { it('should expand/collapse the sidebar when arrow is clicked', function() {
assertSidebarState('expanded'); assertSidebarState('expanded');
$toggle.click(); $toggle.click();
...@@ -63,20 +72,19 @@ import Sidebar from '~/right_sidebar'; ...@@ -63,20 +72,19 @@ import Sidebar from '~/right_sidebar';
return assertSidebarState('collapsed'); return assertSidebarState('collapsed');
}); });
it('should broadcast todo:toggle event when add todo clicked', function() { it('should broadcast todo:toggle event when add todo clicked', function(done) {
var todos = getJSONFixture('todos/todos.json'); var todos = getJSONFixture('todos/todos.json');
spyOn(jQuery, 'ajax').and.callFake(function() { mock.onPost(/(.*)\/todos$/).reply(200, todos);
var d = $.Deferred();
var response = todos;
d.resolve(response);
return d.promise();
});
var todoToggleSpy = spyOnEvent(document, 'todo:toggle'); var todoToggleSpy = spyOnEvent(document, 'todo:toggle');
$('.issuable-sidebar-header .js-issuable-todo').click(); $('.issuable-sidebar-header .js-issuable-todo').click();
expect(todoToggleSpy.calls.count()).toEqual(1); setTimeout(() => {
expect(todoToggleSpy.calls.count()).toEqual(1);
done();
});
}); });
it('should not hide collapsed icons', () => { it('should not hide collapsed icons', () => {
......
...@@ -63,14 +63,14 @@ describe Gitlab::Kubernetes::Helm::Pod do ...@@ -63,14 +63,14 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should mount configMap specification in the volume' do it 'should mount configMap specification in the volume' do
spec = subject.generate.spec spec = subject.generate.spec
expect(spec.volumes.first.configMap['name']).to eq('values-content-configuration') expect(spec.volumes.first.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(spec.volumes.first.configMap['items'].first['key']).to eq('values') expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml') expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
end end
end end
context 'without a configuration file' do context 'without a configuration file' do
let(:app) { create(:clusters_applications_ingress, cluster: cluster) } let(:app) { create(:clusters_applications_helm, cluster: cluster) }
it_behaves_like 'helm pod' it_behaves_like 'helm pod'
......
...@@ -135,7 +135,7 @@ describe ProjectWiki do ...@@ -135,7 +135,7 @@ describe ProjectWiki do
end end
after do after do
destroy_page(subject.pages.first.page) subject.pages.each { |page| destroy_page(page.page) }
end end
it "returns the latest version of the page if it exists" do it "returns the latest version of the page if it exists" do
...@@ -156,6 +156,17 @@ describe ProjectWiki do ...@@ -156,6 +156,17 @@ describe ProjectWiki do
page = subject.find_page("index page") page = subject.find_page("index page")
expect(page).to be_a WikiPage expect(page).to be_a WikiPage
end end
context 'pages with multibyte-character title' do
before do
create_page("autre pagé", "C'est un génial Gollum Wiki")
end
it "can find a page by slug" do
page = subject.find_page("autre pagé")
expect(page.title).to eq("autre pagé")
end
end
end end
context 'when Gitaly wiki_find_page is enabled' do context 'when Gitaly wiki_find_page is enabled' do
......
...@@ -1482,28 +1482,34 @@ describe User do ...@@ -1482,28 +1482,34 @@ describe User do
describe '#sort' do describe '#sort' do
before do before do
described_class.delete_all described_class.delete_all
@user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' @user = create :user, created_at: Date.today, current_sign_in_at: Date.today, name: 'Alpha'
@user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' @user1 = create :user, created_at: Date.today - 1, current_sign_in_at: Date.today - 1, name: 'Omega'
@user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta' @user2 = create :user, created_at: Date.today - 2, name: 'Beta'
end end
context 'when sort by recent_sign_in' do context 'when sort by recent_sign_in' do
it 'sorts users by the recent sign-in time' do let(:users) { described_class.sort('recent_sign_in') }
expect(described_class.sort('recent_sign_in').first).to eq(@user)
it 'sorts users by recent sign-in time' do
expect(users.first).to eq(@user)
expect(users.second).to eq(@user1)
end end
it 'pushes users who never signed in to the end' do it 'pushes users who never signed in to the end' do
expect(described_class.sort('recent_sign_in').third).to eq(@user2) expect(users.third).to eq(@user2)
end end
end end
context 'when sort by oldest_sign_in' do context 'when sort by oldest_sign_in' do
let(:users) { described_class.sort('oldest_sign_in') }
it 'sorts users by the oldest sign-in time' do it 'sorts users by the oldest sign-in time' do
expect(described_class.sort('oldest_sign_in').first).to eq(@user1) expect(users.first).to eq(@user1)
expect(users.second).to eq(@user)
end end
it 'pushes users who never signed in to the end' do it 'pushes users who never signed in to the end' do
expect(described_class.sort('oldest_sign_in').third).to eq(@user2) expect(users.third).to eq(@user2)
end end
end end
......
...@@ -56,10 +56,11 @@ describe SystemNoteService do ...@@ -56,10 +56,11 @@ describe SystemNoteService do
expect(note_lines[0]).to eq "added #{new_commits.size} commits" expect(note_lines[0]).to eq "added #{new_commits.size} commits"
end end
it 'adds a message line for each commit' do it 'adds a message for each commit' do
new_commits.each_with_index do |commit, i| decoded_note_content = HTMLEntities.new.decode(subject.note)
# Skip the header
expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}" new_commits.each do |commit|
expect(decoded_note_content).to include("<li>#{commit.short_id} - #{commit.title}</li>")
end end
end end
end end
...@@ -71,7 +72,7 @@ describe SystemNoteService do ...@@ -71,7 +72,7 @@ describe SystemNoteService do
let(:old_commits) { [noteable.commits.last] } let(:old_commits) { [noteable.commits.last] }
it 'includes the existing commit' do it 'includes the existing commit' do
expect(summary_line).to eq "* #{old_commits.first.short_id} - 1 commit from branch `feature`" expect(summary_line).to start_with("<ul><li>#{old_commits.first.short_id} - 1 commit from branch <code>feature</code>")
end end
end end
...@@ -81,22 +82,16 @@ describe SystemNoteService do ...@@ -81,22 +82,16 @@ describe SystemNoteService do
context 'with oldrev' do context 'with oldrev' do
let(:oldrev) { noteable.commits[2].id } let(:oldrev) { noteable.commits[2].id }
it 'includes a commit range' do it 'includes a commit range and count' do
expect(summary_line).to start_with "* #{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id}" expect(summary_line)
end .to start_with("<ul><li>#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch <code>feature</code>")
it 'includes a commit count' do
expect(summary_line).to end_with " - 26 commits from branch `feature`"
end end
end end
context 'without oldrev' do context 'without oldrev' do
it 'includes a commit range' do it 'includes a commit range and count' do
expect(summary_line).to start_with "* #{old_commits[0].short_id}..#{old_commits[-1].short_id}" expect(summary_line)
end .to start_with("<ul><li>#{old_commits[0].short_id}..#{old_commits[-1].short_id} - 26 commits from branch <code>feature</code>")
it 'includes a commit count' do
expect(summary_line).to end_with " - 26 commits from branch `feature`"
end end
end end
...@@ -106,7 +101,7 @@ describe SystemNoteService do ...@@ -106,7 +101,7 @@ describe SystemNoteService do
end end
it 'includes the project namespace' do it 'includes the project namespace' do
expect(summary_line).to end_with "`#{noteable.target_project_namespace}:feature`" expect(summary_line).to include("<code>#{noteable.target_project_namespace}:feature</code>")
end end
end end
end end
...@@ -695,7 +690,7 @@ describe SystemNoteService do ...@@ -695,7 +690,7 @@ describe SystemNoteService do
describe '.new_commit_summary' do describe '.new_commit_summary' do
it 'escapes HTML titles' do it 'escapes HTML titles' do
commit = double(title: '<pre>This is a test</pre>', short_id: '12345678') commit = double(title: '<pre>This is a test</pre>', short_id: '12345678')
escaped = '&lt;pre&gt;This is a test&lt;&#x2F;pre&gt;' escaped = '&lt;pre&gt;This is a test&lt;/pre&gt;'
expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/)) expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/))
end end
......
controller:
image:
tag: "0.10.2"
repository: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller"
stats.enabled: true
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "10254"
...@@ -2,7 +2,7 @@ alertmanager: ...@@ -2,7 +2,7 @@ alertmanager:
enabled: false enabled: false
kubeStateMetrics: kubeStateMetrics:
enabled: false enabled: true
nodeExporter: nodeExporter:
enabled: false enabled: false
...@@ -10,11 +10,15 @@ nodeExporter: ...@@ -10,11 +10,15 @@ nodeExporter:
pushgateway: pushgateway:
enabled: false enabled: false
server:
image:
tag: v2.1.0
serverFiles: serverFiles:
alerts: "" alerts: {}
rules: "" rules: {}
prometheus.yml: |- prometheus.yml:
rule_files: rule_files:
- /etc/config/rules - /etc/config/rules
- /etc/config/alerts - /etc/config/alerts
...@@ -26,92 +30,108 @@ serverFiles: ...@@ -26,92 +30,108 @@ serverFiles:
- job_name: kubernetes-cadvisor - job_name: kubernetes-cadvisor
scheme: https scheme: https
tls_config: tls_config:
ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true insecure_skip_verify: true
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs: kubernetes_sd_configs:
- role: node - role: node
api_server: https://kubernetes.default.svc:443
tls_config:
ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
relabel_configs: relabel_configs:
- action: labelmap - action: labelmap
regex: __meta_kubernetes_node_label_(.+) regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__ - target_label: __address__
replacement: kubernetes.default.svc:443 replacement: kubernetes.default.svc:443
- source_labels: - source_labels:
- __meta_kubernetes_node_name - __meta_kubernetes_node_name
regex: "(.+)" regex: "(.+)"
target_label: __metrics_path__ target_label: __metrics_path__
replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor" replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
metric_relabel_configs: metric_relabel_configs:
- source_labels: - source_labels:
- pod_name - pod_name
target_label: environment target_label: environment
regex: "(.+)-.+-.+" regex: "(.+)-.+-.+"
- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)(?::\d+);(\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
- job_name: kubernetes-nodes - job_name: kubernetes-nodes
scheme: https scheme: https
tls_config: tls_config:
ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true insecure_skip_verify: true
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs: kubernetes_sd_configs:
- role: node - role: node
api_server: https://kubernetes.default.svc:443
tls_config:
ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
relabel_configs: relabel_configs:
- action: labelmap - action: labelmap
regex: __meta_kubernetes_node_label_(.+) regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__ - target_label: __address__
replacement: kubernetes.default.svc:443 replacement: kubernetes.default.svc:443
- source_labels: - source_labels:
- __meta_kubernetes_node_name - __meta_kubernetes_node_name
regex: "(.+)" regex: "(.+)"
target_label: __metrics_path__ target_label: __metrics_path__
replacement: "/api/v1/nodes/${1}/proxy/metrics" replacement: "/api/v1/nodes/${1}/proxy/metrics"
metric_relabel_configs: metric_relabel_configs:
- source_labels: - source_labels:
- pod_name - pod_name
target_label: environment target_label: environment
regex: "(.+)-.+-.+" regex: "(.+)-.+-.+"
- job_name: kubernetes-pods - job_name: kubernetes-pods
tls_config: tls_config:
ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true insecure_skip_verify: true
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs: kubernetes_sd_configs:
- role: pod - role: pod
api_server: https://kubernetes.default.svc:443
tls_config:
ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
relabel_configs: relabel_configs:
- source_labels: - source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scrape - __meta_kubernetes_pod_annotation_prometheus_io_scrape
action: keep action: keep
regex: 'true' regex: 'true'
- source_labels: - source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_path - __meta_kubernetes_pod_annotation_prometheus_io_path
action: replace action: replace
target_label: __metrics_path__ target_label: __metrics_path__
regex: "(.+)" regex: "(.+)"
- source_labels: - source_labels:
- __address__ - __address__
- __meta_kubernetes_pod_annotation_prometheus_io_port - __meta_kubernetes_pod_annotation_prometheus_io_port
action: replace action: replace
regex: "([^:]+)(?::[0-9]+)?;([0-9]+)" regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
replacement: "$1:$2" replacement: "$1:$2"
target_label: __address__ target_label: __address__
- action: labelmap - action: labelmap
regex: __meta_kubernetes_pod_label_(.+) regex: __meta_kubernetes_pod_label_(.+)
- source_labels: - source_labels:
- __meta_kubernetes_namespace - __meta_kubernetes_namespace
action: replace action: replace
target_label: kubernetes_namespace target_label: kubernetes_namespace
- source_labels: - source_labels:
- __meta_kubernetes_pod_name - __meta_kubernetes_pod_name
action: replace action: replace
target_label: kubernetes_pod_name target_label: kubernetes_pod_name
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