summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke "Jared" Bennett <lbennett@gitlab.com>2017-04-07 15:57:13 +0100
committerLuke "Jared" Bennett <lbennett@gitlab.com>2017-04-07 16:12:59 +0100
commitb70d828f836f58aa9bc1af34fb6019857524619f (patch)
tree579ccac6b9d0f12ab5446500fdd5e85b7f42873d
parent99918be7312b11dd650924e39b2bf8b65a4004d8 (diff)
parent37e7fe2cc90de24c563fe70e4eff6f5d954bb630 (diff)
downloadgitlab-ce-b70d828f836f58aa9bc1af34fb6019857524619f.tar.gz
Merge branch 'master' into new-resolvable-discussion
-rw-r--r--app/assets/javascripts/build.js268
-rw-r--r--app/assets/stylesheets/pages/builds.scss32
-rw-r--r--app/assets/stylesheets/pages/issuable.scss1
-rw-r--r--app/controllers/concerns/requires_health_token.rb25
-rw-r--r--app/controllers/health_check_controller.rb21
-rw-r--r--app/controllers/health_controller.rb60
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/policies/global_policy.rb1
-rw-r--r--app/services/slash_commands/interpret_service.rb2
-rw-r--r--app/views/layouts/header/_default.html.haml12
-rw-r--r--app/views/projects/builds/show.html.haml5
-rw-r--r--app/views/projects/environments/show.html.haml2
-rw-r--r--changelogs/unreleased/24240-add-monitoring-endpoints.yml4
-rw-r--r--changelogs/unreleased/27580-fix-show-go-back.yml4
-rw-r--r--changelogs/unreleased/28574-jira-trigers.yml4
-rw-r--r--changelogs/unreleased/30056-rename-milestones-empty.yml4
-rw-r--r--changelogs/unreleased/30587-pipeline-icon-z.yml4
-rw-r--r--changelogs/unreleased/dz-hide-zero-counter.yml4
-rw-r--r--changelogs/unreleased/metrics-button-misplaced.yml4
-rw-r--r--config/routes.rb6
-rw-r--r--doc/README.md46
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/search/img/filter_issues_project.gifbin0 -> 1430218 bytes
-rwxr-xr-xdoc/user/search/img/issues_any_assignee.pngbin0 -> 90455 bytes
-rwxr-xr-xdoc/user/search/img/issues_assigned_to_you.pngbin0 -> 49079 bytes
-rwxr-xr-xdoc/user/search/img/issues_author.pngbin0 -> 55217 bytes
-rwxr-xr-xdoc/user/search/img/issues_mrs_shortcut.pngbin0 -> 34115 bytes
-rwxr-xr-xdoc/user/search/img/left_menu_bar.pngbin0 -> 37433 bytes
-rwxr-xr-xdoc/user/search/img/project_search.pngbin0 -> 41900 bytes
-rwxr-xr-xdoc/user/search/img/search_issues_board.pngbin0 -> 82113 bytes
-rwxr-xr-xdoc/user/search/img/sort_projects.pngbin0 -> 59495 bytes
-rw-r--r--doc/user/search/index.md94
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/gitlab/email/handler.rb6
-rw-r--r--lib/gitlab/git/repository.rb10
-rw-r--r--lib/gitlab/gitaly_client/commit.rb21
-rw-r--r--lib/gitlab/gitaly_client/notifications.rb9
-rw-r--r--lib/gitlab/gitaly_client/ref.rb13
-rw-r--r--lib/gitlab/gitaly_client/util.rb14
-rw-r--r--lib/gitlab/health_checks/base_abstract_check.rb45
-rw-r--r--lib/gitlab/health_checks/db_check.rb29
-rw-r--r--lib/gitlab/health_checks/fs_shards_check.rb117
-rw-r--r--lib/gitlab/health_checks/metric.rb3
-rw-r--r--lib/gitlab/health_checks/redis_check.rb25
-rw-r--r--lib/gitlab/health_checks/result.rb3
-rw-r--r--lib/gitlab/health_checks/simple_abstract_check.rb43
-rw-r--r--lib/gitlab/workhorse.rb10
-rw-r--r--public/404.html16
-rw-r--r--public/422.html17
-rw-r--r--public/500.html16
-rw-r--r--public/502.html16
-rw-r--r--public/503.html16
-rw-r--r--qa/qa/page/main/groups.rb2
-rw-r--r--spec/controllers/health_controller_spec.rb96
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb35
-rw-r--r--spec/javascripts/build_spec.js106
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/notifications_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_spec.rb2
-rw-r--r--spec/lib/gitlab/healthchecks/db_check_spec.rb6
-rw-r--r--spec/lib/gitlab/healthchecks/fs_shards_check_spec.rb127
-rw-r--r--spec/lib/gitlab/healthchecks/redis_check_spec.rb6
-rw-r--r--spec/lib/gitlab/healthchecks/simple_check_shared.rb66
-rw-r--r--spec/models/milestone_spec.rb12
-rw-r--r--yarn.lock28
67 files changed, 1225 insertions, 309 deletions
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 7cb022c848a..9e7df9ad350 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -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)
- });
- };
- 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);
+ if (log.status !== this.buildStatus) {
+ let pageUrl = this.pageUrl;
+
+ if (this.$autoScrollStatus.data('state') === 'enabled') {
+ pageUrl += DOWN_BUILD_TRACE;
}
- };
- })(this)
+
+ gl.utils.visitUrl(pageUrl);
+ }
+ }),
+ error: () => {
+ this.$buildRefreshAnimation.remove();
+ return this.initScrollMonitor();
+ },
});
};
- 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,
+ },
});
};
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 969fc75c6eb..03fddaeb163 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -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;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e84a05e3e9e..0bca3e93e4c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -196,6 +196,7 @@
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
+ z-index: 2;
&.right-sidebar-expanded {
width: $gutter_width;
diff --git a/app/controllers/concerns/requires_health_token.rb b/app/controllers/concerns/requires_health_token.rb
new file mode 100644
index 00000000000..34ab1a97649
--- /dev/null
+++ b/app/controllers/concerns/requires_health_token.rb
@@ -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
diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb
index 037da7d2bce..5d3109b7187 100644
--- a/app/controllers/health_check_controller.rb
+++ b/app/controllers/health_check_controller.rb
@@ -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
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
new file mode 100644
index 00000000000..df0fc3132ed
--- /dev/null
+++ b/app/controllers/health_controller.rb
@@ -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
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index ac205b9b738..652b1551928 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -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
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 3b90fd1c2c7..97e997d3899 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 1293cb1d486..f4c51cdfdf4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -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
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index cb72c2b4590..4757ba71680 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -10,6 +10,7 @@ class GlobalPolicy < BasePolicy
can! :access_api
can! :access_git
can! :receive_notifications
+ can! :use_slash_commands
end
end
end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 595653ea58a..49d45ec9dbd 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -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 = {}
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 7408fe3f6d0..17e553aeef0 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -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
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index d5fe771613c..0faad57a312 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -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
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index ff6aaebda22..7315e671056 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -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?
diff --git a/changelogs/unreleased/24240-add-monitoring-endpoints.yml b/changelogs/unreleased/24240-add-monitoring-endpoints.yml
new file mode 100644
index 00000000000..a22458965fc
--- /dev/null
+++ b/changelogs/unreleased/24240-add-monitoring-endpoints.yml
@@ -0,0 +1,4 @@
+---
+title: Add /-/readiness /-/liveness and /-/metrics endpoints to track application health
+merge_request: 10416
+author:
diff --git a/changelogs/unreleased/27580-fix-show-go-back.yml b/changelogs/unreleased/27580-fix-show-go-back.yml
new file mode 100644
index 00000000000..c7dbbe7a236
--- /dev/null
+++ b/changelogs/unreleased/27580-fix-show-go-back.yml
@@ -0,0 +1,4 @@
+---
+title: Shows 'Go Back' link only when browser history is available
+merge_request: 9017
+author:
diff --git a/changelogs/unreleased/28574-jira-trigers.yml b/changelogs/unreleased/28574-jira-trigers.yml
new file mode 100644
index 00000000000..6ebd2c0c2c2
--- /dev/null
+++ b/changelogs/unreleased/28574-jira-trigers.yml
@@ -0,0 +1,4 @@
+---
+title: Remove confusing placeholder for JIRA transition_id
+merge_request:
+author:
diff --git a/changelogs/unreleased/30056-rename-milestones-empty.yml b/changelogs/unreleased/30056-rename-milestones-empty.yml
new file mode 100644
index 00000000000..85db342b6df
--- /dev/null
+++ b/changelogs/unreleased/30056-rename-milestones-empty.yml
@@ -0,0 +1,4 @@
+---
+title: Removed Milestone#is_empty?
+merge_request: 10523
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/30587-pipeline-icon-z.yml b/changelogs/unreleased/30587-pipeline-icon-z.yml
new file mode 100644
index 00000000000..548d16ce142
--- /dev/null
+++ b/changelogs/unreleased/30587-pipeline-icon-z.yml
@@ -0,0 +1,4 @@
+---
+title: fix Status icons overlapping sidebar on mobile
+merge_request:
+author:
diff --git a/changelogs/unreleased/dz-hide-zero-counter.yml b/changelogs/unreleased/dz-hide-zero-counter.yml
new file mode 100644
index 00000000000..45f35044c48
--- /dev/null
+++ b/changelogs/unreleased/dz-hide-zero-counter.yml
@@ -0,0 +1,4 @@
+---
+title: Hide header counters for issue/mr/todos if zero
+merge_request: 10506
+author:
diff --git a/changelogs/unreleased/metrics-button-misplaced.yml b/changelogs/unreleased/metrics-button-misplaced.yml
new file mode 100644
index 00000000000..6c685ff32a5
--- /dev/null
+++ b/changelogs/unreleased/metrics-button-misplaced.yml
@@ -0,0 +1,4 @@
+---
+title: Moved the monitoring button inside the show view for the environments page
+merge_request:
+author:
diff --git a/config/routes.rb b/config/routes.rb
index 1a851da6203..1da226a3b57 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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'
diff --git a/doc/README.md b/doc/README.md
index df11d5e49a8..b0b3d2156a7 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -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.
+- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
+- [Environment Variables](administration/environment_variables.md) to configure GitLab.
+- [Git LFS configuration](workflow/lfs/lfs_administration.md)
+- [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.
+- [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.
+- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [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.
-- [Environment Variables](administration/environment_variables.md) to configure GitLab.
+- [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.
-- [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.
-- [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.
## Contributor documentation
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index e02f81fd972..f611029afdc 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -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.
diff --git a/doc/user/search/img/filter_issues_project.gif b/doc/user/search/img/filter_issues_project.gif
new file mode 100644
index 00000000000..d547588be5d
--- /dev/null
+++ b/doc/user/search/img/filter_issues_project.gif
Binary files differ
diff --git a/doc/user/search/img/issues_any_assignee.png b/doc/user/search/img/issues_any_assignee.png
new file mode 100755
index 00000000000..2f902bcc66c
--- /dev/null
+++ b/doc/user/search/img/issues_any_assignee.png
Binary files differ
diff --git a/doc/user/search/img/issues_assigned_to_you.png b/doc/user/search/img/issues_assigned_to_you.png
new file mode 100755
index 00000000000..36c670eedd5
--- /dev/null
+++ b/doc/user/search/img/issues_assigned_to_you.png
Binary files differ
diff --git a/doc/user/search/img/issues_author.png b/doc/user/search/img/issues_author.png
new file mode 100755
index 00000000000..792f9746db6
--- /dev/null
+++ b/doc/user/search/img/issues_author.png
Binary files differ
diff --git a/doc/user/search/img/issues_mrs_shortcut.png b/doc/user/search/img/issues_mrs_shortcut.png
new file mode 100755
index 00000000000..6380b337b54
--- /dev/null
+++ b/doc/user/search/img/issues_mrs_shortcut.png
Binary files differ
diff --git a/doc/user/search/img/left_menu_bar.png b/doc/user/search/img/left_menu_bar.png
new file mode 100755
index 00000000000..d68a71cba8e
--- /dev/null
+++ b/doc/user/search/img/left_menu_bar.png
Binary files differ
diff --git a/doc/user/search/img/project_search.png b/doc/user/search/img/project_search.png
new file mode 100755
index 00000000000..3150b40de29
--- /dev/null
+++ b/doc/user/search/img/project_search.png
Binary files differ
diff --git a/doc/user/search/img/search_issues_board.png b/doc/user/search/img/search_issues_board.png
new file mode 100755
index 00000000000..84048ae6a02
--- /dev/null
+++ b/doc/user/search/img/search_issues_board.png
Binary files differ
diff --git a/doc/user/search/img/sort_projects.png b/doc/user/search/img/sort_projects.png
new file mode 100755
index 00000000000..9bf2770b299
--- /dev/null
+++ b/doc/user/search/img/sort_projects.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
new file mode 100644
index 00000000000..9d1ca1adcb2
--- /dev/null
+++ b/doc/user/search/index.md
@@ -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.
+
+![menu bar - issues and MRs assigned to you](img/left_menu_bar.png)
+
+When you click **Issues**, you'll see the opened issues assigned to you straight away:
+
+![Issues assigned to you](img/issues_assigned_to_you.png)
+
+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:
+
+![shortcut to your issues and mrs](img/issues_mrs_shortcut.png)
+
+## 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.
+
+![filter issues in a project](img/filter_issues_project.gif)
+
+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:
+
+![search per project - shortcut](img/project_search.png)
+
+## 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**:
+
+![sort projects](img/sort_projects.png)
+
+## 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:
+
+![search and select issues to add to board](img/search_issues_board.png)
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 56c597dffcb..70d0d57204d 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -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
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index 35ea2e0ef59..b07c68d1498 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -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|
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 9e338282e96..fc473b2c21e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -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
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
index f15faebe27e..b7f39f3ef0b 100644
--- a/lib/gitlab/gitaly_client/commit.rb
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -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
)
diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notifications.rb
index f0d93ded91b..a94a54883db 100644
--- a/lib/gitlab/gitaly_client/notifications.rb
+++ b/lib/gitlab/gitaly_client/notifications.rb
@@ -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
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb
index fcdf452d567..d3c0743db4e 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref.rb
@@ -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
diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb
index d272c25d1f9..4acd297f5cb 100644
--- a/lib/gitlab/gitaly_client/util.rb
+++ b/lib/gitlab/gitaly_client/util.rb
@@ -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
diff --git a/lib/gitlab/health_checks/base_abstract_check.rb b/lib/gitlab/health_checks/base_abstract_check.rb
new file mode 100644
index 00000000000..7de6d4d9367
--- /dev/null
+++ b/lib/gitlab/health_checks/base_abstract_check.rb
@@ -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
diff --git a/lib/gitlab/health_checks/db_check.rb b/lib/gitlab/health_checks/db_check.rb
new file mode 100644
index 00000000000..fd94984f8a2
--- /dev/null
+++ b/lib/gitlab/health_checks/db_check.rb
@@ -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
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
new file mode 100644
index 00000000000..df962d203b7
--- /dev/null
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -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
diff --git a/lib/gitlab/health_checks/metric.rb b/lib/gitlab/health_checks/metric.rb
new file mode 100644
index 00000000000..1a2eab0b005
--- /dev/null
+++ b/lib/gitlab/health_checks/metric.rb
@@ -0,0 +1,3 @@
+module Gitlab::HealthChecks
+ Metric = Struct.new(:name, :value, :labels)
+end
diff --git a/lib/gitlab/health_checks/redis_check.rb b/lib/gitlab/health_checks/redis_check.rb
new file mode 100644
index 00000000000..57bbe5b3ad0
--- /dev/null
+++ b/lib/gitlab/health_checks/redis_check.rb
@@ -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
diff --git a/lib/gitlab/health_checks/result.rb b/lib/gitlab/health_checks/result.rb
new file mode 100644
index 00000000000..8086760023e
--- /dev/null
+++ b/lib/gitlab/health_checks/result.rb
@@ -0,0 +1,3 @@
+module Gitlab::HealthChecks
+ Result = Struct.new(:success, :message, :labels)
+end
diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb
new file mode 100644
index 00000000000..fbe1645c1b1
--- /dev/null
+++ b/lib/gitlab/health_checks/simple_abstract_check.rb
@@ -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
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index a8a7bf9bc12..e6e40f6945d 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -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'
diff --git a/public/404.html b/public/404.html
index b3b3a0fa3f3..03e98e81862 100644
--- a/public/404.html
+++ b/public/404.html
@@ -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>
diff --git a/public/422.html b/public/422.html
index 119e54ad8bd..49ebbe40f39 100644
--- a/public/422.html
+++ b/public/422.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>
diff --git a/public/500.html b/public/500.html
index 226ef3c40ea..516920f7471 100644
--- a/public/500.html
+++ b/public/500.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>
diff --git a/public/502.html b/public/502.html
index f037b81bace..189458c9816 100644
--- a/public/502.html
+++ b/public/502.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>
diff --git a/public/503.html b/public/503.html
index f946a087871..b09b0e2a67e 100644
--- a/public/503.html
+++ b/public/503.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>
diff --git a/qa/qa/page/main/groups.rb b/qa/qa/page/main/groups.rb
index 84597719a84..169c5ebc967 100644
--- a/qa/qa/page/main/groups.rb
+++ b/qa/qa/page/main/groups.rb
@@ -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',
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
new file mode 100644
index 00000000000..b8b6e0c3a88
--- /dev/null
+++ b/spec/controllers/health_controller_spec.rb
@@ -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
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index a1718912fc6..4fca7577e74 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -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
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index cb48f14fd9a..edd4b3c1440 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -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));
-
- success.call(context, {
+ expect($.ajax.calls.count()).toBe(1);
+
+ // 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);
+
+ 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));
- success.call(context, {
+ 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);
diff --git a/spec/lib/gitlab/gitaly_client/commit_spec.rb b/spec/lib/gitlab/gitaly_client/commit_spec.rb
index 4684b1d1ac0..58f11ff8906 100644
--- a/spec/lib/gitlab/gitaly_client/commit_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notifications_spec.rb
index 39c2048fef8..b87dacb175b 100644
--- a/spec/lib/gitlab/gitaly_client/notifications_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/notifications_spec.rb
@@ -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).
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb
index 79c9ca993e4..5405eafd281 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb
@@ -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)
diff --git a/spec/lib/gitlab/healthchecks/db_check_spec.rb b/spec/lib/gitlab/healthchecks/db_check_spec.rb
new file mode 100644
index 00000000000..33c6c24449c
--- /dev/null
+++ b/spec/lib/gitlab/healthchecks/db_check_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/healthchecks/fs_shards_check_spec.rb b/spec/lib/gitlab/healthchecks/fs_shards_check_spec.rb
new file mode 100644
index 00000000000..4cd8cf313a5
--- /dev/null
+++ b/spec/lib/gitlab/healthchecks/fs_shards_check_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/healthchecks/redis_check_spec.rb b/spec/lib/gitlab/healthchecks/redis_check_spec.rb
new file mode 100644
index 00000000000..734cdcb893e
--- /dev/null
+++ b/spec/lib/gitlab/healthchecks/redis_check_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/healthchecks/simple_check_shared.rb b/spec/lib/gitlab/healthchecks/simple_check_shared.rb
new file mode 100644
index 00000000000..1fa6d0faef9
--- /dev/null
+++ b/spec/lib/gitlab/healthchecks/simple_check_shared.rb
@@ -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
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index f3f48f951a8..e3e8e6d571c 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -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
diff --git a/yarn.lock b/yarn.lock
index e08c2f7f053..783dba2ff73 100644
--- a/yarn.lock
+++ b/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"