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:
sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest
variables:
CONFIDENCE_LEVEL: 2
before_script: []
script:
- /app/bin/run .
......
import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() {
......@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() {
if (message === '') {
$('.js-broadcast-message-preview').text('Your message here');
} else {
$.ajax({
url: previewPath,
type: 'POST',
data: {
broadcast_message: { message },
axios.post(previewPath, {
broadcast_message: {
message,
},
});
})
.then(({ data }) => {
$('.js-broadcast-message-preview').html(data.message);
})
.catch(() => flash(__('An error occurred while rendering preview broadcast message')));
}
}, 250));
}
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
......@@ -77,12 +80,9 @@ export default class UsernameValidator {
this.state.pending = true;
this.state.available = false;
this.renderState();
return $.ajax({
type: 'GET',
url: `${gon.relative_url_root}/users/${username}/exists`,
dataType: 'json',
success: (res) => this.setAvailabilityState(res.exists)
});
axios.get(`${gon.relative_url_root}/users/${username}/exists`)
.then(({ data }) => this.setAvailabilityState(data.exists))
.catch(() => flash(__('An error occurred while validating username')));
}
}
......
......@@ -7,6 +7,10 @@
// more than `x` users are referenced.
//
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
var lastTextareaPreviewed;
var lastTextareaHeight = null;
var markdownPreview;
......@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response);
return;
}
$.ajax({
type: 'POST',
url: url,
data: {
text: text
},
dataType: 'json',
success: (function (response) {
this.ajaxCache = {
text: text,
response: response
};
success(response);
}).bind(this)
});
axios.post(url, {
text,
})
.then(({ data }) => {
this.ajaxCache = {
text: text,
response: data,
};
success(data);
})
.catch(() => flash(__('An error occurred while fetching markdown preview')));
};
MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
......
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
export default class ProjectLabelSubscription {
constructor(container) {
this.$container = $(container);
......@@ -17,10 +21,7 @@ export default class ProjectLabelSubscription {
$btn.addClass('disabled');
$span.toggleClass('hidden');
$.ajax({
type: 'POST',
url,
}).done(() => {
axios.post(url).then(() => {
let newStatus;
let newAction;
......@@ -45,6 +46,6 @@ export default class ProjectLabelSubscription {
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 { backOff } from '../lib/utils/common_utils';
......@@ -81,24 +82,20 @@ export default class PrometheusMetrics {
loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
backOff((next, stop) => {
$.ajax({
url: this.activeMetricsEndpoint,
dataType: 'json',
global: false,
})
.done((res) => {
if (res && res.success) {
stop(res);
axios.get(this.activeMetricsEndpoint)
.then(({ data }) => {
if (data && data.success) {
stop(data);
} else {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < 3) {
next();
} else {
stop(res);
stop(data);
}
}
})
.fail(stop);
.catch(stop);
})
.then((res) => {
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';
export default class ProtectedBranchEdit {
......@@ -38,29 +38,25 @@ export default class ProtectedBranchEdit {
this.$allowedToMergeDropdown.disable();
this.$allowedToPushDropdown.disable();
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
dataType: 'json',
data: {
_method: 'PATCH',
protected_branch: {
merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'),
access_level: $allowedToMergeInput.val(),
}],
push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val(),
}],
},
axios.patch(this.$wrap.data('url'), {
protected_branch: {
merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'),
access_level: $allowedToMergeInput.val(),
}],
push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val(),
}],
},
error() {
new Flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list'));
},
}).always(() => {
}).then(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
}).catch(() => {
this.$allowedToMergeDropdown.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';
export default class ProtectedTagEdit {
......@@ -28,24 +28,19 @@ export default class ProtectedTagEdit {
this.$allowedToCreateDropdownButton.disable();
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
dataType: 'json',
data: {
_method: 'PATCH',
protected_tag: {
create_access_levels_attributes: [{
id: this.$allowedToCreateDropdownButton.data('access-level-id'),
access_level: $allowedToCreateInput.val(),
}],
},
axios.patch(this.$wrap.data('url'), {
protected_tag: {
create_access_levels_attributes: [{
id: this.$allowedToCreateDropdownButton.data('access-level-id'),
access_level: $allowedToCreateInput.val(),
}],
},
error() {
new Flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list'));
},
}).always(() => {
}).then(() => {
this.$allowedToCreateDropdownButton.enable();
}).catch(() => {
this.$allowedToCreateDropdownButton.enable();
flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list'));
});
}
}
......@@ -2,6 +2,8 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
import flash from './flash';
import axios from './lib/utils/axios_utils';
function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this);
......@@ -62,7 +64,7 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$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')) {
url = "" + ($this.attr('data-delete-path'));
} else {
......@@ -71,25 +73,14 @@ Sidebar.prototype.toggleTodo = function(e) {
$this.tooltip('hide');
return $.ajax({
url: url,
type: ajaxType,
dataType: 'json',
data: {
issuable_id: $this.data('issuable-id'),
issuable_type: $this.data('issuable-type')
},
beforeSend: (function(_this) {
return function() {
$('.js-issuable-todo').disable()
.addClass('is-loading');
};
})(this)
}).done((function(_this) {
return function(data) {
return _this.todoUpdateDone(data);
};
})(this));
$('.js-issuable-todo').disable().addClass('is-loading');
axios[ajaxType](url, {
issuable_id: $this.data('issuable-id'),
issuable_type: $this.data('issuable-type'),
}).then(({ data }) => {
this.todoUpdateDone(data);
}).catch(() => flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`));
};
Sidebar.prototype.todoUpdateDone = function(data) {
......
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import axios from './lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
import findAndFollowLink from './shortcuts_dashboard_navigation';
......@@ -85,21 +86,21 @@ export default class Shortcuts {
$modal.modal('toggle');
}
$.ajax({
url: gon.shortcuts_path,
dataType: 'script',
success() {
if (location && location.length > 0) {
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) {
results.push($(location[i]).show());
}
return results;
return axios.get(gon.shortcuts_path, {
responseType: 'text',
}).then(({ data }) => {
$.globalEval(data);
if (location && location.length > 0) {
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) {
results.push($(location[i]).show());
}
return results;
}
$('.hidden-shortcut').show();
return $('.js-more-help-button').remove();
},
$('.hidden-shortcut').show();
return $('.js-more-help-button').remove();
});
}
......
import 'deckar01-task_list';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
export default class TaskList {
......@@ -7,11 +8,11 @@ export default class TaskList {
this.dataType = options.dataType;
this.fieldName = options.fieldName;
this.onSuccess = options.onSuccess || (() => {});
this.onError = function showFlash(response) {
this.onError = function showFlash(e) {
let errorMessages = '';
if (response.responseJSON) {
errorMessages = response.responseJSON.errors.join(' ');
if (e.response.data && typeof e.response.data === 'object') {
errorMessages = e.response.data.errors.join(' ');
}
return new Flash(errorMessages || 'Update failed', 'alert');
......@@ -38,12 +39,9 @@ export default class TaskList {
patchData[this.dataType] = {
[this.fieldName]: $target.val(),
};
return $.ajax({
type: 'PATCH',
url: $target.data('update-url') || $('form.js-issuable-update').attr('action'),
data: patchData,
success: this.onSuccess,
error: this.onError,
});
return axios.patch($target.data('update-url') || $('form.js-issuable-update').attr('action'), patchData)
.then(({ data }) => this.onSuccess(data))
.catch(err => this.onError(err));
}
}
import axios from '../lib/utils/axios_utils';
import Activities from '../activities';
import ActivityCalendar from './activity_calendar';
import { localTimeAgo } from '../lib/utils/datetime_utility';
import { __ } from '../locale';
import flash from '../flash';
/**
* UserTabs
......@@ -131,18 +134,20 @@ export default class UserTabs {
}
loadTab(action, endpoint) {
return $.ajax({
beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false),
dataType: 'json',
url: endpoint,
success: (data) => {
this.toggleLoading(true);
return axios.get(endpoint)
.then(({ data }) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
localTimeAgo($('.js-timeago', tabSelector));
},
});
this.toggleLoading(false);
})
.catch(() => {
this.toggleLoading(false);
});
}
loadActivities() {
......@@ -158,17 +163,15 @@ export default class UserTabs {
utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`;
}
$.ajax({
dataType: 'json',
url: calendarPath,
success: (activityData) => {
axios.get(calendarPath)
.then(({ data }) => {
$calendarWrap.html(CALENDAR_TEMPLATE);
$calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
// 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
new Activities();
......
......@@ -2,6 +2,7 @@
/* global Issuable */
/* global emitSidebarEvent */
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
......@@ -177,32 +178,28 @@ function UsersSelect(currentUser, els, options = {}) {
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return $.ajax({
type: 'PUT',
dataType: 'json',
url: issueURL,
data: data
}).done(function(data) {
var user;
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
if (data.assignee) {
user = {
name: data.assignee.name,
username: data.assignee.username,
avatar: data.assignee.avatar_url
};
} else {
user = {
name: 'Unassigned',
username: '',
avatar: ''
};
}
$value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
});
return axios.put(issueURL, data)
.then(({ data }) => {
var user;
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
if (data.assignee) {
user = {
name: data.assignee.name,
username: data.assignee.username,
avatar: data.assignee.avatar_url
};
} else {
user = {
name: 'Unassigned',
username: '',
avatar: ''
};
}
$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> <% } %>');
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) {
var url;
url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id);
return $.ajax({
url: url,
dataType: "json"
}).done(function(user) {
return callback(user);
});
return axios.get(url)
.then(({ data }) => {
callback(data);
});
};
// Return users list. Filtered by query
// Only active users retrieved
UsersSelect.prototype.users = function(query, options, callback) {
var url;
url = this.buildUrl(this.usersPath);
return $.ajax({
url: url,
data: {
search: query,
per_page: options.perPage || 20,
active: true,
project_id: options.projectId || null,
group_id: options.groupId || null,
skip_ldap: options.skipLdap || null,
todo_filter: options.todoFilter || null,
todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null,
author_id: options.authorId || null,
skip_users: options.skipUsers || null
},
dataType: "json"
}).done(function(users) {
return callback(users);
});
const url = this.buildUrl(this.usersPath);
const params = {
search: query,
per_page: options.perPage || 20,
active: true,
project_id: options.projectId || null,
group_id: options.groupId || null,
skip_ldap: options.skipLdap || null,
todo_filter: options.todoFilter || null,
todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null,
author_id: options.authorId || null,
skip_users: options.skipUsers || null
};
return axios.get(url, { params })
.then(({ data }) => {
callback(data);
});
};
UsersSelect.prototype.buildUrl = function(url) {
......
......@@ -118,7 +118,7 @@
<template>
<div class="branch-commit">
<template v-if="hasCommitRef && showBranch">
<div class="icon-container hidden-xs">
<div class="icon-container">
<i
v-if="tag"
class="fa fa-tag"
......@@ -132,7 +132,7 @@
</div>
<a
class="ref-name hidden-xs"
class="ref-name"
:href="commitRef.ref_url"
v-tooltip
data-container="body"
......
......@@ -148,7 +148,7 @@
.ref-name {
font-weight: $gl-font-weight-bold;
max-width: 120px;
max-width: 100px;
overflow: hidden;
display: inline-block;
white-space: nowrap;
......
class Admin::BroadcastMessagesController < Admin::ApplicationController
include BroadcastMessagesHelper
before_action :finder, only: [:edit, :update, :destroy]
def index
......@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end
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
protected
......
......@@ -4,6 +4,8 @@ class RegistrationsController < Devise::RegistrationsController
before_action :whitelist_query_limiting, only: [:destroy]
before_action :whitelist_query_limiting, only: [:destroy]
def new
redirect_to(new_user_session_path)
end
......
......@@ -17,8 +17,12 @@ module Clusters
'stable/nginx-ingress'
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
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
......
......@@ -230,6 +230,7 @@ class Namespace < ActiveRecord::Base
has_parent?
end
## EE only
def multiple_issue_boards_available?(user = nil)
feature_available?(:multiple_issue_boards)
end
......
......@@ -239,8 +239,8 @@ class User < ActiveRecord::Base
joins(:identities).where(identities: { provider: provider })
end
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_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) }
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('current_sign_in_at', 'ASC')) }
def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
......
......@@ -213,7 +213,7 @@ class WikiPage
last_commit_sha = attrs.delete(: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
update_attributes(attrs)
......@@ -223,7 +223,7 @@ class WikiPage
if wiki.find_page(page_details).present?
@attributes[:title] = @page.url_path
raise PageRenameError.new("WikiEdit|There is already a page with the same title in that path.")
raise PageRenameError
end
else
page_details = @page.url_path
......
......@@ -22,8 +22,7 @@ module SystemNoteService
commits_text = "#{total_count} commit".pluralize(total_count)
body = "added #{commits_text}\n\n"
body << existing_commit_summary(noteable, existing_commits, oldrev)
body << new_commit_summary(new_commits).join("\n")
body << commits_list(noteable, new_commits, existing_commits, 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))
......@@ -481,7 +480,7 @@ module SystemNoteService
# Returns an Array of Strings
def new_commit_summary(new_commits)
new_commits.collect do |commit|
"* #{commit.short_id} - #{escape_html(commit.title)}"
content_tag('li', "#{commit.short_id} - #{commit.title}")
end
end
......@@ -709,6 +708,16 @@ module SystemNoteService
"#{cross_reference_note_prefix}#{gfm_reference}"
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
# request
#
......@@ -745,11 +754,8 @@ module SystemNoteService
branch = noteable.target_branch
branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?
"* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
end
def escape_html(text)
Rack::Utils.escape_html(text)
branch_name = content_tag('code', branch)
content_tag('li', "#{commit_ids} - #{commits_text} from branch #{branch_name}".html_safe)
end
def url_helpers
......@@ -766,4 +772,8 @@ module SystemNoteService
start_sha: oldrev
)
end
def content_tag(*args)
ActionController::Base.helpers.content_tag(*args)
end
end
$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@broadcast_message))}");
......@@ -47,7 +47,7 @@
- if @repository.gitlab_ci_yml
%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)
- unless @repository.changelog
......@@ -65,7 +65,7 @@
- unless @repository.gitlab_ci_yml
%li.missing
= 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?
%li.missing
= link_to _('Set up Koding'), add_koding_stack_path(@project)
......
......@@ -24,7 +24,7 @@
.wiki
= 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]
- 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
Gitlab::BackgroundMigration.steal('CopyColumn')
Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange')
# It's possible the cleanup job was killed which means we need to manually
# migrate any remaining rows.
migrate_remaining_rows if migrate_column_type?
if migrate_column_type?
if closed_at_for_type_change_exists?
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
def down
# Previous migrations already revert the changes made here.
end
def migrate_remaining_rows
......@@ -39,4 +48,8 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
# migration, thus we don't need to migrate those environments again.
column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime
end
def closed_at_for_type_change_exists?
columns('issues').any? { |col| col.name == 'closed_at_for_type_change' }
end
end
......@@ -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
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"
1. Pick `.gitlab-ci.yml` as the template type
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:
### (EEP) 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
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
can be `None`, `Masters`, or `Developers + Masters`.
can be `None`, `Masters`, or `Developers + Masters`.
It is available only in [GitLab Enterprise Edition Premium][eep].
......
......@@ -77,7 +77,7 @@ is useful for submitting merge requests to the upstream.
>
> 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,
but not to forks.
......@@ -88,9 +88,9 @@ click **New project**, and name it considering the
[practical examples](getting_started_part_one.md#practical-examples).
1. Clone it to your local computer, add your website
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.
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)
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
navigating to your **Project**'s **Settings** > **Pages**,
where you'll find its default URL.
......
......@@ -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)
>**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.
## Upload a file
......
......@@ -4,6 +4,7 @@ module Gitlab
ATTRS = %i(title format url_path path name historical raw_data).freeze
include AttributesBag
include Gitlab::EncodingHelper
def initialize(params)
super
......@@ -11,6 +12,10 @@ module Gitlab
# All gRPC strings in a response are frozen, so we get an unfrozen
# version here so appending to `raw_data` doesn't blow up.
@raw_data = @raw_data.dup
@title = encode_utf8(@title)
@path = encode_utf8(@path)
@name = encode_utf8(@name)
end
def historical?
......
......@@ -64,7 +64,7 @@ module Gitlab
{
name: 'configuration-volume',
configMap: {
name: 'values-content-configuration',
name: "values-content-configuration-#{command.name}",
items: [{ key: 'values', path: 'values.yaml' }]
}
}
......@@ -81,7 +81,11 @@ module Gitlab
def create_config_map
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) }
kubeclient.create_config_map(resource)
end
......
......@@ -467,7 +467,7 @@ msgstr ""
msgid "CI / CD"
msgstr ""
msgid "CI configuration"
msgid "CI/CD configuration"
msgstr ""
msgid "CICD|Jobs"
......@@ -2574,7 +2574,7 @@ msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
msgid "Set up CI"
msgid "Set up CI/CD"
msgstr ""
msgid "Set up Koding"
......
......@@ -3,7 +3,6 @@ module QA
module Project
class Show < Page::Base
view 'app/views/shared/_clone_panel.html.haml' do
element :clone_holder, '.git-clone-holder'
element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown'
element :project_repository_location, 'text_field_tag :project_clone'
......@@ -31,7 +30,7 @@ module QA
end
# 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
......
......@@ -16,15 +16,15 @@ FactoryBot.define do
path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') }
trait :personal_snippet_upload do
model { build(:personal_snippet) }
path { File.join(secret, filename) }
uploader "PersonalFileUploader"
path { File.join(secret, filename) }
model { build(:personal_snippet) }
secret SecureRandom.hex
end
trait :issuable_upload do
path { File.join(secret, filename) }
uploader "FileUploader"
path { File.join(secret, filename) }
secret SecureRandom.hex
end
......
/* global BoardService */
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import AssigneeSelect from '~/boards/components/assignee_select.vue';
import '~/boards/services/board_service';
import '~/boards/stores/boards_store';
......@@ -86,12 +87,18 @@ describe('Assignee select component', () => {
});
describe('clicking dropdown items', () => {
let mock;
beforeEach(() => {
const deferred = new jQuery.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.resolve([
mock = new MockAdapter(axios);
mock.onGet('/autocomplete/users.json').reply(200, [
assignee,
assignee2,
]));
]);
});
afterEach(() => {
mock.restore();
});
it('sets assignee', (done) => {
......@@ -99,12 +106,12 @@ describe('Assignee select component', () => {
setTimeout(() => {
vm.$el.querySelectorAll('li a')[2].click();
});
setTimeout(() => {
expect(activeDropdownItem(0)).toEqual('second assignee');
expect(vm.board.assignee).toEqual(assignee2);
done();
setTimeout(() => {
expect(activeDropdownItem(0)).toEqual('second assignee');
expect(vm.board.assignee).toEqual(assignee2);
done();
});
});
});
});
......
/* eslint-disable no-new */
import _ from 'underscore';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar';
import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html.raw';
const jsonFixtureName = 'todos/todos.json';
let mock;
preloadFixtures(fixtureName);
preloadFixtures(jsonFixtureName);
......@@ -19,19 +23,26 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document.querySelector('.js-right-sidebar')
.classList.toggle('right-sidebar-collapsed');
spyOn(jQuery, 'ajax').and.callFake((res) => {
const d = $.Deferred();
mock = new MockAdapter(axios);
mock.onPost(`${gl.TEST_HOST}/frontend-fixtures/issues-project/todos`).reply(() => {
const response = _.clone(todoData);
if (res.type === 'DELETE') {
delete response.delete_path;
}
return [200, response];
});
d.resolve(response);
return d.promise();
mock.onDelete(/(.*)\/dashboard\/todos\/\d+$/).reply(() => {
const response = _.clone(todoData);
delete response.delete_path;
return [200, response];
});
});
afterEach(() => {
mock.restore();
});
it('shows add todo button', () => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon'),
......@@ -52,71 +63,101 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
).toBe('Add todo');
});
it('toggle todo state', () => {
it('toggle todo state', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).not.toBeNull();
setTimeout(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).not.toBeNull();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'),
).not.toBeNull();
});
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'),
).not.toBeNull();
it('toggle todo state of expanded todo toggle', () => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
).toBe('Mark done');
done();
});
});
it('toggles todo button tooltip', () => {
it('toggle todo state of expanded todo toggle', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'),
).toBe('Mark done');
});
it('marks todo as done', () => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => {
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
).toBe('Mark done');
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).not.toBeNull();
done();
});
});
it('toggles todo button tooltip', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).toBeNull();
setTimeout(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'),
).toBe('Mark done');
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
).toBe('Add todo');
done();
});
});
it('updates aria-label to mark done', () => {
it('marks todo as done', (done) => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
).toBe('Mark done');
timeoutPromise()
.then(() => {
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();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
).toBe('Mark done');
setTimeout(() => {
expect(
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();
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'),
).toBe('Add todo');
timeoutPromise()
.then(() => {
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() {
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) => {
describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
const action = isIssueInitiallyOpen ? 'close' : 'reopen';
......
/* 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 CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper';
......@@ -7,11 +8,24 @@ import IssuablesHelper from '~/helpers/issuables_helper';
(function() {
describe('MergeRequest', function() {
describe('task lists', function() {
let mock;
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() {
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();
});
afterEach(() => {
mock.restore();
});
it('modifies the Markdown field', function() {
spyOn(jQuery, 'ajax').and.stub();
const changeEvent = document.createEvent('HTMLEvents');
......@@ -21,14 +35,14 @@ import IssuablesHelper from '~/helpers/issuables_helper';
});
it('submits an ajax request on tasklist:changed', (done) => {
spyOn(jQuery, 'ajax').and.callFake((req) => {
expect(req.type).toBe('PATCH');
expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
expect(req.data.merge_request.description).not.toBe(null);
$('.js-task-list-field').trigger('tasklist:changed');
setTimeout(() => {
expect(axios.patch).toHaveBeenCalledWith(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`, {
merge_request: { description: '- [ ] Task List Item' },
});
done();
});
$('.js-task-list-field').trigger('tasklist:changed');
});
});
......
......@@ -50,13 +50,24 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
describe('task lists', function() {
let mock;
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) {
e.preventDefault();
});
this.notes = new Notes('', []);
});
afterEach(() => {
mock.restore();
});
it('modifies the Markdown field', function() {
const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true);
......@@ -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');
});
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('http://test.host/frontend-fixtures/merge-requests-project/merge_requests/1.json');
return expect(req.data.note).not.toBe(null);
});
it('submits an ajax request on tasklist:changed', function(done) {
$('.js-task-list-container').trigger('tasklist:changed');
$('.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 PANEL_STATE from '~/prometheus_metrics/constants';
import { metrics, missingVarMetrics } from './mock_data';
......@@ -102,25 +104,38 @@ describe('PrometheusMetrics', () => {
describe('loadActiveMetrics', () => {
let prometheusMetrics;
let mock;
function mockSuccess() {
mock.onGet(prometheusMetrics.activeMetricsEndpoint).reply(200, {
data: metrics,
success: true,
});
}
function mockError() {
mock.onGet(prometheusMetrics.activeMetricsEndpoint).networkError();
}
beforeEach(() => {
spyOn(axios, 'get').and.callThrough();
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) => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
mockSuccess();
prometheusMetrics.loadActiveMetrics();
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy();
expect($.ajax).toHaveBeenCalledWith({
url: prometheusMetrics.activeMetricsEndpoint,
dataType: 'json',
global: false,
});
deferred.resolve({ data: metrics, success: true });
expect(axios.get).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint);
setTimeout(() => {
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
......@@ -129,14 +144,10 @@ describe('PrometheusMetrics', () => {
});
it('should show empty state if response failed to load', (done) => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
spyOn(prometheusMetrics, 'populateActiveMetrics');
mockError();
prometheusMetrics.loadActiveMetrics();
deferred.reject();
setTimeout(() => {
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
......@@ -145,14 +156,11 @@ describe('PrometheusMetrics', () => {
});
it('should populate metrics list once response is loaded', (done) => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
spyOn(prometheusMetrics, 'populateActiveMetrics');
mockSuccess();
prometheusMetrics.loadActiveMetrics();
deferred.resolve({ data: metrics, success: true });
setTimeout(() => {
expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
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 */
import MockAdapter from 'axios-mock-adapter';
import '~/commons/bootstrap';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar';
(function() {
......@@ -35,16 +37,23 @@ import Sidebar from '~/right_sidebar';
var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName);
loadJSONFixtures('todos/todos.json');
let mock;
beforeEach(function() {
loadFixtures(fixtureName);
this.sidebar = new Sidebar;
mock = new MockAdapter(axios);
this.sidebar = new Sidebar();
$aside = $('.right-sidebar');
$page = $('.layout-page');
$icon = $aside.find('i');
$toggle = $aside.find('.js-sidebar-toggle');
return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
});
afterEach(() => {
mock.restore();
});
it('should expand/collapse the sidebar when arrow is clicked', function() {
assertSidebarState('expanded');
$toggle.click();
......@@ -63,20 +72,19 @@ import Sidebar from '~/right_sidebar';
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');
spyOn(jQuery, 'ajax').and.callFake(function() {
var d = $.Deferred();
var response = todos;
d.resolve(response);
return d.promise();
});
mock.onPost(/(.*)\/todos$/).reply(200, todos);
var todoToggleSpy = spyOnEvent(document, 'todo:toggle');
$('.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', () => {
......
......@@ -63,14 +63,14 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should mount configMap specification in the volume' do
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['path']).to eq('values.yaml')
end
end
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'
......
......@@ -135,7 +135,7 @@ describe ProjectWiki do
end
after do
destroy_page(subject.pages.first.page)
subject.pages.each { |page| destroy_page(page.page) }
end
it "returns the latest version of the page if it exists" do
......@@ -156,6 +156,17 @@ describe ProjectWiki do
page = subject.find_page("index page")
expect(page).to be_a WikiPage
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
context 'when Gitaly wiki_find_page is enabled' do
......
......@@ -1482,28 +1482,34 @@ describe User do
describe '#sort' do
before do
described_class.delete_all
@user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
@user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
@user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta'
@user = create :user, created_at: Date.today, current_sign_in_at: Date.today, name: 'Alpha'
@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, name: 'Beta'
end
context 'when sort by recent_sign_in' do
it 'sorts users by the recent sign-in time' do
expect(described_class.sort('recent_sign_in').first).to eq(@user)
let(:users) { described_class.sort('recent_sign_in') }
it 'sorts users by recent sign-in time' do
expect(users.first).to eq(@user)
expect(users.second).to eq(@user1)
end
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
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
expect(described_class.sort('oldest_sign_in').first).to eq(@user1)
expect(users.first).to eq(@user1)
expect(users.second).to eq(@user)
end
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
......
......@@ -56,10 +56,11 @@ describe SystemNoteService do
expect(note_lines[0]).to eq "added #{new_commits.size} commits"
end
it 'adds a message line for each commit' do
new_commits.each_with_index do |commit, i|
# Skip the header
expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}"
it 'adds a message for each commit' do
decoded_note_content = HTMLEntities.new.decode(subject.note)
new_commits.each do |commit|
expect(decoded_note_content).to include("<li>#{commit.short_id} - #{commit.title}</li>")
end
end
end
......@@ -71,7 +72,7 @@ describe SystemNoteService do
let(:old_commits) { [noteable.commits.last] }
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
......@@ -81,22 +82,16 @@ describe SystemNoteService do
context 'with oldrev' do
let(:oldrev) { noteable.commits[2].id }
it 'includes a commit range' do
expect(summary_line).to start_with "* #{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id}"
end
it 'includes a commit count' do
expect(summary_line).to end_with " - 26 commits from branch `feature`"
it 'includes a commit range and count' do
expect(summary_line)
.to start_with("<ul><li>#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch <code>feature</code>")
end
end
context 'without oldrev' do
it 'includes a commit range' do
expect(summary_line).to start_with "* #{old_commits[0].short_id}..#{old_commits[-1].short_id}"
end
it 'includes a commit count' do
expect(summary_line).to end_with " - 26 commits from branch `feature`"
it 'includes a commit range and count' do
expect(summary_line)
.to start_with("<ul><li>#{old_commits[0].short_id}..#{old_commits[-1].short_id} - 26 commits from branch <code>feature</code>")
end
end
......@@ -106,7 +101,7 @@ describe SystemNoteService do
end
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
......@@ -695,7 +690,7 @@ describe SystemNoteService do
describe '.new_commit_summary' do
it 'escapes HTML titles' do
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}/))
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:
enabled: false
kubeStateMetrics:
enabled: false
enabled: true
nodeExporter:
enabled: false
......@@ -10,11 +10,15 @@ nodeExporter:
pushgateway:
enabled: false
server:
image:
tag: v2.1.0
serverFiles:
alerts: ""
rules: ""
alerts: {}
rules: {}
prometheus.yml: |-
prometheus.yml:
rule_files:
- /etc/config/rules
- /etc/config/alerts
......@@ -26,92 +30,108 @@ serverFiles:
- job_name: kubernetes-cadvisor
scheme: https
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
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- 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"
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels:
- __meta_kubernetes_node_name
regex: "(.+)"
target_label: __metrics_path__
replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels:
- __meta_kubernetes_node_name
regex: "(.+)"
target_label: __metrics_path__
replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
metric_relabel_configs:
- source_labels:
- pod_name
target_label: environment
regex: "(.+)-.+-.+"
- source_labels:
- pod_name
target_label: environment
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
scheme: https
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
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- 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"
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels:
- __meta_kubernetes_node_name
regex: "(.+)"
target_label: __metrics_path__
replacement: "/api/v1/nodes/${1}/proxy/metrics"
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels:
- __meta_kubernetes_node_name
regex: "(.+)"
target_label: __metrics_path__
replacement: "/api/v1/nodes/${1}/proxy/metrics"
metric_relabel_configs:
- source_labels:
- pod_name
target_label: environment
regex: "(.+)-.+-.+"
- source_labels:
- pod_name
target_label: environment
regex: "(.+)-.+-.+"
- job_name: kubernetes-pods
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
bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- 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"
- role: pod
relabel_configs:
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scrape
action: keep
regex: 'true'
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_path
action: replace
target_label: __metrics_path__
regex: "(.+)"
- source_labels:
- __address__
- __meta_kubernetes_pod_annotation_prometheus_io_port
action: replace
regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
replacement: "$1:$2"
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels:
- __meta_kubernetes_namespace
action: replace
target_label: kubernetes_namespace
- source_labels:
- __meta_kubernetes_pod_name
action: replace
target_label: kubernetes_pod_name
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scrape
action: keep
regex: 'true'
- source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_path
action: replace
target_label: __metrics_path__
regex: "(.+)"
- source_labels:
- __address__
- __meta_kubernetes_pod_annotation_prometheus_io_port
action: replace
regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
replacement: "$1:$2"
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels:
- __meta_kubernetes_namespace
action: replace
target_label: kubernetes_namespace
- source_labels:
- __meta_kubernetes_pod_name
action: replace
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