Merge branch 'master' into new-resolvable-discussion
|
|
@ -1,24 +1,28 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */
|
||||
/* eslint-disable func-names, wrap-iife, no-use-before-define,
|
||||
consistent-return, prefer-rest-params */
|
||||
/* global Breakpoints */
|
||||
|
||||
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
|
||||
var AUTO_SCROLL_OFFSET = 75;
|
||||
var DOWN_BUILD_TRACE = '#down-build-trace';
|
||||
const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
|
||||
const AUTO_SCROLL_OFFSET = 75;
|
||||
const DOWN_BUILD_TRACE = '#down-build-trace';
|
||||
|
||||
window.Build = (function() {
|
||||
window.Build = (function () {
|
||||
Build.timeout = null;
|
||||
|
||||
Build.state = null;
|
||||
|
||||
function Build(options) {
|
||||
options = options || $('.js-build-options').data();
|
||||
this.pageUrl = options.pageUrl;
|
||||
this.buildUrl = options.buildUrl;
|
||||
this.buildStatus = options.buildStatus;
|
||||
this.state = options.logState;
|
||||
this.buildStage = options.buildStage;
|
||||
this.updateDropdown = bind(this.updateDropdown, this);
|
||||
this.options = options || $('.js-build-options').data();
|
||||
|
||||
this.pageUrl = this.options.pageUrl;
|
||||
this.buildUrl = this.options.buildUrl;
|
||||
this.buildStatus = this.options.buildStatus;
|
||||
this.state = this.options.logState;
|
||||
this.buildStage = this.options.buildStage;
|
||||
this.$document = $(document);
|
||||
|
||||
this.updateDropdown = bind(this.updateDropdown, this);
|
||||
|
||||
this.$body = $('body');
|
||||
this.$buildTrace = $('#build-trace');
|
||||
this.$autoScrollContainer = $('.autoscroll-container');
|
||||
|
|
@ -29,112 +33,110 @@ window.Build = (function() {
|
|||
this.$scrollTopBtn = $('#scroll-top');
|
||||
this.$scrollBottomBtn = $('#scroll-bottom');
|
||||
this.$buildRefreshAnimation = $('.js-build-refresh');
|
||||
this.$buildScroll = $('#js-build-scroll');
|
||||
this.$truncatedInfo = $('.js-truncated-info');
|
||||
|
||||
clearTimeout(Build.timeout);
|
||||
// Init breakpoint checker
|
||||
this.bp = Breakpoints.get();
|
||||
|
||||
this.initSidebar();
|
||||
this.$buildScroll = $('#js-build-scroll');
|
||||
|
||||
this.populateJobs(this.buildStage);
|
||||
this.updateStageDropdownText(this.buildStage);
|
||||
this.sidebarOnResize();
|
||||
|
||||
this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
|
||||
this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
|
||||
this.$document
|
||||
.off('click', '.js-sidebar-build-toggle')
|
||||
.on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
|
||||
|
||||
this.$document
|
||||
.off('click', '.stage-item')
|
||||
.on('click', '.stage-item', this.updateDropdown);
|
||||
|
||||
this.$document.on('scroll', this.initScrollMonitor.bind(this));
|
||||
$(window).off('resize.build').on('resize.build', this.sidebarOnResize.bind(this));
|
||||
$('a', this.$buildScroll).off('click.stepTrace').on('click.stepTrace', this.stepTrace);
|
||||
|
||||
$(window)
|
||||
.off('resize.build')
|
||||
.on('resize.build', this.sidebarOnResize.bind(this));
|
||||
|
||||
$('a', this.$buildScroll)
|
||||
.off('click.stepTrace')
|
||||
.on('click.stepTrace', this.stepTrace);
|
||||
|
||||
this.updateArtifactRemoveDate();
|
||||
if ($('#build-trace').length) {
|
||||
this.getInitialBuildTrace();
|
||||
this.initScrollButtonAffix();
|
||||
}
|
||||
this.initScrollButtonAffix();
|
||||
this.invokeBuildTrace();
|
||||
}
|
||||
|
||||
Build.prototype.initSidebar = function() {
|
||||
Build.prototype.initSidebar = function () {
|
||||
this.$sidebar = $('.js-build-sidebar');
|
||||
this.$sidebar.niceScroll();
|
||||
this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
||||
this.$document
|
||||
.off('click', '.js-sidebar-build-toggle')
|
||||
.on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
|
||||
};
|
||||
|
||||
Build.prototype.location = function() {
|
||||
return window.location.href.split("#")[0];
|
||||
Build.prototype.invokeBuildTrace = function () {
|
||||
return this.getBuildTrace();
|
||||
};
|
||||
|
||||
Build.prototype.invokeBuildTrace = function() {
|
||||
var continueRefreshStatuses = ['running', 'pending'];
|
||||
// Continue to update build trace when build is running or pending
|
||||
if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
|
||||
// Check for new build output if user still watching build page
|
||||
// Only valid for runnig build when output changes during time
|
||||
Build.timeout = setTimeout((function(_this) {
|
||||
return function() {
|
||||
if (_this.location() === _this.pageUrl) {
|
||||
return _this.getBuildTrace();
|
||||
}
|
||||
};
|
||||
})(this), 4000);
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.getInitialBuildTrace = function() {
|
||||
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
|
||||
|
||||
Build.prototype.getBuildTrace = function () {
|
||||
return $.ajax({
|
||||
url: this.pageUrl + "/trace.json",
|
||||
url: `${this.pageUrl}/trace.json`,
|
||||
dataType: 'json',
|
||||
success: function(buildData) {
|
||||
$('.js-build-output').html(buildData.html);
|
||||
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
|
||||
if (window.location.hash === DOWN_BUILD_TRACE) {
|
||||
$("html,body").scrollTop(this.$buildTrace.height());
|
||||
data: {
|
||||
state: this.state,
|
||||
},
|
||||
success: ((log) => {
|
||||
const $buildContainer = $('.js-build-output');
|
||||
|
||||
if (log.state) {
|
||||
this.state = log.state;
|
||||
}
|
||||
if (removeRefreshStatuses.indexOf(buildData.status) !== -1) {
|
||||
|
||||
if (log.append) {
|
||||
$buildContainer.append(log.html);
|
||||
} else {
|
||||
$buildContainer.html(log.html);
|
||||
if (log.truncated) {
|
||||
$('.js-truncated-info-size').html(` ${log.size} `);
|
||||
this.$truncatedInfo.removeClass('hidden');
|
||||
this.initAffixTruncatedInfo();
|
||||
} else {
|
||||
this.$truncatedInfo.addClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
this.checkAutoscroll();
|
||||
|
||||
if (!log.complete) {
|
||||
Build.timeout = setTimeout(() => {
|
||||
this.invokeBuildTrace();
|
||||
}, 4000);
|
||||
} else {
|
||||
this.$buildRefreshAnimation.remove();
|
||||
return this.initScrollMonitor();
|
||||
}
|
||||
}.bind(this)
|
||||
|
||||
if (log.status !== this.buildStatus) {
|
||||
let pageUrl = this.pageUrl;
|
||||
|
||||
if (this.$autoScrollStatus.data('state') === 'enabled') {
|
||||
pageUrl += DOWN_BUILD_TRACE;
|
||||
}
|
||||
|
||||
gl.utils.visitUrl(pageUrl);
|
||||
}
|
||||
}),
|
||||
error: () => {
|
||||
this.$buildRefreshAnimation.remove();
|
||||
return this.initScrollMonitor();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.getBuildTrace = function() {
|
||||
return $.ajax({
|
||||
url: this.pageUrl + "/trace.json?state=" + (encodeURIComponent(this.state)),
|
||||
dataType: "json",
|
||||
success: (function(_this) {
|
||||
return function(log) {
|
||||
var pageUrl;
|
||||
|
||||
if (log.state) {
|
||||
_this.state = log.state;
|
||||
}
|
||||
_this.invokeBuildTrace();
|
||||
if (log.status === "running") {
|
||||
if (log.append) {
|
||||
$('.js-build-output').append(log.html);
|
||||
} else {
|
||||
$('.js-build-output').html(log.html);
|
||||
}
|
||||
return _this.checkAutoscroll();
|
||||
} else if (log.status !== _this.buildStatus) {
|
||||
pageUrl = _this.pageUrl;
|
||||
if (_this.$autoScrollStatus.data('state') === 'enabled') {
|
||||
pageUrl += DOWN_BUILD_TRACE;
|
||||
}
|
||||
|
||||
return gl.utils.visitUrl(pageUrl);
|
||||
}
|
||||
};
|
||||
})(this)
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.checkAutoscroll = function() {
|
||||
if (this.$autoScrollStatus.data("state") === "enabled") {
|
||||
return $("html,body").scrollTop(this.$buildTrace.height());
|
||||
Build.prototype.checkAutoscroll = function () {
|
||||
if (this.$autoScrollStatus.data('state') === 'enabled') {
|
||||
return $('html,body').scrollTop(this.$buildTrace.height());
|
||||
}
|
||||
|
||||
// Handle a situation where user started new build
|
||||
|
|
@ -146,7 +148,7 @@ window.Build = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
Build.prototype.initScrollButtonAffix = function() {
|
||||
Build.prototype.initScrollButtonAffix = function () {
|
||||
// Hide everything initially
|
||||
this.$scrollTopBtn.hide();
|
||||
this.$scrollBottomBtn.hide();
|
||||
|
|
@ -167,15 +169,17 @@ window.Build = (function() {
|
|||
// - Show Top Arrow button
|
||||
// - Show Bottom Arrow button
|
||||
// - Disable Autoscroll and hide indicator (when build is running)
|
||||
Build.prototype.initScrollMonitor = function() {
|
||||
if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
|
||||
Build.prototype.initScrollMonitor = function () {
|
||||
if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
|
||||
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
|
||||
// User is somewhere in middle of Build Log
|
||||
|
||||
this.$scrollTopBtn.show();
|
||||
|
||||
if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed
|
||||
this.$scrollBottomBtn.show();
|
||||
} else if (this.$buildRefreshAnimation.is(':visible') && !gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
|
||||
} else if (this.$buildRefreshAnimation.is(':visible') &&
|
||||
!gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
|
||||
this.$scrollBottomBtn.show();
|
||||
} else {
|
||||
this.$scrollBottomBtn.hide();
|
||||
|
|
@ -186,10 +190,13 @@ window.Build = (function() {
|
|||
this.$autoScrollContainer.hide();
|
||||
this.$autoScrollStatusText.removeClass('animate');
|
||||
} else {
|
||||
this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show();
|
||||
this.$autoScrollContainer.css({
|
||||
top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
|
||||
}).show();
|
||||
this.$autoScrollStatusText.addClass('animate');
|
||||
}
|
||||
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
|
||||
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
|
||||
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
|
||||
// User is at Top of Build Log
|
||||
|
||||
this.$scrollTopBtn.hide();
|
||||
|
|
@ -197,17 +204,22 @@ window.Build = (function() {
|
|||
|
||||
this.$autoScrollContainer.hide();
|
||||
this.$autoScrollStatusText.removeClass('animate');
|
||||
} else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
|
||||
(this.$buildRefreshAnimation.is(':visible') && gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
|
||||
} else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
|
||||
gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
|
||||
(this.$buildRefreshAnimation.is(':visible') &&
|
||||
gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
|
||||
// User is at Bottom of Build Log
|
||||
|
||||
this.$scrollTopBtn.show();
|
||||
this.$scrollBottomBtn.hide();
|
||||
|
||||
// Show and Reposition Autoscroll Status Indicator
|
||||
this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show();
|
||||
this.$autoScrollContainer.css({
|
||||
top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
|
||||
}).show();
|
||||
this.$autoScrollStatusText.addClass('animate');
|
||||
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
|
||||
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
|
||||
gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
|
||||
// Build Log height is small
|
||||
|
||||
this.$scrollTopBtn.hide();
|
||||
|
|
@ -218,65 +230,81 @@ window.Build = (function() {
|
|||
this.$autoScrollStatusText.removeClass('animate');
|
||||
}
|
||||
|
||||
if (this.buildStatus === "running" || this.buildStatus === "pending") {
|
||||
if (this.buildStatus === 'running' || this.buildStatus === 'pending') {
|
||||
// Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise.
|
||||
this.$autoScrollStatus.data("state", gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled');
|
||||
this.$autoScrollStatus.data(
|
||||
'state',
|
||||
gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.shouldHideSidebarForViewport = function() {
|
||||
var bootstrapBreakpoint;
|
||||
bootstrapBreakpoint = this.bp.getBreakpointSize();
|
||||
Build.prototype.shouldHideSidebarForViewport = function () {
|
||||
const bootstrapBreakpoint = this.bp.getBreakpointSize();
|
||||
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
||||
};
|
||||
|
||||
Build.prototype.toggleSidebar = function(shouldHide) {
|
||||
var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
|
||||
Build.prototype.toggleSidebar = function (shouldHide) {
|
||||
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
|
||||
|
||||
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
|
||||
.toggleClass('sidebar-collapsed', shouldHide);
|
||||
this.$truncatedInfo.toggleClass('sidebar-expanded', shouldShow)
|
||||
.toggleClass('sidebar-collapsed', shouldHide);
|
||||
this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
|
||||
.toggleClass('right-sidebar-collapsed', shouldHide);
|
||||
};
|
||||
|
||||
Build.prototype.sidebarOnResize = function() {
|
||||
Build.prototype.sidebarOnResize = function () {
|
||||
this.toggleSidebar(this.shouldHideSidebarForViewport());
|
||||
};
|
||||
|
||||
Build.prototype.sidebarOnClick = function() {
|
||||
Build.prototype.sidebarOnClick = function () {
|
||||
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
|
||||
};
|
||||
|
||||
Build.prototype.updateArtifactRemoveDate = function() {
|
||||
var $date, date;
|
||||
$date = $('.js-artifacts-remove');
|
||||
Build.prototype.updateArtifactRemoveDate = function () {
|
||||
const $date = $('.js-artifacts-remove');
|
||||
if ($date.length) {
|
||||
date = $date.text();
|
||||
return $date.text(gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
|
||||
const date = $date.text();
|
||||
return $date.text(
|
||||
gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Build.prototype.populateJobs = function(stage) {
|
||||
Build.prototype.populateJobs = function (stage) {
|
||||
$('.build-job').hide();
|
||||
$('.build-job[data-stage="' + stage + '"]').show();
|
||||
$(`.build-job[data-stage="${stage}"]`).show();
|
||||
};
|
||||
|
||||
Build.prototype.updateStageDropdownText = function(stage) {
|
||||
Build.prototype.updateStageDropdownText = function (stage) {
|
||||
$('.stage-selection').text(stage);
|
||||
};
|
||||
|
||||
Build.prototype.updateDropdown = function(e) {
|
||||
Build.prototype.updateDropdown = function (e) {
|
||||
e.preventDefault();
|
||||
var stage = e.currentTarget.text;
|
||||
const stage = e.currentTarget.text;
|
||||
this.updateStageDropdownText(stage);
|
||||
this.populateJobs(stage);
|
||||
};
|
||||
|
||||
Build.prototype.stepTrace = function(e) {
|
||||
var $currentTarget;
|
||||
Build.prototype.stepTrace = function (e) {
|
||||
e.preventDefault();
|
||||
$currentTarget = $(e.currentTarget);
|
||||
|
||||
const $currentTarget = $(e.currentTarget);
|
||||
$.scrollTo($currentTarget.attr('href'), {
|
||||
offset: 0
|
||||
offset: 0,
|
||||
});
|
||||
};
|
||||
|
||||
Build.prototype.initAffixTruncatedInfo = function () {
|
||||
const offsetTop = this.$buildTrace.offset().top;
|
||||
|
||||
this.$truncatedInfo.affix({
|
||||
offset: {
|
||||
top: offsetTop,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,37 @@
|
|||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.truncated-info {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid;
|
||||
background-color: $black-transparent;
|
||||
height: 45px;
|
||||
|
||||
&.affix {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
// with sidebar
|
||||
&.affix.sidebar-expanded {
|
||||
right: 312px;
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
// without sidebar
|
||||
&.affix.sidebar-collapsed {
|
||||
right: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
&.affix-top {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin: 0 auto;
|
||||
right: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-controls {
|
||||
|
|
@ -186,6 +217,7 @@
|
|||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
|
||||
.fa-refresh {
|
||||
font-size: 24px;
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@
|
|||
transition: width .3s;
|
||||
background: $gray-light;
|
||||
padding: 10px 20px;
|
||||
z-index: 2;
|
||||
|
||||
&.right-sidebar-expanded {
|
||||
width: $gutter_width;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
module RequiresHealthToken
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
before_action :validate_health_check_access!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_health_check_access!
|
||||
render_404 unless token_valid?
|
||||
end
|
||||
|
||||
def token_valid?
|
||||
token = params[:token].presence || request.headers['TOKEN']
|
||||
token.present? &&
|
||||
ActiveSupport::SecurityUtils.variable_size_secure_compare(
|
||||
token,
|
||||
current_application_settings.health_check_access_token
|
||||
)
|
||||
end
|
||||
|
||||
def render_404
|
||||
render file: Rails.root.join('public', '404'), layout: false, status: '404'
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +1,3 @@
|
|||
class HealthCheckController < HealthCheck::HealthCheckController
|
||||
before_action :validate_health_check_access!
|
||||
|
||||
private
|
||||
|
||||
def validate_health_check_access!
|
||||
render_404 unless token_valid?
|
||||
end
|
||||
|
||||
def token_valid?
|
||||
token = params[:token].presence || request.headers['TOKEN']
|
||||
token.present? &&
|
||||
ActiveSupport::SecurityUtils.variable_size_secure_compare(
|
||||
token,
|
||||
current_application_settings.health_check_access_token
|
||||
)
|
||||
end
|
||||
|
||||
def render_404
|
||||
render file: Rails.root.join('public', '404'), layout: false, status: '404'
|
||||
end
|
||||
include RequiresHealthToken
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
class HealthController < ActionController::Base
|
||||
protect_from_forgery with: :exception
|
||||
include RequiresHealthToken
|
||||
|
||||
CHECKS = [
|
||||
Gitlab::HealthChecks::DbCheck,
|
||||
Gitlab::HealthChecks::RedisCheck,
|
||||
Gitlab::HealthChecks::FsShardsCheck,
|
||||
].freeze
|
||||
|
||||
def readiness
|
||||
results = CHECKS.map { |check| [check.name, check.readiness] }
|
||||
|
||||
render_check_results(results)
|
||||
end
|
||||
|
||||
def liveness
|
||||
results = CHECKS.map { |check| [check.name, check.liveness] }
|
||||
|
||||
render_check_results(results)
|
||||
end
|
||||
|
||||
def metrics
|
||||
results = CHECKS.flat_map(&:metrics)
|
||||
|
||||
response = results.map(&method(:metric_to_prom_line)).join("\n")
|
||||
|
||||
render text: response, content_type: 'text/plain; version=0.0.4'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metric_to_prom_line(metric)
|
||||
labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || ''
|
||||
if labels.empty?
|
||||
"#{metric.name} #{metric.value}"
|
||||
else
|
||||
"#{metric.name}{#{labels}} #{metric.value}"
|
||||
end
|
||||
end
|
||||
|
||||
def render_check_results(results)
|
||||
flattened = results.flat_map do |name, result|
|
||||
if result.is_a?(Gitlab::HealthChecks::Result)
|
||||
[[name, result]]
|
||||
else
|
||||
result.map { |r| [name, r] }
|
||||
end
|
||||
end
|
||||
success = flattened.all? { |name, r| r.success }
|
||||
|
||||
response = flattened.map do |name, r|
|
||||
info = { status: r.success ? 'ok' : 'failed' }
|
||||
info['message'] = r.message if r.message
|
||||
info[:labels] = r.labels if r.labels
|
||||
[name, info]
|
||||
end
|
||||
render json: response.to_h, status: success ? :ok : :service_unavailable
|
||||
end
|
||||
end
|
||||
|
|
@ -153,10 +153,6 @@ class Milestone < ActiveRecord::Base
|
|||
active? && issues.opened.count.zero?
|
||||
end
|
||||
|
||||
def is_empty?(user = nil)
|
||||
total_items_count(user).zero?
|
||||
end
|
||||
|
||||
def author_id
|
||||
nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class JiraService < IssueTrackerService
|
|||
{ type: 'text', name: 'project_key', placeholder: 'Project Key' },
|
||||
{ type: 'text', name: 'username', placeholder: '' },
|
||||
{ type: 'password', name: 'password', placeholder: '' },
|
||||
{ type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
|
||||
{ type: 'text', name: 'jira_issue_transition_id', placeholder: '' }
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1156,6 +1156,8 @@ class Repository
|
|||
@project.repository_storage_path
|
||||
end
|
||||
|
||||
delegate :gitaly_channel, :gitaly_repository, to: :raw_repository
|
||||
|
||||
def initialize_raw_repository
|
||||
Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class GlobalPolicy < BasePolicy
|
|||
can! :access_api
|
||||
can! :access_git
|
||||
can! :receive_notifications
|
||||
can! :use_slash_commands
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ module SlashCommands
|
|||
# Takes a text and interprets the commands that are extracted from it.
|
||||
# Returns the content without commands, and hash of changes to be applied to a record.
|
||||
def execute(content, issuable)
|
||||
return [content, {}] unless current_user.can?(:use_slash_commands)
|
||||
|
||||
@issuable = issuable
|
||||
@updates = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,17 +47,19 @@
|
|||
%li
|
||||
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
|
||||
= icon('hashtag fw')
|
||||
%span.badge.issues-count
|
||||
= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
|
||||
- issues_count = cached_assigned_issuables_count(current_user, :issues, :opened)
|
||||
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
|
||||
= number_with_delimiter(issues_count)
|
||||
%li
|
||||
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
|
||||
= custom_icon('mr_bold')
|
||||
%span.badge.merge-requests-count
|
||||
= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
|
||||
- merge_requests_count = cached_assigned_issuables_count(current_user, :merge_requests, :opened)
|
||||
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
|
||||
= number_with_delimiter(merge_requests_count)
|
||||
%li
|
||||
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
|
||||
= icon('check-circle fw')
|
||||
%span.badge.todos-count
|
||||
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
|
||||
= todos_count_format(todos_pending_count)
|
||||
%li.header-user.dropdown
|
||||
= link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@
|
|||
= custom_icon('scroll_down_hover_active')
|
||||
#up-build-trace
|
||||
%pre.build-trace#build-trace
|
||||
.js-truncated-info.truncated-info.hidden
|
||||
%span<
|
||||
Showing last
|
||||
%span.js-truncated-info-size><
|
||||
KiB of log
|
||||
%code.bash.js-build-output
|
||||
.build-loader-animation.js-build-refresh
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
%h3.page-title= @environment.name
|
||||
.col-md-5
|
||||
.nav-controls
|
||||
= render 'projects/environments/metrics_button', environment: @environment
|
||||
= render 'projects/environments/terminal_button', environment: @environment
|
||||
= render 'projects/environments/external_url', environment: @environment
|
||||
= render 'projects/environments/metrics_button', environment: @environment
|
||||
- if can?(current_user, :update_environment, @environment)
|
||||
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
|
||||
- if can?(current_user, :create_deployment, @environment) && @environment.can_stop?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add /-/readiness /-/liveness and /-/metrics endpoints to track application health
|
||||
merge_request: 10416
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Shows 'Go Back' link only when browser history is available
|
||||
merge_request: 9017
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Remove confusing placeholder for JIRA transition_id
|
||||
merge_request:
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Removed Milestone#is_empty?
|
||||
merge_request: 10523
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: fix Status icons overlapping sidebar on mobile
|
||||
merge_request:
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Hide header counters for issue/mr/todos if zero
|
||||
merge_request: 10506
|
||||
author:
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Moved the monitoring button inside the show view for the environments page
|
||||
merge_request:
|
||||
author:
|
||||
|
|
@ -39,6 +39,12 @@ Rails.application.routes.draw do
|
|||
# Health check
|
||||
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
|
||||
|
||||
scope path: '-', controller: 'health' do
|
||||
get :liveness
|
||||
get :readiness
|
||||
get :metrics
|
||||
end
|
||||
|
||||
# Koding route
|
||||
get 'koding' => 'koding#index'
|
||||
|
||||
|
|
|
|||
|
|
@ -18,62 +18,62 @@ All technical content published by GitLab lives in the documentation, including:
|
|||
- [Account Security](user/profile/account/two_factor_authentication.md) Securing your account via two-factor authentication, etc.
|
||||
- [API](api/README.md) Automate GitLab via a simple and powerful API.
|
||||
- [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples.
|
||||
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
|
||||
- [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry.
|
||||
- [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file.
|
||||
- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf) Download a PDF describing the most used Git operations.
|
||||
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
|
||||
- [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
|
||||
- [GitLab Pages](user/project/pages/index.md) Using GitLab Pages.
|
||||
- [Importing to GitLab](workflow/importing/README.md) Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
|
||||
- [Importing and exporting projects between instances](user/project/settings/import_export.md).
|
||||
- [Importing to GitLab](workflow/importing/README.md) Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
|
||||
- [Markdown](user/markdown.md) GitLab's advanced formatting system.
|
||||
- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab.
|
||||
- [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
|
||||
- [Profile Settings](profile/README.md)
|
||||
- [Project Services](user/project/integrations/project_services.md) Integrate a project with external services, such as CI and chat.
|
||||
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
|
||||
- [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
|
||||
- [Snippets](user/snippets.md) Snippets allow you to create little bits of code.
|
||||
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
|
||||
- [Webhooks](user/project/integrations/webhooks.md) Let GitLab notify you when new code has been pushed to your project.
|
||||
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
|
||||
- [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file.
|
||||
- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf) Download a PDF describing the most used Git operations.
|
||||
|
||||
## Administrator documentation
|
||||
|
||||
- [Access restrictions](user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols) Define which Git access protocols can be used to talk to GitLab
|
||||
- [Authentication/Authorization](administration/auth/README.md) Configure
|
||||
external authentication with LDAP, SAML, CAS and additional Omniauth providers.
|
||||
- [Authentication/Authorization](administration/auth/README.md) Configure external authentication with LDAP, SAML, CAS and additional Omniauth providers.
|
||||
- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
|
||||
- [Custom Git hooks](administration/custom_hooks.md) Custom Git hooks (on the filesystem) for when webhooks aren't enough.
|
||||
- [Install](install/README.md) Requirements, directory structures and installation from source.
|
||||
- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components.
|
||||
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
|
||||
- [Issue closing pattern](administration/issue_closing_pattern.md) Customize how to close an issue from commit messages.
|
||||
- [Koding](administration/integration/koding.md) Set up Koding to use with GitLab.
|
||||
- [Web terminals](administration/integration/terminal.md) Provide terminal access to environments from within GitLab.
|
||||
- [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars.
|
||||
- [Log system](administration/logs.md) Log system.
|
||||
- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
|
||||
- [Environment Variables](administration/environment_variables.md) to configure GitLab.
|
||||
- [Operations](administration/operations.md) Keeping GitLab up and running.
|
||||
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
|
||||
- [Repository checks](administration/repository_checks.md) Periodic Git repository checks.
|
||||
- [Repository storage paths](administration/repository_storage_paths.md) Manage the paths used to store repositories.
|
||||
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
|
||||
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
|
||||
- [Update](update/README.md) Update guides to upgrade your installation.
|
||||
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
|
||||
- [Header logo](customization/branded_page_and_email_header.md) Change the logo on the overall page and email header.
|
||||
- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails.
|
||||
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
|
||||
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
|
||||
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
|
||||
- [GitLab Pages configuration](administration/pages/index.md) Configure GitLab Pages.
|
||||
- [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
|
||||
- [GitLab performance monitoring with Prometheus](administration/monitoring/prometheus/index.md) Configure GitLab and Prometheus for measuring performance metrics.
|
||||
- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests.
|
||||
- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint.
|
||||
- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
|
||||
- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
|
||||
- [Header logo](customization/branded_page_and_email_header.md) Change the logo on the overall page and email header.
|
||||
- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability.
|
||||
- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
|
||||
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
|
||||
- [Install](install/README.md) Requirements, directory structures and installation from source.
|
||||
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
|
||||
- [Issue closing pattern](administration/issue_closing_pattern.md) Customize how to close an issue from commit messages.
|
||||
- [Koding](administration/integration/koding.md) Set up Koding to use with GitLab.
|
||||
- [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars.
|
||||
- [Log system](administration/logs.md) Log system.
|
||||
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
|
||||
- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint.
|
||||
- [Operations](administration/operations.md) Keeping GitLab up and running.
|
||||
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
|
||||
- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails.
|
||||
- [Repository checks](administration/repository_checks.md) Periodic Git repository checks.
|
||||
- [Repository storage paths](administration/repository_storage_paths.md) Manage the paths used to store repositories.
|
||||
- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests.
|
||||
- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components.
|
||||
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
|
||||
- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
|
||||
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
|
||||
- [Update](update/README.md) Update guides to upgrade your installation.
|
||||
- [Web terminals](administration/integration/terminal.md) Provide terminal access to environments from within GitLab.
|
||||
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
|
||||
|
||||
## Contributor documentation
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ in the table below.
|
|||
| `Project key` | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
|
||||
| `Username` | The user name created in [configuring JIRA step](#configuring-jira). |
|
||||
| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
|
||||
| `JIRA issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). |
|
||||
| `JIRA issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
|
||||
|
||||
After saving the configuration, your GitLab project will be able to interact
|
||||
with the linked JIRA project.
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
|
@ -0,0 +1,94 @@
|
|||
# Search through GitLab
|
||||
|
||||
## Issues and merge requests
|
||||
|
||||
To search through issues and merge requests in multiple projects, you can use the left-sidebar.
|
||||
|
||||
Click the menu bar, then **Issues** or **Merge Requests**, which work in the same way,
|
||||
therefore, the following notes are valid for both.
|
||||
|
||||
The number displayed on their right represents the number of issues and merge requests assigned to you.
|
||||
|
||||

|
||||
|
||||
When you click **Issues**, you'll see the opened issues assigned to you straight away:
|
||||
|
||||

|
||||
|
||||
You can filter them by **Author**, **Assignee**, **Milestone**, and **Labels**,
|
||||
searching through **Open**, **Closed**, and **All** issues.
|
||||
|
||||
Of course, you can combine all filters together.
|
||||
|
||||
### Issues and MRs assigned to you or created by you
|
||||
|
||||
You'll find a shortcut to issues and merge requests create by you or assigned to you
|
||||
on the search field on the top-right of your screen:
|
||||
|
||||

|
||||
|
||||
## Issues and merge requests per project
|
||||
|
||||
If you want to search for issues present in a specific project, navigate to
|
||||
a project's **Issues** tab, and click on the field **Search or filter results...**. It will
|
||||
display a dropdown menu, from which you can add filters per author, assignee, milestone, label,
|
||||
and weight. When done, press **Enter** on your keyboard to filter the issues.
|
||||
|
||||

|
||||
|
||||
The same process is valid for merge requests. Navigate to your project's **Merge Requests** tab,
|
||||
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
|
||||
milestone, and label.
|
||||
|
||||
### Shortcut
|
||||
|
||||
You'll also find a shortcut on the search field on the top-right of the project's dashboard to
|
||||
quickly access issues and merge requests created or assigned to you within that project:
|
||||
|
||||

|
||||
|
||||
## Todos
|
||||
|
||||
Your [todos](../../workflow/todos.md#gitlab-todos) can be searched by "to do" and "done".
|
||||
You can [filter](../../workflow/todos.md#filtering-your-todos) them per project,
|
||||
author, type, and action. Also, you can sort them by
|
||||
[**Label priority**](../../user/project/labels.md#prioritize-labels),
|
||||
**Last created** and **Oldest created**.
|
||||
|
||||
## Projects
|
||||
|
||||
You can search through your projects from the left menu, by clicking the menu bar, then **Projects**.
|
||||
On the field **Filter by name**, type the project or group name you want to find, and GitLab
|
||||
will filter them for you as you type.
|
||||
|
||||
You can also look for the projects you starred (**Starred projects**), and **Explore** all
|
||||
public and internal projects available in GitLab.com, from which you can filter by visibitily,
|
||||
through **Trending**, best rated with **Most starts**, or **All** of them.
|
||||
|
||||
You can also sort them by **Name**, **Last created**, **Oldest created**, **Last updated**,
|
||||
**Oldest updated**, **Owner**, and choose to hide or show **archived projects**:
|
||||
|
||||

|
||||
|
||||
## Groups
|
||||
|
||||
Similarly to [projects search](#projects), you can search through your groups from
|
||||
the left menu, by clicking the menu bar, then **Groups**.
|
||||
|
||||
On the field **Filter by name**, type the group name you want to find, and GitLab
|
||||
will filter them for you as you type.
|
||||
|
||||
You can also **Explore** all public and internal groups available in GitLab.com,
|
||||
and sort them by **Last created**, **Oldest created**, **Last updated**, or **Oldest updated**.
|
||||
|
||||
## Issue Boards
|
||||
|
||||
From an [Issue Board](../../user/project/issue_board.md), you can filter issues by **Author**, **Assignee**, **Milestone**, and **Labels**.
|
||||
You can also filter them by name (issue title), from the field **Filter by name**, which is loaded as you type.
|
||||
|
||||
When you want to search for issues to add to lists present in your Issue Board, click
|
||||
the button **Add issues** on the top-right of your screen, opening a modal window from which
|
||||
you'll be able to, besides filtering them by **Name**, **Author**, **Assignee**, **Milestone**,
|
||||
and **Labels**, select multiple issues to add to a list of your choice:
|
||||
|
||||

|
||||
|
|
@ -142,7 +142,7 @@ module API
|
|||
project = Project.find_by_full_path(relative_path.sub(/\.(git|wiki)\z/, ''))
|
||||
|
||||
begin
|
||||
Gitlab::GitalyClient::Notifications.new(project.repository_storage, relative_path).post_receive
|
||||
Gitlab::GitalyClient::Notifications.new(project.repository).post_receive
|
||||
rescue GRPC::Unavailable => e
|
||||
render_api_error(e, 500)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ require 'gitlab/email/handler/unsubscribe_handler'
|
|||
module Gitlab
|
||||
module Email
|
||||
module Handler
|
||||
HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler].freeze
|
||||
HANDLERS = [
|
||||
UnsubscribeHandler,
|
||||
CreateNoteHandler,
|
||||
CreateIssueHandler
|
||||
].freeze
|
||||
|
||||
def self.for(mail, mail_key)
|
||||
HANDLERS.find do |klass|
|
||||
|
|
|
|||
|
|
@ -968,6 +968,14 @@ module Gitlab
|
|||
@attributes.attributes(path)
|
||||
end
|
||||
|
||||
def gitaly_repository
|
||||
Gitlab::GitalyClient::Util.repository(@repository_storage, @relative_path)
|
||||
end
|
||||
|
||||
def gitaly_channel
|
||||
Gitlab::GitalyClient.get_channel(@repository_storage)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Get the content of a blob for a given commit. If the blob is a commit
|
||||
|
|
@ -1247,7 +1255,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def gitaly_ref_client
|
||||
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(@repository_storage, @relative_path)
|
||||
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ module Gitlab
|
|||
|
||||
class << self
|
||||
def diff_from_parent(commit, options = {})
|
||||
project = commit.project
|
||||
channel = GitalyClient.get_channel(project.repository_storage)
|
||||
stub = Gitaly::Diff::Stub.new(nil, nil, channel_override: channel)
|
||||
repo = Gitaly::Repository.new(path: project.repository.path_to_repo)
|
||||
parent = commit.parents[0]
|
||||
repository = commit.project.repository
|
||||
gitaly_repo = repository.gitaly_repository
|
||||
stub = Gitaly::Diff::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
|
||||
parent = commit.parents[0]
|
||||
parent_id = parent ? parent.id : EMPTY_TREE_ID
|
||||
request = Gitaly::CommitDiffRequest.new(
|
||||
repository: repo,
|
||||
request = Gitaly::CommitDiffRequest.new(
|
||||
repository: gitaly_repo,
|
||||
left_commit_id: parent_id,
|
||||
right_commit_id: commit.id
|
||||
)
|
||||
|
|
@ -23,12 +22,10 @@ module Gitlab
|
|||
end
|
||||
|
||||
def is_ancestor(repository, ancestor_id, child_id)
|
||||
project = Project.find_by_path(repository.path)
|
||||
channel = GitalyClient.get_channel(project.repository_storage)
|
||||
stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: channel)
|
||||
repo = Gitaly::Repository.new(path: repository.path_to_repo)
|
||||
gitaly_repo = repository.gitaly_repository
|
||||
stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
|
||||
request = Gitaly::CommitIsAncestorRequest.new(
|
||||
repository: repo,
|
||||
repository: gitaly_repo,
|
||||
ancestor_id: ancestor_id,
|
||||
child_id: child_id
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ module Gitlab
|
|||
class Notifications
|
||||
attr_accessor :stub
|
||||
|
||||
def initialize(repository_storage, relative_path)
|
||||
@channel, @repository = Util.process_path(repository_storage, relative_path)
|
||||
@stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: @channel)
|
||||
# 'repository' is a Gitlab::Git::Repository
|
||||
def initialize(repository)
|
||||
@gitaly_repo = repository.gitaly_repository
|
||||
@stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
|
||||
end
|
||||
|
||||
def post_receive
|
||||
request = Gitaly::PostReceiveRequest.new(repository: @repository)
|
||||
request = Gitaly::PostReceiveRequest.new(repository: @gitaly_repo)
|
||||
@stub.post_receive(request)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,23 +3,24 @@ module Gitlab
|
|||
class Ref
|
||||
attr_accessor :stub
|
||||
|
||||
def initialize(repository_storage, relative_path)
|
||||
@channel, @repository = Util.process_path(repository_storage, relative_path)
|
||||
@stub = Gitaly::Ref::Stub.new(nil, nil, channel_override: @channel)
|
||||
# 'repository' is a Gitlab::Git::Repository
|
||||
def initialize(repository)
|
||||
@gitaly_repo = repository.gitaly_repository
|
||||
@stub = Gitaly::Ref::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
|
||||
end
|
||||
|
||||
def default_branch_name
|
||||
request = Gitaly::FindDefaultBranchNameRequest.new(repository: @repository)
|
||||
request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
|
||||
stub.find_default_branch_name(request).name.gsub(/^refs\/heads\//, '')
|
||||
end
|
||||
|
||||
def branch_names
|
||||
request = Gitaly::FindAllBranchNamesRequest.new(repository: @repository)
|
||||
request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
|
||||
consume_refs_response(stub.find_all_branch_names(request), prefix: 'refs/heads/')
|
||||
end
|
||||
|
||||
def tag_names
|
||||
request = Gitaly::FindAllTagNamesRequest.new(repository: @repository)
|
||||
request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
|
||||
consume_refs_response(stub.find_all_tag_names(request), prefix: 'refs/tags/')
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
module Gitlab
|
||||
module GitalyClient
|
||||
module Util
|
||||
def self.process_path(repository_storage, relative_path)
|
||||
channel = GitalyClient.get_channel(repository_storage)
|
||||
storage_path = Gitlab.config.repositories.storages[repository_storage]['path']
|
||||
repository = Gitaly::Repository.new(path: File.join(storage_path, relative_path))
|
||||
|
||||
[channel, repository]
|
||||
class << self
|
||||
def repository(repository_storage, relative_path)
|
||||
Gitaly::Repository.new(
|
||||
path: File.join(Gitlab.config.repositories.storages[repository_storage]['path'], relative_path),
|
||||
storage_name: repository_storage,
|
||||
relative_path: relative_path,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
module Gitlab
|
||||
module HealthChecks
|
||||
module BaseAbstractCheck
|
||||
def name
|
||||
super.demodulize.underscore
|
||||
end
|
||||
|
||||
def human_name
|
||||
name.sub(/_check$/, '').capitalize
|
||||
end
|
||||
|
||||
def readiness
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def liveness
|
||||
HealthChecks::Result.new(true)
|
||||
end
|
||||
|
||||
def metrics
|
||||
[]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def metric(name, value, **labels)
|
||||
Metric.new(name, value, labels)
|
||||
end
|
||||
|
||||
def with_timing(proc)
|
||||
start = Time.now
|
||||
result = proc.call
|
||||
yield result, Time.now.to_f - start.to_f
|
||||
end
|
||||
|
||||
def catch_timeout(seconds, &block)
|
||||
begin
|
||||
Timeout.timeout(seconds.to_i, &block)
|
||||
rescue Timeout::Error => ex
|
||||
ex
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
module Gitlab
|
||||
module HealthChecks
|
||||
class DbCheck
|
||||
extend SimpleAbstractCheck
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
def metric_prefix
|
||||
'db_ping'
|
||||
end
|
||||
|
||||
def is_successful?(result)
|
||||
result == '1'
|
||||
end
|
||||
|
||||
def check
|
||||
catch_timeout 10.seconds do
|
||||
if Gitlab::Database.postgresql?
|
||||
ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping')
|
||||
else
|
||||
ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.first&.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
module Gitlab
|
||||
module HealthChecks
|
||||
class FsShardsCheck
|
||||
extend BaseAbstractCheck
|
||||
|
||||
class << self
|
||||
def readiness
|
||||
repository_storages.map do |storage_name|
|
||||
begin
|
||||
tmp_file_path = tmp_file_path(storage_name)
|
||||
|
||||
if !storage_stat_test(storage_name)
|
||||
HealthChecks::Result.new(false, 'cannot stat storage', shard: storage_name)
|
||||
elsif !storage_write_test(tmp_file_path)
|
||||
HealthChecks::Result.new(false, 'cannot write to storage', shard: storage_name)
|
||||
elsif !storage_read_test(tmp_file_path)
|
||||
HealthChecks::Result.new(false, 'cannot read from storage', shard: storage_name)
|
||||
else
|
||||
HealthChecks::Result.new(true, nil, shard: storage_name)
|
||||
end
|
||||
rescue RuntimeError => ex
|
||||
message = "unexpected error #{ex} when checking storage #{storage_name}"
|
||||
Rails.logger.error(message)
|
||||
HealthChecks::Result.new(false, message, shard: storage_name)
|
||||
ensure
|
||||
delete_test_file(tmp_file_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def metrics
|
||||
repository_storages.flat_map do |storage_name|
|
||||
tmp_file_path = tmp_file_path(storage_name)
|
||||
[
|
||||
operation_metrics(:filesystem_accessible, :filesystem_access_latency, -> { storage_stat_test(storage_name) }, shard: storage_name),
|
||||
operation_metrics(:filesystem_writable, :filesystem_write_latency, -> { storage_write_test(tmp_file_path) }, shard: storage_name),
|
||||
operation_metrics(:filesystem_readable, :filesystem_read_latency, -> { storage_read_test(tmp_file_path) }, shard: storage_name)
|
||||
].flatten
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
RANDOM_STRING = SecureRandom.hex(1000).freeze
|
||||
|
||||
def operation_metrics(ok_metric, latency_metric, operation, **labels)
|
||||
with_timing operation do |result, elapsed|
|
||||
[
|
||||
metric(latency_metric, elapsed, **labels),
|
||||
metric(ok_metric, result ? 1 : 0, **labels)
|
||||
]
|
||||
end
|
||||
rescue RuntimeError => ex
|
||||
Rails.logger("unexpected error #{ex} when checking #{ok_metric}")
|
||||
[metric(ok_metric, 0, **labels)]
|
||||
end
|
||||
|
||||
def repository_storages
|
||||
@repository_storage ||= Gitlab::CurrentSettings.current_application_settings.repository_storages
|
||||
end
|
||||
|
||||
def storages_paths
|
||||
@storage_paths ||= Gitlab.config.repositories.storages
|
||||
end
|
||||
|
||||
def with_timeout(args)
|
||||
%w{timeout 1}.concat(args)
|
||||
end
|
||||
|
||||
def tmp_file_path(storage_name)
|
||||
Dir::Tmpname.create(%w(fs_shards_check +deleted), path(storage_name)) { |path| path }
|
||||
end
|
||||
|
||||
def path(storage_name)
|
||||
storages_paths&.dig(storage_name, 'path')
|
||||
end
|
||||
|
||||
def storage_stat_test(storage_name)
|
||||
stat_path = File.join(path(storage_name), '.')
|
||||
begin
|
||||
_, status = Gitlab::Popen.popen(with_timeout(%W{ stat #{stat_path} }))
|
||||
status == 0
|
||||
rescue Errno::ENOENT
|
||||
File.exist?(stat_path) && File::Stat.new(stat_path).readable?
|
||||
end
|
||||
end
|
||||
|
||||
def storage_write_test(tmp_path)
|
||||
_, status = Gitlab::Popen.popen(with_timeout(%W{ tee #{tmp_path} })) do |stdin|
|
||||
stdin.write(RANDOM_STRING)
|
||||
end
|
||||
status == 0
|
||||
rescue Errno::ENOENT
|
||||
written_bytes = File.write(tmp_path, RANDOM_STRING) rescue Errno::ENOENT
|
||||
written_bytes == RANDOM_STRING.length
|
||||
end
|
||||
|
||||
def storage_read_test(tmp_path)
|
||||
_, status = Gitlab::Popen.popen(with_timeout(%W{ diff #{tmp_path} - })) do |stdin|
|
||||
stdin.write(RANDOM_STRING)
|
||||
end
|
||||
status == 0
|
||||
rescue Errno::ENOENT
|
||||
file_contents = File.read(tmp_path) rescue Errno::ENOENT
|
||||
file_contents == RANDOM_STRING
|
||||
end
|
||||
|
||||
def delete_test_file(tmp_path)
|
||||
_, status = Gitlab::Popen.popen(with_timeout(%W{ rm -f #{tmp_path} }))
|
||||
status == 0
|
||||
rescue Errno::ENOENT
|
||||
File.delete(tmp_path) rescue Errno::ENOENT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module Gitlab::HealthChecks
|
||||
Metric = Struct.new(:name, :value, :labels)
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
module Gitlab
|
||||
module HealthChecks
|
||||
class RedisCheck
|
||||
extend SimpleAbstractCheck
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
def metric_prefix
|
||||
'redis_ping'
|
||||
end
|
||||
|
||||
def is_successful?(result)
|
||||
result == 'PONG'
|
||||
end
|
||||
|
||||
def check
|
||||
catch_timeout 10.seconds do
|
||||
Gitlab::Redis.with(&:ping)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module Gitlab::HealthChecks
|
||||
Result = Struct.new(:success, :message, :labels)
|
||||
end
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
module Gitlab
|
||||
module HealthChecks
|
||||
module SimpleAbstractCheck
|
||||
include BaseAbstractCheck
|
||||
|
||||
def readiness
|
||||
check_result = check
|
||||
if is_successful?(check_result)
|
||||
HealthChecks::Result.new(true)
|
||||
elsif check_result.is_a?(Timeout::Error)
|
||||
HealthChecks::Result.new(false, "#{human_name} check timed out")
|
||||
else
|
||||
HealthChecks::Result.new(false, "unexpected #{human_name} check result: #{check_result}")
|
||||
end
|
||||
end
|
||||
|
||||
def metrics
|
||||
with_timing method(:check) do |result, elapsed|
|
||||
Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless is_successful?(result)
|
||||
[
|
||||
metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
|
||||
metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0),
|
||||
metric("#{metric_prefix}_latency", elapsed)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metric_prefix
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def is_successful?(result)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def check
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -24,14 +24,8 @@ module Gitlab
|
|||
}
|
||||
|
||||
if Gitlab.config.gitaly.enabled
|
||||
storage = repository.project.repository_storage
|
||||
address = Gitlab::GitalyClient.get_address(storage)
|
||||
# TODO: use GitalyClient code to assemble the Repository message
|
||||
params[:Repository] = Gitaly::Repository.new(
|
||||
path: repo_path,
|
||||
storage_name: storage,
|
||||
relative_path: Gitlab::RepoPath.strip_storage_path(repo_path),
|
||||
).to_h
|
||||
address = Gitlab::GitalyClient.get_address(repository.project.repository_storage)
|
||||
params[:Repository] = repository.gitaly_repository.to_h
|
||||
|
||||
feature_enabled = case action.to_s
|
||||
when 'git_receive_pack'
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
.container {
|
||||
margin: auto 20px;
|
||||
}
|
||||
|
||||
.go-back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -71,7 +76,16 @@
|
|||
<hr />
|
||||
<p>Make sure the address is correct and that the page hasn't moved.</p>
|
||||
<p>Please contact your GitLab administrator if you think this is a mistake.</p>
|
||||
<a href="javascript:history.back()">Go back</a>
|
||||
<a href="javascript:history.back()" class="js-go-back go-back">Go back</a>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var goBack = document.querySelector('.js-go-back');
|
||||
|
||||
if (history.length > 1) {
|
||||
goBack.style.display = 'inline';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
.container {
|
||||
margin: auto 20px;
|
||||
}
|
||||
|
||||
.go-back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -71,7 +76,17 @@
|
|||
<hr />
|
||||
<p>Make sure you have access to the thing you tried to change.</p>
|
||||
<p>Please contact your GitLab administrator if you think this is a mistake.</p>
|
||||
<a href="javascript:history.back()">Go back</a>
|
||||
<a href="javascript:history.back()" class="js-go-back go-back">Go back</a>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var goBack = document.querySelector('.js-go-back');
|
||||
|
||||
if (history.length > 1) {
|
||||
goBack.style.display = 'inline';
|
||||
}
|
||||
})();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
.container {
|
||||
margin: auto 20px;
|
||||
}
|
||||
|
||||
.go-back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -71,7 +76,16 @@
|
|||
<hr />
|
||||
<p>Try refreshing the page, or going back and attempting the action again.</p>
|
||||
<p>Please contact your GitLab administrator if this problem persists.</p>
|
||||
<a href="javascript:history.back()">Go back</a>
|
||||
<a href="javascript:history.back()" class="js-go-back go-back">Go back</a>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var goBack = document.querySelector('.js-go-back');
|
||||
|
||||
if (history.length > 1) {
|
||||
goBack.style.display = 'inline';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
.container {
|
||||
margin: auto 20px;
|
||||
}
|
||||
|
||||
.go-back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -71,7 +76,16 @@
|
|||
<hr />
|
||||
<p>Try refreshing the page, or going back and attempting the action again.</p>
|
||||
<p>Please contact your GitLab administrator if this problem persists.</p>
|
||||
<a href="javascript:history.back()">Go back</a>
|
||||
<a href="javascript:history.back()" class="js-go-back go-back">Go back</a>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var goBack = document.querySelector('.js-go-back');
|
||||
|
||||
if (history.length > 1) {
|
||||
goBack.style.display = 'inline';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
.container {
|
||||
margin: auto 20px;
|
||||
}
|
||||
|
||||
.go-back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -71,7 +76,16 @@
|
|||
<hr />
|
||||
<p>Try refreshing the page, or going back and attempting the action again.</p>
|
||||
<p>Please contact your GitLab administrator if this problem persists.</p>
|
||||
<a href="javascript:history.back()">Go back</a>
|
||||
<a href="javascript:history.back()" class="js-go-back go-back">Go back</a>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var goBack = document.querySelector('.js-go-back');
|
||||
|
||||
if (history.length > 1) {
|
||||
goBack.style.display = 'inline';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module QA
|
|||
def prepare_test_namespace
|
||||
return if page.has_content?(Runtime::Namespace.name)
|
||||
|
||||
click_on 'New Group'
|
||||
click_on 'New group'
|
||||
|
||||
fill_in 'group_path', with: Runtime::Namespace.name
|
||||
fill_in 'group_description',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe HealthController do
|
||||
include StubENV
|
||||
|
||||
let(:token) { current_application_settings.health_check_access_token }
|
||||
let(:json_response) { JSON.parse(response.body) }
|
||||
|
||||
before do
|
||||
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
|
||||
end
|
||||
|
||||
describe '#readiness' do
|
||||
context 'authorization token provided' do
|
||||
before do
|
||||
request.headers['TOKEN'] = token
|
||||
end
|
||||
|
||||
it 'returns proper response' do
|
||||
get :readiness
|
||||
expect(json_response['db_check']['status']).to eq('ok')
|
||||
expect(json_response['redis_check']['status']).to eq('ok')
|
||||
expect(json_response['fs_shards_check']['status']).to eq('ok')
|
||||
expect(json_response['fs_shards_check']['labels']['shard']).to eq('default')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without authorization token' do
|
||||
it 'returns proper response' do
|
||||
get :readiness
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#liveness' do
|
||||
context 'authorization token provided' do
|
||||
before do
|
||||
request.headers['TOKEN'] = token
|
||||
end
|
||||
|
||||
it 'returns proper response' do
|
||||
get :liveness
|
||||
expect(json_response['db_check']['status']).to eq('ok')
|
||||
expect(json_response['redis_check']['status']).to eq('ok')
|
||||
expect(json_response['fs_shards_check']['status']).to eq('ok')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without authorization token' do
|
||||
it 'returns proper response' do
|
||||
get :liveness
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#metrics' do
|
||||
context 'authorization token provided' do
|
||||
before do
|
||||
request.headers['TOKEN'] = token
|
||||
end
|
||||
|
||||
it 'returns DB ping metrics' do
|
||||
get :metrics
|
||||
expect(response.body).to match(/^db_ping_timeout 0$/)
|
||||
expect(response.body).to match(/^db_ping_success 1$/)
|
||||
expect(response.body).to match(/^db_ping_latency [0-9\.]+$/)
|
||||
end
|
||||
|
||||
it 'returns Redis ping metrics' do
|
||||
get :metrics
|
||||
expect(response.body).to match(/^redis_ping_timeout 0$/)
|
||||
expect(response.body).to match(/^redis_ping_success 1$/)
|
||||
expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/)
|
||||
end
|
||||
|
||||
it 'returns file system check metrics' do
|
||||
get :metrics
|
||||
expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/)
|
||||
expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/)
|
||||
expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/)
|
||||
expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/)
|
||||
expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/)
|
||||
expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without authorization token' do
|
||||
it 'returns proper response' do
|
||||
get :metrics
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Navigation bar counter', feature: true, js: true, caching: true do
|
||||
describe 'Navigation bar counter', feature: true, caching: true do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:empty_project, namespace: user.namespace) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
|
@ -13,33 +13,48 @@ describe 'Navigation bar counter', feature: true, js: true, caching: true do
|
|||
end
|
||||
|
||||
it 'reflects dashboard issues count' do
|
||||
visit issues_dashboard_path
|
||||
visit issues_path
|
||||
|
||||
expect_counters('issues', '1')
|
||||
|
||||
issue.update(assignee: nil)
|
||||
visit issues_dashboard_path
|
||||
|
||||
expect_counters('issues', '1')
|
||||
Timecop.travel(3.minutes.from_now) do
|
||||
visit issues_path
|
||||
|
||||
expect_counters('issues', '0')
|
||||
end
|
||||
end
|
||||
|
||||
it 'reflects dashboard merge requests count' do
|
||||
visit merge_requests_dashboard_path
|
||||
visit merge_requests_path
|
||||
|
||||
expect_counters('merge_requests', '1')
|
||||
|
||||
merge_request.update(assignee: nil)
|
||||
visit merge_requests_dashboard_path
|
||||
|
||||
expect_counters('merge_requests', '1')
|
||||
Timecop.travel(3.minutes.from_now) do
|
||||
visit merge_requests_path
|
||||
|
||||
expect_counters('merge_requests', '0')
|
||||
end
|
||||
end
|
||||
|
||||
def issues_path
|
||||
issues_dashboard_path(assignee_id: user.id)
|
||||
end
|
||||
|
||||
def merge_requests_path
|
||||
merge_requests_dashboard_path(assignee_id: user.id)
|
||||
end
|
||||
|
||||
def expect_counters(issuable_type, count)
|
||||
dashboard_count = find('li.active')
|
||||
find('.global-dropdown-toggle').click
|
||||
dashboard_count = find('.nav-links li.active')
|
||||
nav_count = find(".dashboard-shortcuts-#{issuable_type}")
|
||||
header_count = find(".header-content .#{issuable_type.tr('_', '-')}-count")
|
||||
|
||||
expect(nav_count).to have_content(count)
|
||||
expect(dashboard_count).to have_content(count)
|
||||
expect(nav_count).to have_content(count)
|
||||
expect(header_count).to have_content(count)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,58 +64,33 @@ describe('Build', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('initial build trace', () => {
|
||||
beforeEach(() => {
|
||||
new Build();
|
||||
});
|
||||
|
||||
it('displays the initial build trace', () => {
|
||||
expect($.ajax.calls.count()).toBe(1);
|
||||
const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0);
|
||||
expect(url).toBe(
|
||||
`${BUILD_URL}/trace.json`,
|
||||
);
|
||||
expect(dataType).toBe('json');
|
||||
expect(success).toEqual(jasmine.any(Function));
|
||||
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
|
||||
|
||||
success.call(context, { html: '<span>Example</span>', status: 'running' });
|
||||
|
||||
expect($('#build-trace .js-build-output').text()).toMatch(/Example/);
|
||||
});
|
||||
|
||||
it('removes the spinner', () => {
|
||||
const [{ success, context }] = $.ajax.calls.argsFor(0);
|
||||
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
|
||||
success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
|
||||
|
||||
expect($('.js-build-refresh').length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('running build', () => {
|
||||
beforeEach(function () {
|
||||
$('.js-build-options').data('buildStatus', 'running');
|
||||
this.build = new Build();
|
||||
spyOn(this.build, 'location').and.returnValue(BUILD_URL);
|
||||
});
|
||||
|
||||
it('updates the build trace on an interval', function () {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
|
||||
jasmine.clock().tick(4001);
|
||||
|
||||
expect($.ajax.calls.count()).toBe(2);
|
||||
let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
|
||||
expect(url).toBe(
|
||||
`${BUILD_URL}/trace.json?state=`,
|
||||
);
|
||||
expect(dataType).toBe('json');
|
||||
expect(success).toEqual(jasmine.any(Function));
|
||||
expect($.ajax.calls.count()).toBe(1);
|
||||
|
||||
success.call(context, {
|
||||
// We have to do it this way to prevent Webpack to fail to compile
|
||||
// when destructuring assignments and reusing
|
||||
// the same variables names inside the same scope
|
||||
let args = $.ajax.calls.argsFor(0)[0];
|
||||
|
||||
expect(args.url).toBe(`${BUILD_URL}/trace.json`);
|
||||
expect(args.dataType).toBe('json');
|
||||
expect(args.success).toEqual(jasmine.any(Function));
|
||||
|
||||
args.success.call($, {
|
||||
html: '<span>Update<span>',
|
||||
status: 'running',
|
||||
state: 'newstate',
|
||||
append: true,
|
||||
complete: false,
|
||||
});
|
||||
|
||||
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
|
||||
|
|
@ -123,17 +98,20 @@ describe('Build', () => {
|
|||
|
||||
jasmine.clock().tick(4001);
|
||||
|
||||
expect($.ajax.calls.count()).toBe(3);
|
||||
[{ url, dataType, success, context }] = $.ajax.calls.argsFor(2);
|
||||
expect(url).toBe(`${BUILD_URL}/trace.json?state=newstate`);
|
||||
expect(dataType).toBe('json');
|
||||
expect(success).toEqual(jasmine.any(Function));
|
||||
expect($.ajax.calls.count()).toBe(2);
|
||||
|
||||
success.call(context, {
|
||||
args = $.ajax.calls.argsFor(1)[0];
|
||||
expect(args.url).toBe(`${BUILD_URL}/trace.json`);
|
||||
expect(args.dataType).toBe('json');
|
||||
expect(args.data.state).toBe('newstate');
|
||||
expect(args.success).toEqual(jasmine.any(Function));
|
||||
|
||||
args.success.call($, {
|
||||
html: '<span>More</span>',
|
||||
status: 'running',
|
||||
state: 'finalstate',
|
||||
append: true,
|
||||
complete: true,
|
||||
});
|
||||
|
||||
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
|
||||
|
|
@ -141,19 +119,22 @@ describe('Build', () => {
|
|||
});
|
||||
|
||||
it('replaces the entire build trace', () => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
|
||||
jasmine.clock().tick(4001);
|
||||
let [{ success, context }] = $.ajax.calls.argsFor(1);
|
||||
success.call(context, {
|
||||
let args = $.ajax.calls.argsFor(0)[0];
|
||||
args.success.call($, {
|
||||
html: '<span>Update</span>',
|
||||
status: 'running',
|
||||
append: true,
|
||||
append: false,
|
||||
complete: false,
|
||||
});
|
||||
|
||||
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
|
||||
|
||||
jasmine.clock().tick(4001);
|
||||
[{ success, context }] = $.ajax.calls.argsFor(2);
|
||||
success.call(context, {
|
||||
args = $.ajax.calls.argsFor(1)[0];
|
||||
args.success.call($, {
|
||||
html: '<span>Different</span>',
|
||||
status: 'running',
|
||||
append: false,
|
||||
|
|
@ -163,15 +144,34 @@ describe('Build', () => {
|
|||
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
|
||||
});
|
||||
|
||||
it('shows information about truncated log', () => {
|
||||
jasmine.clock().tick(4001);
|
||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
||||
|
||||
success.call($, {
|
||||
html: '<span>Update</span>',
|
||||
status: 'success',
|
||||
append: false,
|
||||
truncated: true,
|
||||
size: '50',
|
||||
});
|
||||
|
||||
expect(
|
||||
$('#build-trace .js-truncated-info').text().trim(),
|
||||
).toContain('Showing last 50 KiB of log');
|
||||
expect($('#build-trace .js-truncated-info-size').text()).toMatch('50');
|
||||
});
|
||||
|
||||
it('reloads the page when the build is done', () => {
|
||||
spyOn(gl.utils, 'visitUrl');
|
||||
|
||||
jasmine.clock().tick(4001);
|
||||
const [{ success, context }] = $.ajax.calls.argsFor(1);
|
||||
success.call(context, {
|
||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
||||
success.call($, {
|
||||
html: '<span>Final</span>',
|
||||
status: 'passed',
|
||||
append: true,
|
||||
complete: true,
|
||||
});
|
||||
|
||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe Gitlab::GitalyClient::Commit do
|
|||
describe '.diff_from_parent' do
|
||||
let(:diff_stub) { double('Gitaly::Diff::Stub') }
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:repository_message) { Gitaly::Repository.new(path: project.repository.path) }
|
||||
let(:repository_message) { project.repository.gitaly_repository }
|
||||
let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe Gitlab::GitalyClient::Notifications do
|
|||
describe '#post_receive' do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:repo_path) { project.repository.path_to_repo }
|
||||
subject { described_class.new(project.repository_storage, project.full_path + '.git') }
|
||||
subject { described_class.new(project.repository) }
|
||||
|
||||
it 'sends a post_receive message' do
|
||||
expect_any_instance_of(Gitaly::Notifications::Stub).
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
describe Gitlab::GitalyClient::Ref do
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:repo_path) { project.repository.path_to_repo }
|
||||
let(:client) { Gitlab::GitalyClient::Ref.new(project.repository_storage, project.full_path + '.git') }
|
||||
let(:client) { Gitlab::GitalyClient::Ref.new(project.repository) }
|
||||
|
||||
before do
|
||||
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
require 'spec_helper'
|
||||
require_relative './simple_check_shared'
|
||||
|
||||
describe Gitlab::HealthChecks::DbCheck do
|
||||
include_examples 'simple_check', 'db_ping', 'Db', '1'
|
||||
end
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::HealthChecks::FsShardsCheck do
|
||||
let(:metric_class) { Gitlab::HealthChecks::Metric }
|
||||
let(:result_class) { Gitlab::HealthChecks::Result }
|
||||
let(:repository_storages) { [:default] }
|
||||
let(:tmp_dir) { Dir.mktmpdir }
|
||||
|
||||
let(:storages_paths) do
|
||||
{
|
||||
default: { path: tmp_dir }
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:repository_storages) { repository_storages }
|
||||
allow(described_class).to receive(:storages_paths) { storages_paths }
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.remove_entry_secure(tmp_dir) if Dir.exist?(tmp_dir)
|
||||
end
|
||||
|
||||
shared_examples 'filesystem checks' do
|
||||
describe '#readiness' do
|
||||
subject { described_class.readiness }
|
||||
|
||||
context 'storage points to not existing folder' do
|
||||
let(:storages_paths) do
|
||||
{
|
||||
default: { path: 'tmp/this/path/doesnt/exist' }
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) }
|
||||
end
|
||||
|
||||
context 'storage points to directory that has both read and write rights' do
|
||||
before do
|
||||
FileUtils.chmod_R(0755, tmp_dir)
|
||||
end
|
||||
|
||||
it { is_expected.to include(result_class.new(true, nil, shard: :default)) }
|
||||
|
||||
it 'cleans up files used for testing' do
|
||||
expect(described_class).to receive(:storage_write_test).with(any_args).and_call_original
|
||||
|
||||
subject
|
||||
|
||||
expect(Dir.entries(tmp_dir).count).to eq(2)
|
||||
end
|
||||
|
||||
context 'read test fails' do
|
||||
before do
|
||||
allow(described_class).to receive(:storage_read_test).with(any_args).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to include(result_class.new(false, 'cannot read from storage', shard: :default)) }
|
||||
end
|
||||
|
||||
context 'write test fails' do
|
||||
before do
|
||||
allow(described_class).to receive(:storage_write_test).with(any_args).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to include(result_class.new(false, 'cannot write to storage', shard: :default)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#metrics' do
|
||||
subject { described_class.metrics }
|
||||
|
||||
context 'storage points to not existing folder' do
|
||||
let(:storages_paths) do
|
||||
{
|
||||
default: { path: 'tmp/this/path/doesnt/exist' }
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
it { is_expected.to include(metric_class.new(:filesystem_accessible, 0, shard: :default)) }
|
||||
it { is_expected.to include(metric_class.new(:filesystem_readable, 0, shard: :default)) }
|
||||
it { is_expected.to include(metric_class.new(:filesystem_writable, 0, shard: :default)) }
|
||||
|
||||
it { is_expected.to include(have_attributes(name: :filesystem_access_latency, value: be > 0, labels: { shard: :default })) }
|
||||
it { is_expected.to include(have_attributes(name: :filesystem_read_latency, value: be > 0, labels: { shard: :default })) }
|
||||
it { is_expected.to include(have_attributes(name: :filesystem_write_latency, value: be > 0, labels: { shard: :default })) }
|
||||
end
|
||||
|
||||
context 'storage points to directory that has both read and write rights' do
|
||||
before do
|
||||
FileUtils.chmod_R(0755, tmp_dir)
|
||||
end
|
||||
|
||||
it { is_expected.to include(metric_class.new(:filesystem_accessible, 1, shard: :default)) }
|
||||
it { is_expected.to include(metric_class.new(:filesystem_readable, 1, shard: :default)) }
|
||||
it { is_expected.to include(metric_class.new(:filesystem_writable, 1, shard: :default)) }
|
||||
|
||||
it { is_expected.to include(have_attributes(name: :filesystem_access_latency, value: be > 0, labels: { shard: :default })) }
|
||||
it { is_expected.to include(have_attributes(name: :filesystem_read_latency, value: be > 0, labels: { shard: :default })) }
|
||||
it { is_expected.to include(have_attributes(name: :filesystem_write_latency, value: be > 0, labels: { shard: :default })) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when popen always finds required binaries' do
|
||||
before do
|
||||
allow(Gitlab::Popen).to receive(:popen).and_wrap_original do |method, *args, &block|
|
||||
begin
|
||||
method.call(*args, &block)
|
||||
rescue RuntimeError
|
||||
raise 'expected not to happen'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'filesystem checks'
|
||||
end
|
||||
|
||||
context 'when popen never finds required binaries' do
|
||||
before do
|
||||
allow(Gitlab::Popen).to receive(:popen).and_raise(Errno::ENOENT)
|
||||
end
|
||||
|
||||
it_behaves_like 'filesystem checks'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
require 'spec_helper'
|
||||
require_relative './simple_check_shared'
|
||||
|
||||
describe Gitlab::HealthChecks::RedisCheck do
|
||||
include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
|
||||
end
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
|
||||
describe '#metrics' do
|
||||
subject { described_class.metrics }
|
||||
context 'Check is passing' do
|
||||
before do
|
||||
allow(described_class).to receive(:check).and_return success_result
|
||||
end
|
||||
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 1)) }
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) }
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be > 0)) }
|
||||
end
|
||||
|
||||
context 'Check is misbehaving' do
|
||||
before do
|
||||
allow(described_class).to receive(:check).and_return 'error!'
|
||||
end
|
||||
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) }
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) }
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be > 0)) }
|
||||
end
|
||||
|
||||
context 'Check is timeouting' do
|
||||
before do
|
||||
allow(described_class).to receive(:check).and_return Timeout::Error.new
|
||||
end
|
||||
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) }
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 1)) }
|
||||
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be > 0)) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#readiness' do
|
||||
subject { described_class.readiness }
|
||||
context 'Check returns ok' do
|
||||
before do
|
||||
allow(described_class).to receive(:check).and_return success_result
|
||||
end
|
||||
|
||||
it { is_expected.to have_attributes(success: true) }
|
||||
end
|
||||
|
||||
context 'Check is misbehaving' do
|
||||
before do
|
||||
allow(described_class).to receive(:check).and_return 'error!'
|
||||
end
|
||||
|
||||
it { is_expected.to have_attributes(success: false, message: "unexpected #{check_name} check result: error!") }
|
||||
end
|
||||
|
||||
context 'Check is timeouting' do
|
||||
before do
|
||||
allow(described_class).to receive(:check ).and_return Timeout::Error.new
|
||||
end
|
||||
|
||||
it { is_expected.to have_attributes(success: false, message: "#{check_name} check timed out") }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#liveness' do
|
||||
subject { described_class.readiness }
|
||||
it { is_expected.to eq(Gitlab::HealthChecks::Result.new(true)) }
|
||||
end
|
||||
end
|
||||
|
|
@ -109,18 +109,6 @@ describe Milestone, models: true do
|
|||
it { expect(milestone.percent_complete(user)).to eq(75) }
|
||||
end
|
||||
|
||||
describe '#is_empty?' do
|
||||
before do
|
||||
milestone.issues << create(:issue, project: project)
|
||||
milestone.issues << create(:closed_issue, project: project)
|
||||
milestone.merge_requests << create(:merge_request)
|
||||
end
|
||||
|
||||
it { expect(milestone.closed_items_count(user)).to eq(1) }
|
||||
it { expect(milestone.total_items_count(user)).to eq(3) }
|
||||
it { expect(milestone.is_empty?(user)).to be_falsey }
|
||||
end
|
||||
|
||||
describe '#can_be_closed?' do
|
||||
it { expect(milestone.can_be_closed?).to be_truthy }
|
||||
end
|
||||
|
|
|
|||
28
yarn.lock
|
|
@ -895,8 +895,8 @@ bootstrap-sass@^3.3.6:
|
|||
resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.3.7.tgz#6596c7ab40f6637393323ab0bc80d064fc630498"
|
||||
|
||||
brace-expansion@^1.0.0:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9"
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59"
|
||||
dependencies:
|
||||
balanced-match "^0.4.1"
|
||||
concat-map "0.0.1"
|
||||
|
|
@ -970,7 +970,7 @@ browserify-zlib@^0.1.4:
|
|||
dependencies:
|
||||
pako "~0.2.0"
|
||||
|
||||
buffer-shims@^1.0.0:
|
||||
buffer-shims@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
|
||||
|
||||
|
|
@ -2416,8 +2416,8 @@ ieee754@^1.1.4:
|
|||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
||||
|
||||
ignore@^3.2.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.6.tgz#26e8da0644be0bb4cb39516f6c79f0e0f4ffe48c"
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd"
|
||||
|
||||
immediate@~3.0.5:
|
||||
version "3.0.6"
|
||||
|
|
@ -3773,19 +3773,19 @@ read-pkg@^1.0.0:
|
|||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816"
|
||||
"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.8.tgz#ad28b686f3554c73d39bc32347fa058356624705"
|
||||
dependencies:
|
||||
buffer-shims "^1.0.0"
|
||||
buffer-shims "~1.0.0"
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~1.0.6"
|
||||
string_decoder "~0.10.x"
|
||||
string_decoder "~1.0.0"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@~2.0.0, readable-stream@~2.0.6:
|
||||
readable-stream@^2.0.5, readable-stream@~2.0.0, readable-stream@~2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
|
||||
dependencies:
|
||||
|
|
@ -4321,6 +4321,12 @@ string_decoder@^0.10.25, string_decoder@~0.10.x:
|
|||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
||||
string_decoder@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.0.tgz#f06f41157b664d86069f84bdbdc9b0d8ab281667"
|
||||
dependencies:
|
||||
buffer-shims "~1.0.0"
|
||||
|
||||
stringstream@~0.0.4:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
||||
|
|
|
|||