summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml1
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock10
-rw-r--r--app/assets/javascripts/issuables_list/components/issuable.vue18
-rw-r--r--app/assets/javascripts/profile/gl_crop.js29
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue26
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js4
-rw-r--r--app/controllers/application_controller.rb10
-rw-r--r--app/controllers/concerns/confirm_email_warning.rb7
-rw-r--r--app/controllers/concerns/sourcegraph_gon.rb2
-rw-r--r--app/controllers/concerns/uploads_actions.rb17
-rw-r--r--app/models/environment.rb3
-rw-r--r--app/serializers/issuable_sidebar_extras_entity.rb9
-rw-r--r--app/services/merge_requests/ff_merge_service.rb18
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml8
-rw-r--r--changelogs/unreleased/31184-refactor-disabled-sidebar-notification-to-vue.yml5
-rw-r--r--changelogs/unreleased/34564-vulnerability-issue-links.yml5
-rw-r--r--changelogs/unreleased/35618-add-clipboard-button-to-package-registry-information.yml5
-rw-r--r--changelogs/unreleased/35709-update-squash-commit-sha-only-on-successful-merge.yml5
-rw-r--r--changelogs/unreleased/add-dead-jobs-to-api-sidekiq-metrics.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-fix-group-export-descendants.yml5
-rw-r--r--changelogs/unreleased/jej-group-saml-test-button-shows-response.yml5
-rw-r--r--db/migrate/20191115091425_create_vulnerability_issue_links.rb23
-rw-r--r--db/schema.rb15
-rw-r--r--doc/api/sidekiq_metrics.md6
-rw-r--r--doc/ci/variables/predefined_variables.md210
-rw-r--r--doc/ci/yaml/README.md10
-rw-r--r--doc/development/policies.md15
-rw-r--r--doc/update/README.md8
-rw-r--r--doc/user/incident_management/index.md2
-rw-r--r--doc/user/permissions.md1
-rw-r--r--doc/user/project/clusters/img/kubernetes_pod_logs_v12_4.pngbin393690 -> 0 bytes
-rw-r--r--doc/user/project/clusters/img/kubernetes_pod_logs_v12_5.pngbin0 -> 183707 bytes
-rw-r--r--doc/user/project/clusters/img/sidebar_menu_pod_logs_v12_5.pngbin0 -> 13681 bytes
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md32
-rw-r--r--doc/user/project/integrations/img/grafana_panel_v12_5.pngbin0 -> 44193 bytes
-rw-r--r--doc/user/project/integrations/img/grafana_sharing_dialog_v12_5.pngbin0 -> 41203 bytes
-rw-r--r--doc/user/project/integrations/img/http_proxy_access_v12_5.pngbin0 -> 47813 bytes
-rw-r--r--doc/user/project/integrations/img/rendered_grafana_embed_v12_5.pngbin0 -> 61194 bytes
-rw-r--r--doc/user/project/integrations/img/select_query_variables_v12_5.pngbin0 -> 7368 bytes
-rw-r--r--doc/user/project/integrations/prometheus.md54
-rw-r--r--lib/api/sidekiq_metrics.rb3
-rw-r--r--lib/declarative_policy.rb9
-rw-r--r--lib/gitlab/import_export/group_tree_saver.rb6
-rw-r--r--locale/gitlab.pot36
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--spec/controllers/application_controller_spec.rb14
-rw-r--r--spec/controllers/uploads_controller_spec.rb24
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb1
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json2
-rw-r--r--spec/frontend/issuables_list/components/issuable_spec.js7
-rw-r--r--spec/frontend/monitoring/mock_data.js135
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js367
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js (renamed from spec/javascripts/monitoring/store/mutations_spec.js)45
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js (renamed from spec/javascripts/monitoring/store/utils_spec.js)0
-rw-r--r--spec/javascripts/monitoring/mock_data.js146
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js335
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js21
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/group_tree_saver_spec.rb20
-rw-r--r--spec/models/environment_spec.rb6
-rw-r--r--spec/requests/user_avatar_spec.rb36
-rw-r--r--spec/serializers/issuable_sidebar_extras_entity_spec.rb20
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb50
-rw-r--r--spec/spec_helper.rb5
68 files changed, 1094 insertions, 784 deletions
diff --git a/.gitignore b/.gitignore
index 6bec6d2596f..b8cbfe9966d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,6 +59,7 @@ eslint-report.html
/public/uploads.*
/public/uploads/
/shared/artifacts/
+/spec/examples.txt
/rails_best_practices_output.html
/tags
/vendor/bundle/*
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 1c271ad8299..3cb5a40a8b5 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -66,3 +66,4 @@ schedule:package-and-qa:
- .default-only
- .only:variables_refs-canonical-dot-com-schedules
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
+ allow_failure: true
diff --git a/Gemfile b/Gemfile
index 5a721364084..5dd7688579f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -64,7 +64,7 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
-gem 'rubyzip', '~> 1.2.2', require: 'zip'
+gem 'rubyzip', '~> 1.3.0', require: 'zip'
# GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.2'
@@ -72,7 +72,7 @@ gem 'acme-client', '~> 2.0.2'
gem 'browser', '~> 2.5'
# GPG
-gem 'gpgme', '~> 2.0.18'
+gem 'gpgme', '~> 2.0.19'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -151,7 +151,7 @@ gem 'asciidoctor-plantuml', '0.0.9'
gem 'rouge', '~> 3.11.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
-gem 'nokogiri', '~> 1.10.4'
+gem 'nokogiri', '~> 1.10.5'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
diff --git a/Gemfile.lock b/Gemfile.lock
index 55fc902584c..5b7785b9475 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -411,7 +411,7 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
- gpgme (2.0.18)
+ gpgme (2.0.19)
mini_portile2 (~> 2.3)
grape (1.1.0)
activesupport
@@ -902,7 +902,7 @@ GEM
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
rubypants (0.2.0)
- rubyzip (1.2.2)
+ rubyzip (1.3.0)
rugged (0.28.3.1)
safe_yaml (1.0.4)
sanitize (4.6.6)
@@ -1183,7 +1183,7 @@ DEPENDENCIES
gon (~> 6.2)
google-api-client (~> 0.23)
google-protobuf (~> 3.8.0)
- gpgme (~> 2.0.18)
+ gpgme (~> 2.0.19)
grape (~> 1.1.0)
grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.1)
@@ -1227,7 +1227,7 @@ DEPENDENCIES
net-ldap
net-ntp
net-ssh (~> 5.2)
- nokogiri (~> 1.10.4)
+ nokogiri (~> 1.10.5)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1293,7 +1293,7 @@ DEPENDENCIES
ruby-prof (~> 1.0.0)
ruby-progressbar
ruby_parser (~> 3.8)
- rubyzip (~> 1.2.2)
+ rubyzip (~> 1.3.0)
rugged (~> 0.28)
sanitize (~> 4.6)
sassc-rails (~> 2.1.0)
diff --git a/app/assets/javascripts/issuables_list/components/issuable.vue b/app/assets/javascripts/issuables_list/components/issuable.vue
index 41b826e0394..eb924609a8a 100644
--- a/app/assets/javascripts/issuables_list/components/issuable.vue
+++ b/app/assets/javascripts/issuables_list/components/issuable.vue
@@ -54,6 +54,11 @@ export default {
},
},
computed: {
+ milestoneLink() {
+ const { title } = this.issuable.milestone;
+
+ return this.issuableLink({ milestone_title: title });
+ },
hasLabels() {
return Boolean(this.issuable.labels && this.issuable.labels.length);
},
@@ -167,8 +172,11 @@ export default {
color: label.text_color,
};
},
+ issuableLink(params) {
+ return mergeUrlParams(params, this.baseUrl);
+ },
labelHref({ name }) {
- return mergeUrlParams({ 'label_name[]': name }, this.baseUrl);
+ return this.issuableLink({ 'label_name[]': name });
},
onSelect(ev) {
this.$emit('select', {
@@ -216,9 +224,9 @@ export default {
></i>
<gl-link :href="issuable.web_url">{{ issuable.title }}</gl-link>
</span>
- <span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block">
- {{ issuable.task_status }}
- </span>
+ <span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block">{{
+ issuable.task_status
+ }}</span>
</div>
<div class="issuable-info">
@@ -233,7 +241,7 @@ export default {
v-if="issuable.milestone"
v-gl-tooltip
class="d-none d-sm-inline-block mr-1 js-milestone"
- :href="issuable.milestone.web_url"
+ :href="milestoneLink"
:title="milestoneTooltipText"
>
<i class="fa fa-clock-o"></i>
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 44bc2d9f5f8..880e1a88975 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-useless-escape, no-var, no-underscore-dangle, func-names, no-return-assign, one-var, consistent-return, class-methods-use-this */
+/* eslint-disable no-useless-escape, no-underscore-dangle, func-names, no-return-assign, consistent-return, class-methods-use-this */
import $ from 'jquery';
import 'cropper';
@@ -59,8 +59,7 @@ import _ from 'underscore';
}
bindEvents() {
- var _this;
- _this = this;
+ const _this = this;
this.fileInput.on('change', function(e) {
_this.onFileInputChange(e, this);
this.value = null;
@@ -70,8 +69,7 @@ import _ from 'underscore';
this.modalCrop.on('hidden.bs.modal', this.onModalHide);
this.uploadImageBtn.on('click', this.onUploadImageBtnClick);
this.cropActionsBtn.on('click', function() {
- var btn;
- btn = this;
+ const btn = this;
return _this.onActionBtnClick(btn);
});
return (this.croppedImageBlob = null);
@@ -82,8 +80,7 @@ import _ from 'underscore';
}
onModalShow() {
- var _this;
- _this = this;
+ const _this = this;
return this.modalCropImg.cropper({
viewMode: 1,
center: false,
@@ -128,8 +125,7 @@ import _ from 'underscore';
}
onActionBtnClick(btn) {
- var data;
- data = $(btn).data();
+ const data = $(btn).data();
if (this.modalCropImg.data('cropper') && data.method) {
return this.modalCropImg.cropper(data.method, data.option);
}
@@ -140,9 +136,8 @@ import _ from 'underscore';
}
readFile(input) {
- var _this, reader;
- _this = this;
- reader = new FileReader();
+ const _this = this;
+ const reader = new FileReader();
reader.onload = () => {
_this.modalCropImg.attr('src', reader.result);
return _this.modalCrop.modal('show');
@@ -151,9 +146,10 @@ import _ from 'underscore';
}
dataURLtoBlob(dataURL) {
- var array, binary, i, len;
- binary = atob(dataURL.split(',')[1]);
- array = [];
+ let i = 0;
+ let len = 0;
+ const binary = atob(dataURL.split(',')[1]);
+ const array = [];
for (i = 0, len = binary.length; i < len; i += 1) {
array.push(binary.charCodeAt(i));
@@ -164,9 +160,8 @@ import _ from 'underscore';
}
setPreview() {
- var filename;
+ const filename = this.fileInput.val().replace(FILENAMEREGEX, '');
this.previewImage.attr('src', this.dataURL);
- filename = this.fileInput.val().replace(FILENAMEREGEX, '');
return this.filename.text(filename);
}
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
index 95a2c8cce6e..91fe5fc50a9 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
@@ -33,6 +33,8 @@ export default {
<div class="block subscriptions">
<subscriptions
:loading="store.isFetching.subscriptions"
+ :project-emails-disabled="store.projectEmailsDisabled"
+ :subscribe-disabled-description="store.subscribeDisabledDescription"
:subscribed="store.subscribed"
@toggleSubscription="onToggleSubscription"
/>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index ea5edb3ce3f..0e489b28593 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -26,6 +26,16 @@ export default {
required: false,
default: false,
},
+ projectEmailsDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ subscribeDisabledDescription: {
+ type: String,
+ required: false,
+ default: '',
+ },
subscribed: {
type: Boolean,
required: false,
@@ -42,11 +52,23 @@ export default {
return this.subscribed === null;
},
notificationIcon() {
+ if (this.projectEmailsDisabled) {
+ return ICON_OFF;
+ }
return this.subscribed ? ICON_ON : ICON_OFF;
},
notificationTooltip() {
+ if (this.projectEmailsDisabled) {
+ return this.subscribeDisabledDescription;
+ }
return this.subscribed ? LABEL_ON : LABEL_OFF;
},
+ notificationText() {
+ if (this.projectEmailsDisabled) {
+ return this.subscribeDisabledDescription;
+ }
+ return __('Notifications');
+ },
},
methods: {
/**
@@ -81,6 +103,7 @@ export default {
<template>
<div>
<span
+ ref="tooltip"
v-tooltip
class="sidebar-collapsed-icon"
:title="notificationTooltip"
@@ -96,8 +119,9 @@ export default {
class="sidebar-item-icon is-active"
/>
</span>
- <span class="issuable-header-text hide-collapsed float-left"> {{ __('Notifications') }} </span>
+ <span class="issuable-header-text hide-collapsed float-left"> {{ notificationText }} </span>
<toggle-button
+ v-if="!projectEmailsDisabled"
ref="toggleButton"
:is-loading="showLoadingState"
:value="subscribed"
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 63c4a2a3f84..66f7f9e3c66 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -28,6 +28,8 @@ export default class SidebarStore {
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
+ this.projectEmailsDisabled = false;
+ this.subscribeDisabledDescription = '';
this.subscribed = null;
SidebarStore.singleton = this;
@@ -53,6 +55,8 @@ export default class SidebarStore {
}
setSubscriptionsData(data) {
+ this.projectEmailsDisabled = data.project_emails_disabled || false;
+ this.subscribeDisabledDescription = data.subscribe_disabled_description;
this.isFetching.subscriptions = false;
this.subscribed = data.subscribed || false;
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 4d55d7f00f0..25c1d80b117 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -20,11 +20,11 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
- before_action :check_password_expiration, if: :html_request?
+ before_action :check_password_expiration
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
- before_action :add_gon_variables, if: :html_request?
+ before_action :add_gon_variables, unless: [:peek_request?, :json_request?]
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
before_action :active_user_check, unless: :devise_controller?
@@ -455,8 +455,8 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
- def html_request?
- request.format.html?
+ def peek_request?
+ request.path.start_with?('/-/peek')
end
def json_request?
@@ -466,7 +466,7 @@ class ApplicationController < ActionController::Base
def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
- html_request? && !devise_controller?
+ !(peek_request? || devise_controller?)
end
def set_usage_stats_consent_flag
diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb
index 32e1a46e580..86df0010665 100644
--- a/app/controllers/concerns/confirm_email_warning.rb
+++ b/app/controllers/concerns/confirm_email_warning.rb
@@ -4,18 +4,15 @@ module ConfirmEmailWarning
extend ActiveSupport::Concern
included do
- before_action :set_confirm_warning, if: :show_confirm_warning?
+ before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) }
end
protected
- def show_confirm_warning?
- html_request? && request.get? && Feature.enabled?(:soft_email_confirmation)
- end
-
def set_confirm_warning
return unless current_user
return if current_user.confirmed?
+ return if peek_request? || json_request? || !request.get?
email = current_user.unconfirmed_email || current_user.email
diff --git a/app/controllers/concerns/sourcegraph_gon.rb b/app/controllers/concerns/sourcegraph_gon.rb
index 01925cf9d4d..ab4abd734fb 100644
--- a/app/controllers/concerns/sourcegraph_gon.rb
+++ b/app/controllers/concerns/sourcegraph_gon.rb
@@ -4,7 +4,7 @@ module SourcegraphGon
extend ActiveSupport::Concern
included do
- before_action :push_sourcegraph_gon, if: :html_request?
+ before_action :push_sourcegraph_gon, unless: :json_request?
end
private
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 023c41821da..b87779c22d3 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -1,16 +1,11 @@
# frozen_string_literal: true
module UploadsActions
- extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
- included do
- prepend_before_action :set_request_format_from_path_extension
- end
-
def create
uploader = UploadService.new(model, params[:file], uploader_class).execute
@@ -69,18 +64,6 @@ module UploadsActions
private
- # From ActionDispatch::Http::MimeNegotiation. We have an initializer that
- # monkey-patches this method out (so that repository paths don't guess a
- # format based on extension), but we do want this behaviour when serving
- # uploads.
- def set_request_format_from_path_extension
- path = request.headers['action_dispatch.original_path'] || request.headers['PATH_INFO']
-
- if match = path&.match(/\.(\w+)\z/)
- request.format = match.captures.first
- end
- end
-
def uploader_class
raise NotImplementedError
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 569299ad4b4..327b1e594d7 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -4,6 +4,9 @@ class Environment < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
+ self.reactive_cache_refresh_interval = 1.minute
+ self.reactive_cache_lifetime = 55.seconds
+
belongs_to :project, required: true
has_many :deployments, -> { visible }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/serializers/issuable_sidebar_extras_entity.rb b/app/serializers/issuable_sidebar_extras_entity.rb
index fb35b7522c5..0e1fcc58d7a 100644
--- a/app/serializers/issuable_sidebar_extras_entity.rb
+++ b/app/serializers/issuable_sidebar_extras_entity.rb
@@ -3,11 +3,20 @@
class IssuableSidebarExtrasEntity < Grape::Entity
include RequestAwareEntity
include TimeTrackableEntity
+ include NotificationsHelper
expose :participants, using: ::API::Entities::UserBasic do |issuable|
issuable.participants(request.current_user)
end
+ expose :project_emails_disabled do |issuable|
+ issuable.project.emails_disabled?
+ end
+
+ expose :subscribe_disabled_description do |issuable|
+ notification_description(:owner_disabled)
+ end
+
expose :subscribed do |issuable|
issuable.subscribed?(request.current_user, issuable.project)
end
diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb
index cfbee3e2ff6..6f1fa607ef9 100644
--- a/app/services/merge_requests/ff_merge_service.rb
+++ b/app/services/merge_requests/ff_merge_service.rb
@@ -11,19 +11,21 @@ module MergeRequests
private
def commit
- repository.ff_merge(current_user,
- source,
- merge_request.target_branch,
- merge_request: merge_request)
+ ff_merge = repository.ff_merge(current_user,
+ source,
+ merge_request.target_branch,
+ merge_request: merge_request)
+
+ if merge_request.squash
+ merge_request.update_column(:squash_commit_sha, merge_request.in_progress_merge_commit_sha)
+ end
+
+ ff_merge
rescue Gitlab::Git::PreReceiveError => e
raise MergeError, e.message
rescue StandardError => e
raise MergeError, "Something went wrong during merge: #{e.message}"
ensure
- if merge_request.squash
- merge_request.update_column(:squash_commit_sha, merge_request.in_progress_merge_commit_sha)
- end
-
merge_request.update(in_progress_merge_commit_sha: nil)
end
end
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c8b2adcf084..2170b88c7c3 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -141,13 +141,7 @@
.js-sidebar-participants-entry-point
- if signed_in
- - if issuable_sidebar[:project_emails_disabled]
- .block.js-emails-disabled
- .sidebar-collapsed-icon.has-tooltip{ title: notification_description(:owner_disabled), data: { placement: "left", container: "body", boundary: 'viewport' } }
- = notification_setting_icon
- .hide-collapsed= notification_description(:owner_disabled)
- - else
- .js-sidebar-subscriptions-entry-point
+ .js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference]
.block.project-reference
diff --git a/changelogs/unreleased/31184-refactor-disabled-sidebar-notification-to-vue.yml b/changelogs/unreleased/31184-refactor-disabled-sidebar-notification-to-vue.yml
new file mode 100644
index 00000000000..8ef877ff2fc
--- /dev/null
+++ b/changelogs/unreleased/31184-refactor-disabled-sidebar-notification-to-vue.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor disabled sidebar notifications to Vue
+merge_request: 20007
+author: minghuan lei
+type: other
diff --git a/changelogs/unreleased/34564-vulnerability-issue-links.yml b/changelogs/unreleased/34564-vulnerability-issue-links.yml
new file mode 100644
index 00000000000..0f6f5610159
--- /dev/null
+++ b/changelogs/unreleased/34564-vulnerability-issue-links.yml
@@ -0,0 +1,5 @@
+---
+title: Update the DB schema to allow linking between Vulnerabilities and Issues
+merge_request: 19852
+author:
+type: added
diff --git a/changelogs/unreleased/35618-add-clipboard-button-to-package-registry-information.yml b/changelogs/unreleased/35618-add-clipboard-button-to-package-registry-information.yml
new file mode 100644
index 00000000000..faf22561fb0
--- /dev/null
+++ b/changelogs/unreleased/35618-add-clipboard-button-to-package-registry-information.yml
@@ -0,0 +1,5 @@
+---
+title: Adds a copy button next to package metadata on the details page
+merge_request: 19881
+author:
+type: added
diff --git a/changelogs/unreleased/35709-update-squash-commit-sha-only-on-successful-merge.yml b/changelogs/unreleased/35709-update-squash-commit-sha-only-on-successful-merge.yml
new file mode 100644
index 00000000000..8ab549c4b06
--- /dev/null
+++ b/changelogs/unreleased/35709-update-squash-commit-sha-only-on-successful-merge.yml
@@ -0,0 +1,5 @@
+---
+title: Update squash_commit_sha only on successful merge
+merge_request: 19688
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-dead-jobs-to-api-sidekiq-metrics.yml b/changelogs/unreleased/add-dead-jobs-to-api-sidekiq-metrics.yml
new file mode 100644
index 00000000000..ac06e83bc73
--- /dev/null
+++ b/changelogs/unreleased/add-dead-jobs-to-api-sidekiq-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Add dead jobs to Sidekiq metrics API
+merge_request: 19350
+author: Marco Peterseil
+type: added
diff --git a/changelogs/unreleased/georgekoltsov-fix-group-export-descendants.yml b/changelogs/unreleased/georgekoltsov-fix-group-export-descendants.yml
new file mode 100644
index 00000000000..b826e4bbac4
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-fix-group-export-descendants.yml
@@ -0,0 +1,5 @@
+---
+title: Fix sub group export to export direct children
+merge_request: 20172
+author:
+type: fixed
diff --git a/changelogs/unreleased/jej-group-saml-test-button-shows-response.yml b/changelogs/unreleased/jej-group-saml-test-button-shows-response.yml
new file mode 100644
index 00000000000..03dcfcfb0c8
--- /dev/null
+++ b/changelogs/unreleased/jej-group-saml-test-button-shows-response.yml
@@ -0,0 +1,5 @@
+---
+title: Users can verify SAML configuration and view SamlResponse XML
+merge_request: 18362
+author:
+type: added
diff --git a/db/migrate/20191115091425_create_vulnerability_issue_links.rb b/db/migrate/20191115091425_create_vulnerability_issue_links.rb
new file mode 100644
index 00000000000..8398b6357c4
--- /dev/null
+++ b/db/migrate/20191115091425_create_vulnerability_issue_links.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class CreateVulnerabilityIssueLinks < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :vulnerability_issue_links do |t|
+ # index: false because idx_vulnerability_issue_links_on_vulnerability_id_and_issue_id refers the same column
+ t.references :vulnerability, null: false, index: false, foreign_key: { on_delete: :cascade }
+ # index: true is implied
+ t.references :issue, null: false, foreign_key: { on_delete: :cascade }
+ t.integer 'link_type', limit: 2, null: false, default: 1 # 'related'
+ t.index %i[vulnerability_id issue_id],
+ name: 'idx_vulnerability_issue_links_on_vulnerability_id_and_issue_id',
+ unique: true # only one link (and of only one type) is allowed
+ t.index %i[vulnerability_id link_type],
+ name: 'idx_vulnerability_issue_links_on_vulnerability_id_and_link_type',
+ where: 'link_type = 2',
+ unique: true # only one 'created' link per vulnerability is allowed
+ t.timestamps_with_timezone
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fd36599e1f9..e3413722991 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_11_14_173624) do
+ActiveRecord::Schema.define(version: 2019_11_15_091425) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -4018,6 +4018,17 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do
t.index ["project_id", "fingerprint"], name: "index_vulnerability_identifiers_on_project_id_and_fingerprint", unique: true
end
+ create_table "vulnerability_issue_links", force: :cascade do |t|
+ t.bigint "vulnerability_id", null: false
+ t.bigint "issue_id", null: false
+ t.integer "link_type", limit: 2, default: 1, null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.index ["issue_id"], name: "index_vulnerability_issue_links_on_issue_id"
+ t.index ["vulnerability_id", "issue_id"], name: "idx_vulnerability_issue_links_on_vulnerability_id_and_issue_id", unique: true
+ t.index ["vulnerability_id", "link_type"], name: "idx_vulnerability_issue_links_on_vulnerability_id_and_link_type", unique: true, where: "(link_type = 2)"
+ end
+
create_table "vulnerability_occurrence_identifiers", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -4547,6 +4558,8 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do
add_foreign_key "vulnerability_feedback", "users", column: "author_id", on_delete: :cascade
add_foreign_key "vulnerability_feedback", "users", column: "comment_author_id", name: "fk_94f7c8a81e", on_delete: :nullify
add_foreign_key "vulnerability_identifiers", "projects", on_delete: :cascade
+ add_foreign_key "vulnerability_issue_links", "issues", on_delete: :cascade
+ add_foreign_key "vulnerability_issue_links", "vulnerabilities", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_identifiers", "vulnerability_identifiers", column: "identifier_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_identifiers", "vulnerability_occurrences", column: "occurrence_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_pipelines", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
diff --git a/doc/api/sidekiq_metrics.md b/doc/api/sidekiq_metrics.md
index 5f2202fa51d..95449d1ff77 100644
--- a/doc/api/sidekiq_metrics.md
+++ b/doc/api/sidekiq_metrics.md
@@ -92,7 +92,8 @@ Example response:
"jobs": {
"processed": 2,
"failed": 0,
- "enqueued": 0
+ "enqueued": 0,
+ "dead": 0
}
}
```
@@ -145,7 +146,8 @@ Example response:
"jobs": {
"processed": 2,
"failed": 0,
- "enqueued": 0
+ "enqueued": 0,
+ "dead": 0
}
}
```
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 8b4208a8703..b93ff62cc21 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -21,111 +21,111 @@ future GitLab releases.**
## Variables reference
-| Variable | GitLab | Runner | Description |
-|-------------------------------------------|--------|--------|-------------|
-| `ARTIFACT_DOWNLOAD_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
-| `CHAT_CHANNEL` | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command |
-| `CHAT_INPUT` | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command |
-| `CI` | all | 0.4 | Mark that job is executed in CI environment |
-| `CI_API_V4_URL` | 11.7 | all | The GitLab API v4 root URL |
-| `CI_BUILDS_DIR` | all | 11.10 | Top-level directory where builds are executed. |
-| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a merge request. Only populated when there is a merge request associated with the pipeline. |
-| `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
-| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
-| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built |
-| `CI_COMMIT_REF_PROTECTED` | 11.11 | all | If the job is running on a protected branch |
-| `CI_COMMIT_REF_SLUG` | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
-| `CI_COMMIT_SHA` | 9.0 | all | The commit revision for which project is built |
-| `CI_COMMIT_SHORT_SHA` | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` |
-| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
-| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit - the full first line of the message |
-| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. |
-| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. |
-| `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
-| `CI_DEBUG_TRACE` | all | 1.7 | Whether [debug logging (tracing)](README.md#debug-logging) is enabled |
-| `CI_DEFAULT_BRANCH` | 12.4 | all | The name of the default branch for the project. |
-| `CI_DEPLOY_PASSWORD` | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
-| `CI_DEPLOY_USER` | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
-| `CI_DISPOSABLE_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
-| `CI_ENVIRONMENT_NAME` | 8.15 | all | The name of the environment for this job. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
-| `CI_ENVIRONMENT_SLUG` | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
-| `CI_ENVIRONMENT_URL` | 9.3 | all | The URL of the environment for this job. Only present if [`environment:url`](../yaml/README.md#environmenturl) is set. |
-| `CI_EXTERNAL_PULL_REQUEST_IID` | 12.3 | all | Pull Request ID from GitHub if the [pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
-| `CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME` | 12.3 | all | The source branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
-| `CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the source branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
-| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME` | 12.3 | all | The target branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
-| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the target branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
-| `CI_JOB_ID` | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
-| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
-| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
-| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] |
-| `CI_JOB_URL` | 11.1 | 0.5 | Job details URL |
-| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of username(s) of assignee(s) for the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_ID` | 11.6 | all | The ID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_IID` | 11.6 | all | The IID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_PROJECT_ID` | 11.6 | all | The ID of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_PROJECT_PATH` | 11.6 | all | The path of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_PROJECT_URL` | 11.6 | all | The URL of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_REF_PATH` | 11.6 | all | The ref path of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | 11.6 | all | The source branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_SOURCE_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the source branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used, the merge request is created, and the pipeline is a [merged result pipeline](../merge_request_pipelines/pipelines_for_merged_results/index.md). **(PREMIUM)** |
-| `CI_MERGE_REQUEST_SOURCE_PROJECT_ID` | 11.6 | all | The ID of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH` | 11.6 | all | The path of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_SOURCE_PROJECT_URL` | 11.6 | all | The URL of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | 11.6 | all | The target branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_MERGE_REQUEST_TARGET_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the target branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used, the merge request is created, and the pipeline is a [merged result pipeline](../merge_request_pipelines/pipelines_for_merged_results/index.md). **(PREMIUM)** |
-| `CI_MERGE_REQUEST_TITLE` | 11.9 | all | The title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
-| `CI_NODE_INDEX` | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
-| `CI_NODE_TOTAL` | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
-| `CI_PAGES_DOMAIN` | 11.8 | all | The configured domain that hosts GitLab Pages. |
-| `CI_PAGES_URL` | 11.8 | all | URL to GitLab Pages-built pages. Always belongs to a subdomain of `CI_PAGES_DOMAIN`. |
-| `CI_PIPELINE_ID` | 8.10 | all | The unique id of the current pipeline that GitLab CI uses internally |
-| `CI_PIPELINE_IID` | 11.0 | all | The unique id of the current pipeline scoped to project |
-| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
-| `CI_PIPELINE_TRIGGERED` | all | all | The flag to indicate that job was [triggered](../triggers/README.md) |
-| `CI_PIPELINE_URL` | 11.1 | 0.5 | Pipeline details URL |
-| `CI_PROJECT_DIR` | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
-| `CI_PROJECT_ID` | all | all | The unique id of the current project that GitLab CI uses internally |
-| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is currently being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
-| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
-| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
-| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
-| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercased list of the languages used in the repository (e.g. `ruby,javascript,html,css`) |
-| `CI_PROJECT_TITLE` | 12.4 | all | The human-readable project name as displayed in the GitLab web interface. |
-| `CI_PROJECT_URL` | 8.10 | 0.5 | The HTTP(S) address to access project |
-| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) |
-| `CI_REGISTRY` | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry. This variable will include a `:port` value if one has been specified in the registry configuration. |
-| `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
-| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
-| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
-| `CI_REPOSITORY_URL` | 9.0 | all | The URL to clone the Git repository |
-| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner as saved in GitLab |
-| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
-| `CI_RUNNER_ID` | 8.10 | 0.5 | The unique id of runner being used |
-| `CI_RUNNER_REVISION` | all | 10.6 | GitLab Runner revision that is executing the current job |
-| `CI_RUNNER_SHORT_TOKEN` | all | 12.3 | First eight characters of GitLab Runner's token used to authenticate new job requests. Used as Runner's unique ID |
-| `CI_RUNNER_TAGS` | 8.10 | 0.5 | The defined runner tags |
-| `CI_RUNNER_VERSION` | all | 10.6 | GitLab Runner version that is executing the current job |
-| `CI_SERVER` | all | all | Mark that job is executed in CI environment |
-| `CI_SERVER_HOST` | 12.1 | all | Host component of the GitLab instance URL, without protocol and port (like `gitlab.example.com`) |
-| `CI_SERVER_NAME` | all | all | The name of CI server that is used to coordinate jobs |
-| `CI_SERVER_REVISION` | all | all | GitLab revision that is used to schedule jobs |
-| `CI_SERVER_VERSION` | all | all | GitLab version that is used to schedule jobs |
-| `CI_SERVER_VERSION_MAJOR` | 11.4 | all | GitLab version major component |
-| `CI_SERVER_VERSION_MINOR` | 11.4 | all | GitLab version minor component |
-| `CI_SERVER_VERSION_PATCH` | 11.4 | all | GitLab version patch component |
-| `CI_SHARED_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
-| `GET_SOURCES_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
-| `GITLAB_CI` | all | all | Mark that job is executed in GitLab CI environment |
-| `GITLAB_FEATURES` | 10.6 | all | The comma separated list of licensed features available for your instance and plan |
-| `GITLAB_USER_EMAIL` | 8.12 | all | The email of the user who started the job |
-| `GITLAB_USER_ID` | 8.12 | all | The id of the user who started the job |
-| `GITLAB_USER_LOGIN` | 10.0 | all | The login username of the user who started the job |
-| `GITLAB_USER_NAME` | 10.0 | all | The real name of the user who started the job |
-| `RESTORE_CACHE_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
+| Variable | GitLab | Runner | Description |
+|-----------------------------------------------|--------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `ARTIFACT_DOWNLOAD_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
+| `CHAT_CHANNEL` | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command |
+| `CHAT_INPUT` | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command |
+| `CI` | all | 0.4 | Mark that job is executed in CI environment |
+| `CI_API_V4_URL` | 11.7 | all | The GitLab API v4 root URL |
+| `CI_BUILDS_DIR` | all | 11.10 | Top-level directory where builds are executed. |
+| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a merge request. Only populated when there is a merge request associated with the pipeline. |
+| `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
+| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
+| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built |
+| `CI_COMMIT_REF_PROTECTED` | 11.11 | all | If the job is running on a protected branch |
+| `CI_COMMIT_REF_SLUG` | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
+| `CI_COMMIT_SHA` | 9.0 | all | The commit revision for which project is built |
+| `CI_COMMIT_SHORT_SHA` | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` |
+| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit - the full first line of the message |
+| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. |
+| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. |
+| `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
+| `CI_DEBUG_TRACE` | all | 1.7 | Whether [debug logging (tracing)](README.md#debug-logging) is enabled |
+| `CI_DEFAULT_BRANCH` | 12.4 | all | The name of the default branch for the project. |
+| `CI_DEPLOY_PASSWORD` | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related. |
+| `CI_DEPLOY_USER` | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related. |
+| `CI_DISPOSABLE_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
+| `CI_ENVIRONMENT_NAME` | 8.15 | all | The name of the environment for this job. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
+| `CI_ENVIRONMENT_SLUG` | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
+| `CI_ENVIRONMENT_URL` | 9.3 | all | The URL of the environment for this job. Only present if [`environment:url`](../yaml/README.md#environmenturl) is set. |
+| `CI_EXTERNAL_PULL_REQUEST_IID` | 12.3 | all | Pull Request ID from GitHub if the [pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
+| `CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME` | 12.3 | all | The source branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
+| `CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the source branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
+| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME` | 12.3 | all | The target branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
+| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the target branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
+| `CI_JOB_ID` | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
+| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
+| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
+| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] |
+| `CI_JOB_URL` | 11.1 | 0.5 | Job details URL |
+| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of username(s) of assignee(s) for the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_ID` | 11.6 | all | The ID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_IID` | 11.6 | all | The IID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_ID` | 11.6 | all | The ID of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_PATH` | 11.6 | all | The path of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_URL` | 11.6 | all | The URL of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_REF_PATH` | 11.6 | all | The ref path of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | 11.6 | all | The source branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the source branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used, the merge request is created, and the pipeline is a [merged result pipeline](../merge_request_pipelines/pipelines_for_merged_results/index.md). **(PREMIUM)** |
+| `CI_MERGE_REQUEST_SOURCE_PROJECT_ID` | 11.6 | all | The ID of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH` | 11.6 | all | The path of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_SOURCE_PROJECT_URL` | 11.6 | all | The URL of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | 11.6 | all | The target branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_MERGE_REQUEST_TARGET_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the target branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used, the merge request is created, and the pipeline is a [merged result pipeline](../merge_request_pipelines/pipelines_for_merged_results/index.md). **(PREMIUM)** |
+| `CI_MERGE_REQUEST_TITLE` | 11.9 | all | The title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` is used and the merge request is created. |
+| `CI_NODE_INDEX` | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
+| `CI_NODE_TOTAL` | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
+| `CI_PAGES_DOMAIN` | 11.8 | all | The configured domain that hosts GitLab Pages. |
+| `CI_PAGES_URL` | 11.8 | all | URL to GitLab Pages-built pages. Always belongs to a subdomain of `CI_PAGES_DOMAIN`. |
+| `CI_PIPELINE_ID` | 8.10 | all | The unique id of the current pipeline that GitLab CI uses internally |
+| `CI_PIPELINE_IID` | 11.0 | all | The unique id of the current pipeline scoped to project |
+| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
+| `CI_PIPELINE_TRIGGERED` | all | all | The flag to indicate that job was [triggered](../triggers/README.md) |
+| `CI_PIPELINE_URL` | 11.1 | 0.5 | Pipeline details URL |
+| `CI_PROJECT_DIR` | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
+| `CI_PROJECT_ID` | all | all | The unique id of the current project that GitLab CI uses internally |
+| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is currently being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
+| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
+| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
+| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercased list of the languages used in the repository (e.g. `ruby,javascript,html,css`) |
+| `CI_PROJECT_TITLE` | 12.4 | all | The human-readable project name as displayed in the GitLab web interface. |
+| `CI_PROJECT_URL` | 8.10 | 0.5 | The HTTP(S) address to access project |
+| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) |
+| `CI_REGISTRY` | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry. This variable will include a `:port` value if one has been specified in the registry configuration. |
+| `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
+| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
+| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
+| `CI_REPOSITORY_URL` | 9.0 | all | The URL to clone the Git repository |
+| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner as saved in GitLab |
+| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
+| `CI_RUNNER_ID` | 8.10 | 0.5 | The unique id of runner being used |
+| `CI_RUNNER_REVISION` | all | 10.6 | GitLab Runner revision that is executing the current job |
+| `CI_RUNNER_SHORT_TOKEN` | all | 12.3 | First eight characters of GitLab Runner's token used to authenticate new job requests. Used as Runner's unique ID |
+| `CI_RUNNER_TAGS` | 8.10 | 0.5 | The defined runner tags |
+| `CI_RUNNER_VERSION` | all | 10.6 | GitLab Runner version that is executing the current job |
+| `CI_SERVER` | all | all | Mark that job is executed in CI environment |
+| `CI_SERVER_HOST` | 12.1 | all | Host component of the GitLab instance URL, without protocol and port (like `gitlab.example.com`) |
+| `CI_SERVER_NAME` | all | all | The name of CI server that is used to coordinate jobs |
+| `CI_SERVER_REVISION` | all | all | GitLab revision that is used to schedule jobs |
+| `CI_SERVER_VERSION` | all | all | GitLab version that is used to schedule jobs |
+| `CI_SERVER_VERSION_MAJOR` | 11.4 | all | GitLab version major component |
+| `CI_SERVER_VERSION_MINOR` | 11.4 | all | GitLab version minor component |
+| `CI_SERVER_VERSION_PATCH` | 11.4 | all | GitLab version patch component |
+| `CI_SHARED_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
+| `GET_SOURCES_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
+| `GITLAB_CI` | all | all | Mark that job is executed in GitLab CI environment |
+| `GITLAB_FEATURES` | 10.6 | all | The comma separated list of licensed features available for your instance and plan |
+| `GITLAB_USER_EMAIL` | 8.12 | all | The email of the user who started the job |
+| `GITLAB_USER_ID` | 8.12 | all | The id of the user who started the job |
+| `GITLAB_USER_LOGIN` | 10.0 | all | The login username of the user who started the job |
+| `GITLAB_USER_NAME` | 10.0 | all | The real name of the user who started the job |
+| `RESTORE_CACHE_ATTEMPTS` | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
[gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token
[registry]: ../../user/packages/container_registry/index.md
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 27ced0eecf5..62644e78872 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1539,9 +1539,9 @@ cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/18986) in GitLab v12.5.
-If `cache:key:files` is added, the cache `key` will use the SHA of the most recent commit
-that changed either of the given files. If neither file was changed in any commits, the key will be `default`.
-A maximum of two files are allowed.
+If `cache:key:files` is added, one or two files must be defined with it. The cache `key`
+will be a SHA computed from the most recent commits (one or two) that changed the
+given files. If neither file was changed in any commits, the key will be `default`.
```yaml
cache:
@@ -1559,8 +1559,8 @@ cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/18986) in GitLab v12.5.
The `prefix` parameter adds extra functionality to `key:files` by allowing the key to
-be composed of the given `prefix` combined with the SHA of the most recent commit
-that changed either of the files. For example, adding a `prefix` of `rspec`, will
+be composed of the given `prefix` combined with the SHA computed for `cache:key:files`.
+For example, adding a `prefix` of `rspec`, will
cause keys to look like: `rspec-feef9576d21ee9b6a32e30c5c79d0a0ceb68d1e5`. If neither
file was changed in any commits, the prefix is added to `default`, so the key in the
example would be `rspec-default`.
diff --git a/doc/development/policies.md b/doc/development/policies.md
index 833b0acb13e..8e5ef6e57c0 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -157,3 +157,18 @@ end
```
will include all rules from `ProjectPolicy`. The delegated conditions will be evaluated with the correct delegated subject, and will be sorted along with the regular rules in the policy. Note that only the relevant rules for a particular ability will actually be considered.
+
+## Specifying Policy Class
+
+You can also override the Policy used for a given subject:
+
+```ruby
+class Foo
+
+ def self.declarative_policy_class
+ 'SomeOtherPolicy'
+ end
+end
+```
+
+This will use & check permissions on the `SomeOtherPolicy` class rather than the usual calculated `FooPolicy` class.
diff --git a/doc/update/README.md b/doc/update/README.md
index 965f29bc8aa..6834deb1a85 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -69,7 +69,13 @@ before continuing the upgrading procedure. While this won't require downtime
between upgrading major/minor releases, allowing the background migrations to
finish. The time necessary to complete these migrations can be reduced by
increasing the number of Sidekiq workers that can process jobs in the
-`background_migration` queue.
+`background_migration` queue. To check the size of this queue,
+[start a Rails console session](https://docs.gitlab.com/omnibus/maintenance/#starting-a-rails-console-session)
+and run the command below:
+
+```ruby
+Sidekiq::Queue.new('background_migration').size
+```
As a rule of thumb, any database smaller than 10 GB won't take too much time to
upgrade; perhaps an hour at most per minor release. Larger databases however may
diff --git a/doc/user/incident_management/index.md b/doc/user/incident_management/index.md
index 36c3d29f911..5ac27d227a1 100644
--- a/doc/user/incident_management/index.md
+++ b/doc/user/incident_management/index.md
@@ -75,7 +75,7 @@ Learn how to embed [GitLab hosted metric charts](../project/integrations/prometh
### Grafana metrics
-Learn how to embed [Grafana hosted metric charts](../project/integrations/prometheus.md#embedding-live-grafana-charts).
+Learn how to embed [Grafana hosted metric charts](../project/integrations/prometheus.md#embedding-grafana-charts).
## Slack integration
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 42c38a8e34b..70660e5e22f 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -121,6 +121,7 @@ The following table depicts the various user permission levels in a project.
| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
| Remove GitLab Pages | | | | ✓ | ✓ |
| Manage clusters | | | | ✓ | ✓ |
+| View Pods logs **(ULTIMATE)** | | | | ✓ | ✓ |
| Manage license policy **(ULTIMATE)** | | | | ✓ | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ |
| Manage Error Tracking | | | | ✓ | ✓ |
diff --git a/doc/user/project/clusters/img/kubernetes_pod_logs_v12_4.png b/doc/user/project/clusters/img/kubernetes_pod_logs_v12_4.png
deleted file mode 100644
index 73c2ecd182a..00000000000
--- a/doc/user/project/clusters/img/kubernetes_pod_logs_v12_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/clusters/img/kubernetes_pod_logs_v12_5.png b/doc/user/project/clusters/img/kubernetes_pod_logs_v12_5.png
new file mode 100644
index 00000000000..e54637e7218
--- /dev/null
+++ b/doc/user/project/clusters/img/kubernetes_pod_logs_v12_5.png
Binary files differ
diff --git a/doc/user/project/clusters/img/sidebar_menu_pod_logs_v12_5.png b/doc/user/project/clusters/img/sidebar_menu_pod_logs_v12_5.png
new file mode 100644
index 00000000000..f113b0353f2
--- /dev/null
+++ b/doc/user/project/clusters/img/sidebar_menu_pod_logs_v12_5.png
Binary files differ
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index 4036eaf0bfb..797ddf784cc 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -11,7 +11,31 @@ Everything you need to build, test, deploy, and run your app at scale.
## Overview
-[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md):
+[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab.
+
+![Pod logs](img/kubernetes_pod_logs_v12_5.png)
+
+## Requirements
+
+[Deploying to a Kubernetes environment](../deploy_boards.md#enabling-deploy-boards) is required in order to be able to use Pod Logs.
+
+## Usage
+
+To access pod logs, you must have the right [permissions](../../permissions.md#project-members-permissions).
+
+You can access them in two ways.
+
+### From the project sidebar
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/22011) in GitLab 12.5.
+
+Go to **Operations > Pod logs** on the sidebar menu.
+
+![Sidebar menu](img/sidebar_menu_pod_logs_v12_5.png)
+
+### From Deploy Boards
+
+Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md):
1. Go to **Operations > Environments** and find the environment which contains the desired pod, like `production`.
1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](../deploy_boards.md).
@@ -23,9 +47,3 @@ Everything you need to build, test, deploy, and run your app at scale.
- [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/issues/5769), environments.
Support for pods with multiple containers is coming [in a future release](https://gitlab.com/gitlab-org/gitlab/issues/6502).
-
- ![Deploy Boards pod list](img/kubernetes_pod_logs_v12_4.png)
-
-## Requirements
-
-[Enabling Deploy Boards](../deploy_boards.md#enabling-deploy-boards) is required in order to be able to use Pod Logs.
diff --git a/doc/user/project/integrations/img/grafana_panel_v12_5.png b/doc/user/project/integrations/img/grafana_panel_v12_5.png
new file mode 100644
index 00000000000..18c17b910cd
--- /dev/null
+++ b/doc/user/project/integrations/img/grafana_panel_v12_5.png
Binary files differ
diff --git a/doc/user/project/integrations/img/grafana_sharing_dialog_v12_5.png b/doc/user/project/integrations/img/grafana_sharing_dialog_v12_5.png
new file mode 100644
index 00000000000..fae62dd50df
--- /dev/null
+++ b/doc/user/project/integrations/img/grafana_sharing_dialog_v12_5.png
Binary files differ
diff --git a/doc/user/project/integrations/img/http_proxy_access_v12_5.png b/doc/user/project/integrations/img/http_proxy_access_v12_5.png
new file mode 100644
index 00000000000..0036a916a12
--- /dev/null
+++ b/doc/user/project/integrations/img/http_proxy_access_v12_5.png
Binary files differ
diff --git a/doc/user/project/integrations/img/rendered_grafana_embed_v12_5.png b/doc/user/project/integrations/img/rendered_grafana_embed_v12_5.png
new file mode 100644
index 00000000000..6cabe4193bd
--- /dev/null
+++ b/doc/user/project/integrations/img/rendered_grafana_embed_v12_5.png
Binary files differ
diff --git a/doc/user/project/integrations/img/select_query_variables_v12_5.png b/doc/user/project/integrations/img/select_query_variables_v12_5.png
new file mode 100644
index 00000000000..23503577327
--- /dev/null
+++ b/doc/user/project/integrations/img/select_query_variables_v12_5.png
Binary files differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 05cf79a8a62..d3d4afefb59 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -357,6 +357,13 @@ Note the following properties:
![heatmap panel type](img/heatmap_panel_type.png)
+### View and edit the source file of a custom dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34779) in GitLab 12.5.
+
+When viewing a custom dashboard of a project, you can view the original
+`.yml` file by clicking on **Edit dashboard** button.
+
### Downloading data as CSV
Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
@@ -465,6 +472,8 @@ Prometheus server.
## Embedding metric charts within GitLab Flavored Markdown
+### Embedding GitLab-managed Kubernetes metrics
+
> [Introduced][ce-29691] in GitLab 12.2.
It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
@@ -492,13 +501,17 @@ The following requirements must be met for the metric to unfurl:
### Embedding metrics in issue templates
-It is also possible to embed either a dashboard or individual metrics in issue templates. The entire dashboard can be embedded as well as individual metrics, separated by either a comma or a space.
+It is also possible to embed either the default dashboard metrics or individual metrics in issue templates. For charts to render side-by-side, links to the entire metrics dashboard or individual metrics should be separated by either a comma or a space.
![Embedded Metrics in issue templates](img/embed_metrics_issue_template.png)
-### Embedding live Grafana charts
+### Embedding Grafana charts
-It is also possible to embed live [Grafana](https://docs.gitlab.com/omnibus/settings/grafana.html) charts within issues, as a [Direct Linked Rendered Image](https://grafana.com/docs/reference/sharing/#direct-link-rendered-image).
+Grafana metrics can be embedded in [GitLab Flavored Markdown](../../markdown.md).
+
+#### Embedding charts via Grafana Rendered Images
+
+It is possible to embed live [Grafana](https://docs.gitlab.com/omnibus/settings/grafana.html) charts in issues, as a [direct linked rendered image](https://grafana.com/docs/reference/sharing/#direct-link-rendered-image).
The sharing dialog within Grafana provides the link, as highlighted below.
@@ -517,6 +530,41 @@ This will render like so:
<img src="https://dashboards.gitlab.com/render/d-solo/RZmbBr7mk/gitlab-triage?orgId=1&refresh=30s&var-env=gprd&var-environment=gprd&var-prometheus=prometheus-01-inf-gprd&var-prometheus_app=prometheus-app-01-inf-gprd&var-backend=All&var-type=All&var-stage=main&panelId=1247&width=1000&height=300"/>
+#### Embedding charts via integration with Grafana HTTP API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/31376) in GitLab 12.5.
+
+Each project can support integration with one Grafana instance. This configuration allows a user to copy a link to a panel in Grafana, then paste it into a GitLab markdown field. The chart will be rendered in the GitLab chart format.
+
+Prerequisites for embedding from a Grafana instance:
+
+1. The datasource must be a Prometheus instance.
+1. The datasource must be proxyable, so the HTTP Access setting should be set to `Server`.
+
+![HTTP Proxy Access](img/http_proxy_access_v12_5.png)
+
+##### Setting up the Grafana integration
+
+1. [Generate an Admin-level API Token in Grafana.](https://grafana.com/docs/http_api/auth/#create-api-token)
+1. In your GitLab project, navigate to **Settings > Operations > Grafana Authentication**.
+1. To enable the integration, check the "Active" checkbox.
+1. For "Grafana URL", enter the base URL of the Grafana instance.
+1. For "API Token", enter the Admin API Token you just generated.
+1. Click **Save Changes**.
+
+##### Generating a link to a chart
+
+1. In Grafana, navigate to the dashboard you wish to embed a panel from.
+ ![Grafana Metric Panel](img/grafana_panel_v12_5.png)
+1. In the upper-left corner of the page, select a specific value for each variable required for the queries in the chart.
+ ![Select Query Variables](img/select_query_variables_v12_5.png)
+1. In Grafana, click on a panel's title, then click **Share** to open the panel's sharing dialog to the **Link** tab.
+1. If your Prometheus queries use Grafana's custom template variables, ensure that "Template variables" and "Current time range" options are toggled to **On**. Of Grafana global template variables, only `$__interval`, `$__from`, and `$__to` are currently supported.
+ ![Grafana Sharing Dialog](img/grafana_sharing_dialog_v12_5.png)
+1. Click **Copy** to copy the URL to the clipboard.
+1. In GitLab, paste the URL into a markdown field and save. The chart will take a few moments to render.
+ ![GitLab Rendered Grafana Panel](img/rendered_grafana_embed_v12_5.png)
+
## Troubleshooting
If the "No data found" screen continues to appear, it could be due to:
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index daa9598a204..693c20cb73a 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -36,7 +36,8 @@ module API
{
processed: stats.processed,
failed: stats.failed,
- enqueued: stats.enqueued
+ enqueued: stats.enqueued,
+ dead: stats.dead_size
}
end
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index d99a209dc87..9e9df88373a 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -74,7 +74,14 @@ module DeclarativePolicy
next unless klass.name
begin
- policy_class = "#{klass.name}Policy".constantize
+ klass_name =
+ if subject_class.respond_to?(:declarative_policy_class)
+ subject_class.declarative_policy_class
+ else
+ "#{klass.name}Policy"
+ end
+
+ policy_class = klass_name.constantize
# NOTE: the < operator here tests whether policy_class
# inherits from Base. We can't use #is_a? because that
diff --git a/lib/gitlab/import_export/group_tree_saver.rb b/lib/gitlab/import_export/group_tree_saver.rb
index 1d42bc8d3f3..8d2fb881cc0 100644
--- a/lib/gitlab/import_export/group_tree_saver.rb
+++ b/lib/gitlab/import_export/group_tree_saver.rb
@@ -28,9 +28,9 @@ module Gitlab
def serialize(group, relations_tree)
group_tree = tree_saver.serialize(group, relations_tree)
- group.descendants.each do |descendant|
- group_tree['descendants'] = [] unless group_tree['descendants']
- group_tree['descendants'] << serialize(descendant, relations_tree)
+ group.children.each do |child|
+ group_tree['children'] ||= []
+ group_tree['children'] << serialize(child, relations_tree)
end
group_tree
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 81709437eef..82bd6053144 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4682,6 +4682,9 @@ msgstr ""
msgid "Copy"
msgstr ""
+msgid "Copy %{field}"
+msgstr ""
+
msgid "Copy %{http_label} clone URL"
msgstr ""
@@ -8634,6 +8637,9 @@ msgstr ""
msgid "GroupSAML|Configuration"
msgstr ""
+msgid "GroupSAML|Copy SAML Response XML"
+msgstr ""
+
msgid "GroupSAML|Enable SAML authentication for this group."
msgstr ""
@@ -8667,6 +8673,18 @@ msgstr ""
msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
msgstr ""
+msgid "GroupSAML|NameID"
+msgstr ""
+
+msgid "GroupSAML|NameID Format"
+msgstr ""
+
+msgid "GroupSAML|SAML Response Output"
+msgstr ""
+
+msgid "GroupSAML|SAML Response XML"
+msgstr ""
+
msgid "GroupSAML|SAML Single Sign On"
msgstr ""
@@ -8694,12 +8712,24 @@ msgstr ""
msgid "GroupSAML|Toggle SAML authentication"
msgstr ""
+msgid "GroupSAML|Valid SAML Response"
+msgstr ""
+
msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group."
msgstr ""
msgid "GroupSAML|Your SCIM token"
msgstr ""
+msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
+msgstr ""
+
+msgid "GroupSAML|should be \"persistent\""
+msgstr ""
+
+msgid "GroupSAML|should be a random persistent ID, emails are discouraged"
+msgstr ""
+
msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
msgstr ""
@@ -16922,9 +16952,6 @@ msgstr ""
msgid "Terms of Service and Privacy Policy"
msgstr ""
-msgid "Test SAML SSO"
-msgstr ""
-
msgid "Test coverage parsing"
msgstr ""
@@ -19136,6 +19163,9 @@ msgstr ""
msgid "Verified"
msgstr ""
+msgid "Verify SAML Configuration"
+msgstr ""
+
msgid "Version"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 90a2c6e5b8b..5266fc57b0a 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -8,7 +8,7 @@ gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
-gem 'nokogiri', '~> 1.10.4'
+gem 'nokogiri', '~> 1.10.5'
gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_junit_formatter', '~> 0.4.1'
gem 'faker', '~> 1.6', '>= 1.6.6'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 6728c0cceee..84eab990c95 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -55,7 +55,7 @@ GEM
mini_portile2 (2.4.0)
minitest (5.11.3)
netrc (0.11.0)
- nokogiri (1.10.4)
+ nokogiri (1.10.5)
mini_portile2 (~> 2.4.0)
parallel (1.17.0)
parallel_tests (2.29.0)
@@ -119,7 +119,7 @@ DEPENDENCIES
faker (~> 1.6, >= 1.6.6)
gitlab-qa
knapsack (~> 1.17)
- nokogiri (~> 1.10.4)
+ nokogiri (~> 1.10.5)
parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 04bbffc587f..4a10e7b5325 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -90,16 +90,18 @@ describe ApplicationController do
let(:format) { :html }
it_behaves_like 'setting gon variables'
- end
- context 'with json format' do
- let(:format) { :json }
+ context 'for peek requests' do
+ before do
+ request.path = '/-/peek'
+ end
- it_behaves_like 'not setting gon variables'
+ it_behaves_like 'not setting gon variables'
+ end
end
- context 'with atom format' do
- let(:format) { :atom }
+ context 'with json format' do
+ let(:format) { :json }
it_behaves_like 'not setting gon variables'
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index f35babc1b56..1bcf3bb106b 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -228,10 +228,10 @@ describe UploadsController do
user.block
end
- it "responds with status 401" do
+ it "redirects to the sign in page" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to redirect_to(new_user_session_path)
end
end
@@ -320,10 +320,10 @@ describe UploadsController do
end
context "when not signed in" do
- it "responds with status 401" do
+ it "redirects to the sign in page" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to redirect_to(new_user_session_path)
end
end
@@ -343,10 +343,10 @@ describe UploadsController do
project.add_maintainer(user)
end
- it "responds with status 401" do
+ it "redirects to the sign in page" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to redirect_to(new_user_session_path)
end
end
@@ -439,10 +439,10 @@ describe UploadsController do
user.block
end
- it "responds with status 401" do
+ it "redirects to the sign in page" do
get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to redirect_to(new_user_session_path)
end
end
@@ -526,10 +526,10 @@ describe UploadsController do
end
context "when not signed in" do
- it "responds with status 401" do
+ it "redirects to the sign in page" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to redirect_to(new_user_session_path)
end
end
@@ -549,10 +549,10 @@ describe UploadsController do
project.add_maintainer(user)
end
- it "responds with status 401" do
+ it "redirects to the sign in page" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to redirect_to(new_user_session_path)
end
end
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index 165d41950da..ba167362511 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -33,7 +33,6 @@ describe "User toggles subscription", :js do
it 'is disabled' do
expect(page).to have_content('Notifications have been disabled by the project or group owner')
- expect(page).to have_selector('.js-emails-disabled', visible: true)
expect(page).not_to have_selector('.js-issuable-subscribe-button')
end
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json
index 682e345d5f5..11076ec73de 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json
@@ -3,6 +3,8 @@
"properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
+ "project_emails_disabled": { "type": "boolean" },
+ "subscribe_disabled_description": { "type": "string" },
"subscribed": { "type": "boolean" },
"time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" },
diff --git a/spec/frontend/issuables_list/components/issuable_spec.js b/spec/frontend/issuables_list/components/issuable_spec.js
index 915e908dd81..6148f3c68f2 100644
--- a/spec/frontend/issuables_list/components/issuable_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_spec.js
@@ -196,6 +196,13 @@ describe('Issuable component', () => {
`${formatDate(dueDate, DATE_FORMAT)} (${expectedTooltipPart})`,
);
});
+
+ it('renders milestone with the correct href', () => {
+ const { title } = issuable.milestone;
+ const expected = mergeUrlParams({ milestone_title: title }, TEST_BASE_URL);
+
+ expect(findMilestone().attributes('href')).toBe(expected);
+ });
});
describe.each`
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index 74e2d079d9b..c42366ab484 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -328,3 +328,138 @@ export const metricsGroupsAPIResponse = [
],
},
];
+
+export const environmentData = [
+ {
+ id: 34,
+ name: 'production',
+ state: 'available',
+ external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
+ environment_type: null,
+ stop_action: false,
+ metrics_path: '/root/hello-prometheus/environments/34/metrics',
+ environment_path: '/root/hello-prometheus/environments/34',
+ stop_path: '/root/hello-prometheus/environments/34/stop',
+ terminal_path: '/root/hello-prometheus/environments/34/terminal',
+ folder_path: '/root/hello-prometheus/environments/folders/production',
+ created_at: '2018-06-29T16:53:38.301Z',
+ updated_at: '2018-06-29T16:57:09.825Z',
+ last_deployment: {
+ id: 127,
+ },
+ },
+ {
+ id: 35,
+ name: 'review/noop-branch',
+ state: 'available',
+ external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
+ environment_type: 'review',
+ stop_action: true,
+ metrics_path: '/root/hello-prometheus/environments/35/metrics',
+ environment_path: '/root/hello-prometheus/environments/35',
+ stop_path: '/root/hello-prometheus/environments/35/stop',
+ terminal_path: '/root/hello-prometheus/environments/35/terminal',
+ folder_path: '/root/hello-prometheus/environments/folders/review',
+ created_at: '2018-07-03T18:39:41.702Z',
+ updated_at: '2018-07-03T18:44:54.010Z',
+ last_deployment: {
+ id: 128,
+ },
+ },
+ {
+ id: 36,
+ name: 'no-deployment/noop-branch',
+ state: 'available',
+ created_at: '2018-07-04T18:39:41.702Z',
+ updated_at: '2018-07-04T18:44:54.010Z',
+ },
+];
+
+export const metricsDashboardResponse = {
+ dashboard: {
+ dashboard: 'Environment metrics',
+ priority: 1,
+ panel_groups: [
+ {
+ group: 'System metrics (Kubernetes)',
+ priority: 5,
+ panels: [
+ {
+ title: 'Memory Usage (Total)',
+ type: 'area-chart',
+ y_label: 'Total Memory Used',
+ weight: 4,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_memory_total',
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ label: 'Total',
+ unit: 'GB',
+ metric_id: 12,
+ prometheus_endpoint_path: 'http://test',
+ },
+ ],
+ },
+ {
+ title: 'Core Usage (Total)',
+ type: 'area-chart',
+ y_label: 'Total Cores',
+ weight: 3,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_cores_total',
+ query_range:
+ 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
+ label: 'Total',
+ unit: 'cores',
+ metric_id: 13,
+ },
+ ],
+ },
+ {
+ title: 'Memory Usage (Pod average)',
+ type: 'line-chart',
+ y_label: 'Memory Used per Pod',
+ weight: 2,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_memory_average',
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
+ label: 'Pod average',
+ unit: 'MB',
+ metric_id: 14,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ status: 'success',
+};
+
+export const dashboardGitResponse = [
+ {
+ default: true,
+ display_name: 'Default',
+ can_edit: false,
+ project_blob_path: null,
+ path: 'config/prometheus/common_metrics.yml',
+ },
+ {
+ default: false,
+ display_name: 'Custom Dashboard 1',
+ can_edit: true,
+ project_blob_path: `${mockProjectDir}/blob/master/dashboards/.gitlab/dashboards/dashboard_1.yml`,
+ path: '.gitlab/dashboards/dashboard_1.yml',
+ },
+ {
+ default: false,
+ display_name: 'Custom Dashboard 2',
+ can_edit: true,
+ project_blob_path: `${mockProjectDir}/blob/master/dashboards/.gitlab/dashboards/dashboard_2.yml`,
+ path: '.gitlab/dashboards/dashboard_2.yml',
+ },
+];
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 513a0e0d103..d4bc613ffea 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -1,12 +1,44 @@
-import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
-import { backOffRequest } from '~/monitoring/stores/actions';
+import testAction from 'helpers/vuex_action_helper';
+import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import { backOff } from '~/lib/utils/common_utils';
+import store from '~/monitoring/stores';
+import * as types from '~/monitoring/stores/mutation_types';
+import {
+ backOffRequest,
+ fetchDashboard,
+ receiveMetricsDashboardSuccess,
+ receiveMetricsDashboardFailure,
+ fetchDeploymentsData,
+ fetchEnvironmentsData,
+ fetchPrometheusMetrics,
+ fetchPrometheusMetric,
+ requestMetricsData,
+ setEndpoints,
+ setGettingStartedEmptyState,
+} from '~/monitoring/stores/actions';
+import storeState from '~/monitoring/stores/state';
+import {
+ deploymentData,
+ environmentData,
+ metricsDashboardResponse,
+ metricsGroupsAPIResponse,
+ dashboardGitResponse,
+} from '../mock_data';
+
jest.mock('~/lib/utils/common_utils');
+const resetStore = str => {
+ str.replaceState({
+ showEmptyState: true,
+ emptyState: 'loading',
+ groups: [],
+ });
+};
+
const MAX_REQUESTS = 3;
describe('Monitoring store helpers', () => {
@@ -51,3 +83,334 @@ describe('Monitoring store helpers', () => {
});
});
});
+
+describe('Monitoring store actions', () => {
+ let mock;
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+ afterEach(() => {
+ resetStore(store);
+ mock.restore();
+ });
+ describe('requestMetricsData', () => {
+ it('sets emptyState to loading', () => {
+ const commit = jest.fn();
+ const { state } = store;
+ requestMetricsData({
+ state,
+ commit,
+ });
+ expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA);
+ });
+ });
+ describe('fetchDeploymentsData', () => {
+ it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
+ const dispatch = jest.fn();
+ const { state } = store;
+ state.deploymentsEndpoint = '/success';
+ mock.onGet(state.deploymentsEndpoint).reply(200, {
+ deployments: deploymentData,
+ });
+ fetchDeploymentsData({
+ state,
+ dispatch,
+ })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataSuccess', deploymentData);
+ done();
+ })
+ .catch(done.fail);
+ });
+ it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => {
+ const dispatch = jest.fn();
+ const { state } = store;
+ state.deploymentsEndpoint = '/error';
+ mock.onGet(state.deploymentsEndpoint).reply(500);
+ fetchDeploymentsData({
+ state,
+ dispatch,
+ })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataFailure');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ describe('fetchEnvironmentsData', () => {
+ it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => {
+ const dispatch = jest.fn();
+ const { state } = store;
+ state.environmentsEndpoint = '/success';
+ mock.onGet(state.environmentsEndpoint).reply(200, {
+ environments: environmentData,
+ });
+ fetchEnvironmentsData({
+ state,
+ dispatch,
+ })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData);
+ done();
+ })
+ .catch(done.fail);
+ });
+ it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => {
+ const dispatch = jest.fn();
+ const { state } = store;
+ state.environmentsEndpoint = '/error';
+ mock.onGet(state.environmentsEndpoint).reply(500);
+ fetchEnvironmentsData({
+ state,
+ dispatch,
+ })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ describe('Set endpoints', () => {
+ let mockedState;
+ beforeEach(() => {
+ mockedState = storeState();
+ });
+ it('should commit SET_ENDPOINTS mutation', done => {
+ testAction(
+ setEndpoints,
+ {
+ metricsEndpoint: 'additional_metrics.json',
+ deploymentsEndpoint: 'deployments.json',
+ environmentsEndpoint: 'deployments.json',
+ },
+ mockedState,
+ [
+ {
+ type: types.SET_ENDPOINTS,
+ payload: {
+ metricsEndpoint: 'additional_metrics.json',
+ deploymentsEndpoint: 'deployments.json',
+ environmentsEndpoint: 'deployments.json',
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+ describe('Set empty states', () => {
+ let mockedState;
+ beforeEach(() => {
+ mockedState = storeState();
+ });
+ it('should commit SET_METRICS_ENDPOINT mutation', done => {
+ testAction(
+ setGettingStartedEmptyState,
+ null,
+ mockedState,
+ [
+ {
+ type: types.SET_GETTING_STARTED_EMPTY_STATE,
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+ describe('fetchDashboard', () => {
+ let dispatch;
+ let state;
+ const response = metricsDashboardResponse;
+ beforeEach(() => {
+ dispatch = jest.fn();
+ state = storeState();
+ state.dashboardEndpoint = '/dashboard';
+ });
+ it('dispatches receive and success actions', done => {
+ const params = {};
+ mock.onGet(state.dashboardEndpoint).reply(200, response);
+ fetchDashboard(
+ {
+ state,
+ dispatch,
+ },
+ params,
+ )
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('requestMetricsDashboard');
+ expect(dispatch).toHaveBeenCalledWith('receiveMetricsDashboardSuccess', {
+ response,
+ params,
+ });
+ done();
+ })
+ .catch(done.fail);
+ });
+ it('dispatches failure action', done => {
+ const params = {};
+ mock.onGet(state.dashboardEndpoint).reply(500);
+ fetchDashboard(
+ {
+ state,
+ dispatch,
+ },
+ params,
+ )
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith(
+ 'receiveMetricsDashboardFailure',
+ new Error('Request failed with status code 500'),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ describe('receiveMetricsDashboardSuccess', () => {
+ let commit;
+ let dispatch;
+ let state;
+ beforeEach(() => {
+ commit = jest.fn();
+ dispatch = jest.fn();
+ state = storeState();
+ });
+ it('stores groups ', () => {
+ const params = {};
+ const response = metricsDashboardResponse;
+ receiveMetricsDashboardSuccess(
+ {
+ state,
+ commit,
+ dispatch,
+ },
+ {
+ response,
+ params,
+ },
+ );
+ expect(commit).toHaveBeenCalledWith(
+ types.RECEIVE_METRICS_DATA_SUCCESS,
+ metricsDashboardResponse.dashboard.panel_groups,
+ );
+ expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params);
+ });
+ it('sets the dashboards loaded from the repository', () => {
+ const params = {};
+ const response = metricsDashboardResponse;
+ response.all_dashboards = dashboardGitResponse;
+ receiveMetricsDashboardSuccess(
+ {
+ state,
+ commit,
+ dispatch,
+ },
+ {
+ response,
+ params,
+ },
+ );
+ expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
+ });
+ });
+ describe('receiveMetricsDashboardFailure', () => {
+ let commit;
+ beforeEach(() => {
+ commit = jest.fn();
+ });
+ it('commits failure action', () => {
+ receiveMetricsDashboardFailure({
+ commit,
+ });
+ expect(commit).toHaveBeenCalledWith(types.RECEIVE_METRICS_DATA_FAILURE, undefined);
+ });
+ it('commits failure action with error', () => {
+ receiveMetricsDashboardFailure(
+ {
+ commit,
+ },
+ 'uh-oh',
+ );
+ expect(commit).toHaveBeenCalledWith(types.RECEIVE_METRICS_DATA_FAILURE, 'uh-oh');
+ });
+ });
+ describe('fetchPrometheusMetrics', () => {
+ let commit;
+ let dispatch;
+ beforeEach(() => {
+ commit = jest.fn();
+ dispatch = jest.fn();
+ });
+ it('commits empty state when state.groups is empty', done => {
+ const state = storeState();
+ const params = {};
+ fetchPrometheusMetrics(
+ {
+ state,
+ commit,
+ dispatch,
+ },
+ params,
+ )
+ .then(() => {
+ expect(commit).toHaveBeenCalledWith(types.SET_NO_DATA_EMPTY_STATE);
+ expect(dispatch).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+ it('dispatches fetchPrometheusMetric for each panel query', done => {
+ const params = {};
+ const state = storeState();
+ state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups;
+ const metric = state.dashboard.panel_groups[0].panels[0].metrics[0];
+ fetchPrometheusMetrics(
+ {
+ state,
+ commit,
+ dispatch,
+ },
+ params,
+ )
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledTimes(3);
+ expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
+ metric,
+ params,
+ });
+ done();
+ })
+ .catch(done.fail);
+ done();
+ });
+ });
+ describe('fetchPrometheusMetric', () => {
+ it('commits prometheus query result', done => {
+ const commit = jest.fn();
+ const params = {
+ start: '2019-08-06T12:40:02.184Z',
+ end: '2019-08-06T20:40:02.184Z',
+ };
+ const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
+ const state = storeState();
+ const data = metricsGroupsAPIResponse[0].panels[0].metrics[0];
+ const response = {
+ data,
+ };
+ mock.onGet('http://test').reply(200, response);
+ fetchPrometheusMetric({ state, commit }, { metric, params })
+ .then(() => {
+ expect(commit).toHaveBeenCalledWith(types.SET_QUERY_RESULT, {
+ metricId: metric.metric_id,
+ result: data.result,
+ });
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 91948b83eec..fdad290a8d6 100644
--- a/spec/javascripts/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -11,81 +11,62 @@ import { uniqMetricsId } from '~/monitoring/stores/utils';
describe('Monitoring mutations', () => {
let stateCopy;
-
beforeEach(() => {
stateCopy = state();
});
-
- describe(types.RECEIVE_METRICS_DATA_SUCCESS, () => {
+ describe('RECEIVE_METRICS_DATA_SUCCESS', () => {
let groups;
-
beforeEach(() => {
stateCopy.dashboard.panel_groups = [];
groups = metricsGroupsAPIResponse;
});
-
it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
-
expect(stateCopy.dashboard.panel_groups[0].key).toBe('system-metrics-kubernetes--0');
});
-
it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
-
const expectedLabel = 'Pod average';
const { label, query_range } = stateCopy.dashboard.panel_groups[0].metrics[0].metrics[0];
-
expect(label).toEqual(expectedLabel);
expect(query_range.length).toBeGreaterThan(0);
});
-
it('contains one group, which it has two panels and one metrics property', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
-
expect(stateCopy.dashboard.panel_groups).toBeDefined();
expect(stateCopy.dashboard.panel_groups.length).toEqual(1);
expect(stateCopy.dashboard.panel_groups[0].panels.length).toEqual(2);
expect(stateCopy.dashboard.panel_groups[0].panels[0].metrics.length).toEqual(1);
expect(stateCopy.dashboard.panel_groups[0].panels[1].metrics.length).toEqual(1);
});
-
it('assigns queries a metric id', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
-
expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].metricId).toEqual(
'17_system_metrics_kubernetes_container_memory_average',
);
});
-
describe('dashboard endpoint', () => {
const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups;
-
it('aliases group panels to metrics for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
-
expect(stateCopy.dashboard.panel_groups[0].metrics[0]).toBeDefined();
});
-
it('aliases panel metrics to queries for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
-
expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries).toBeDefined();
});
});
});
- describe(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, () => {
+ describe('RECEIVE_DEPLOYMENTS_DATA_SUCCESS', () => {
it('stores the deployment data', () => {
stateCopy.deploymentData = [];
mutations[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](stateCopy, deploymentData);
-
expect(stateCopy.deploymentData).toBeDefined();
expect(stateCopy.deploymentData.length).toEqual(3);
expect(typeof stateCopy.deploymentData[0]).toEqual('object');
});
});
-
describe('SET_ENDPOINTS', () => {
it('should set all the endpoints', () => {
mutations[types.SET_ENDPOINTS](stateCopy, {
@@ -95,7 +76,6 @@ describe('Monitoring mutations', () => {
dashboardEndpoint: 'dashboard.json',
projectPath: '/gitlab-org/gitlab-foss',
});
-
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
@@ -103,46 +83,44 @@ describe('Monitoring mutations', () => {
expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-foss');
});
});
-
describe('SET_QUERY_RESULT', () => {
const metricId = 12;
const id = 'system_metrics_kubernetes_container_memory_total';
- const result = [{ values: [[0, 1], [1, 1], [1, 3]] }];
-
+ const result = [
+ {
+ values: [[0, 1], [1, 1], [1, 3]],
+ },
+ ];
beforeEach(() => {
const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups;
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
});
-
it('clears empty state', () => {
mutations[types.SET_QUERY_RESULT](stateCopy, {
metricId,
result,
});
-
expect(stateCopy.showEmptyState).toBe(false);
});
-
it('sets metricsWithData value', () => {
- const uniqId = uniqMetricsId({ metric_id: metricId, id });
+ const uniqId = uniqMetricsId({
+ metric_id: metricId,
+ id,
+ });
mutations[types.SET_QUERY_RESULT](stateCopy, {
metricId: uniqId,
result,
});
-
expect(stateCopy.metricsWithData).toEqual([uniqId]);
});
-
it('does not store empty results', () => {
mutations[types.SET_QUERY_RESULT](stateCopy, {
metricId,
result: [],
});
-
expect(stateCopy.metricsWithData).toEqual([]);
});
});
-
describe('SET_ALL_DASHBOARDS', () => {
it('stores `undefined` dashboards as an empty array', () => {
mutations[types.SET_ALL_DASHBOARDS](stateCopy, undefined);
@@ -158,7 +136,6 @@ describe('Monitoring mutations', () => {
it('stores dashboards loaded from the git repository', () => {
mutations[types.SET_ALL_DASHBOARDS](stateCopy, dashboardGitResponse);
-
expect(stateCopy.allDashboards).toEqual(dashboardGitResponse);
});
});
diff --git a/spec/javascripts/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 98388ac19f8..98388ac19f8 100644
--- a/spec/javascripts/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index b1cd27f49be..f9cc839bde6 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -1,20 +1,17 @@
import {
anomalyMockGraphData as importedAnomalyMockGraphData,
- deploymentData as importedDeploymentData,
- metricsNewGroupsAPIResponse as importedMetricsNewGroupsAPIResponse,
metricsGroupsAPIResponse as importedMetricsGroupsAPIResponse,
+ environmentData as importedEnvironmentData,
+ dashboardGitResponse as importedDashboardGitResponse,
} from '../../frontend/monitoring/mock_data';
-// TODO Check if these exports are still needed
export const anomalyMockGraphData = importedAnomalyMockGraphData;
-export const deploymentData = importedDeploymentData;
-export const metricsNewGroupsAPIResponse = importedMetricsNewGroupsAPIResponse;
export const metricsGroupsAPIResponse = importedMetricsGroupsAPIResponse;
+export const environmentData = importedEnvironmentData;
+export const dashboardGitResponse = importedDashboardGitResponse;
export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
-export const mockProjectPath = '/frontend-fixtures/environments-project';
-
export const mockedQueryResultPayload = {
metricId: '17_system_metrics_kubernetes_container_memory_average',
result: [
@@ -101,141 +98,6 @@ export const mockedQueryResultPayloadCoresTotal = {
],
};
-export const environmentData = [
- {
- id: 34,
- name: 'production',
- state: 'available',
- external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
- environment_type: null,
- stop_action: false,
- metrics_path: '/root/hello-prometheus/environments/34/metrics',
- environment_path: '/root/hello-prometheus/environments/34',
- stop_path: '/root/hello-prometheus/environments/34/stop',
- terminal_path: '/root/hello-prometheus/environments/34/terminal',
- folder_path: '/root/hello-prometheus/environments/folders/production',
- created_at: '2018-06-29T16:53:38.301Z',
- updated_at: '2018-06-29T16:57:09.825Z',
- last_deployment: {
- id: 127,
- },
- },
- {
- id: 35,
- name: 'review/noop-branch',
- state: 'available',
- external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
- environment_type: 'review',
- stop_action: true,
- metrics_path: '/root/hello-prometheus/environments/35/metrics',
- environment_path: '/root/hello-prometheus/environments/35',
- stop_path: '/root/hello-prometheus/environments/35/stop',
- terminal_path: '/root/hello-prometheus/environments/35/terminal',
- folder_path: '/root/hello-prometheus/environments/folders/review',
- created_at: '2018-07-03T18:39:41.702Z',
- updated_at: '2018-07-03T18:44:54.010Z',
- last_deployment: {
- id: 128,
- },
- },
- {
- id: 36,
- name: 'no-deployment/noop-branch',
- state: 'available',
- created_at: '2018-07-04T18:39:41.702Z',
- updated_at: '2018-07-04T18:44:54.010Z',
- },
-];
-
-export const metricsDashboardResponse = {
- dashboard: {
- dashboard: 'Environment metrics',
- priority: 1,
- panel_groups: [
- {
- group: 'System metrics (Kubernetes)',
- priority: 5,
- panels: [
- {
- title: 'Memory Usage (Total)',
- type: 'area-chart',
- y_label: 'Total Memory Used',
- weight: 4,
- metrics: [
- {
- id: 'system_metrics_kubernetes_container_memory_total',
- query_range:
- 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
- label: 'Total',
- unit: 'GB',
- metric_id: 12,
- prometheus_endpoint_path: 'http://test',
- },
- ],
- },
- {
- title: 'Core Usage (Total)',
- type: 'area-chart',
- y_label: 'Total Cores',
- weight: 3,
- metrics: [
- {
- id: 'system_metrics_kubernetes_container_cores_total',
- query_range:
- 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
- label: 'Total',
- unit: 'cores',
- metric_id: 13,
- },
- ],
- },
- {
- title: 'Memory Usage (Pod average)',
- type: 'line-chart',
- y_label: 'Memory Used per Pod',
- weight: 2,
- metrics: [
- {
- id: 'system_metrics_kubernetes_container_memory_average',
- query_range:
- 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
- label: 'Pod average',
- unit: 'MB',
- metric_id: 14,
- },
- ],
- },
- ],
- },
- ],
- },
- status: 'success',
-};
-
-export const dashboardGitResponse = [
- {
- default: true,
- display_name: 'Default',
- can_edit: false,
- project_blob_path: null,
- path: 'config/prometheus/common_metrics.yml',
- },
- {
- default: false,
- display_name: 'Custom Dashboard 1',
- can_edit: true,
- project_blob_path: `${mockProjectPath}/blob/master/dashboards/.gitlab/dashboards/dashboard_1.yml`,
- path: '.gitlab/dashboards/dashboard_1.yml',
- },
- {
- default: false,
- display_name: 'Custom Dashboard 2',
- can_edit: true,
- project_blob_path: `${mockProjectPath}/blob/master/dashboards/.gitlab/dashboards/dashboard_2.yml`,
- path: '.gitlab/dashboards/dashboard_2.yml',
- },
-];
-
export const graphDataPrometheusQuery = {
title: 'Super Chart A2',
type: 'single-stat',
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
deleted file mode 100644
index 684e26641c7..00000000000
--- a/spec/javascripts/monitoring/store/actions_spec.js
+++ /dev/null
@@ -1,335 +0,0 @@
-import axios from '~/lib/utils/axios_utils';
-import MockAdapter from 'axios-mock-adapter';
-import store from '~/monitoring/stores';
-import * as types from '~/monitoring/stores/mutation_types';
-import {
- fetchDashboard,
- receiveMetricsDashboardSuccess,
- receiveMetricsDashboardFailure,
- fetchDeploymentsData,
- fetchEnvironmentsData,
- fetchPrometheusMetrics,
- fetchPrometheusMetric,
- requestMetricsData,
- setEndpoints,
- setGettingStartedEmptyState,
-} from '~/monitoring/stores/actions';
-import storeState from '~/monitoring/stores/state';
-import testAction from 'spec/helpers/vuex_action_helper';
-import { resetStore } from '../helpers';
-import {
- deploymentData,
- environmentData,
- metricsDashboardResponse,
- metricsGroupsAPIResponse,
- dashboardGitResponse,
-} from '../mock_data';
-
-describe('Monitoring store actions', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- resetStore(store);
- mock.restore();
- });
-
- describe('requestMetricsData', () => {
- it('sets emptyState to loading', () => {
- const commit = jasmine.createSpy();
- const { state } = store;
-
- requestMetricsData({ state, commit });
-
- expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA);
- });
- });
-
- describe('fetchDeploymentsData', () => {
- it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
- const dispatch = jasmine.createSpy();
- const { state } = store;
- state.deploymentsEndpoint = '/success';
-
- mock.onGet(state.deploymentsEndpoint).reply(200, {
- deployments: deploymentData,
- });
-
- fetchDeploymentsData({ state, dispatch })
- .then(() => {
- expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataSuccess', deploymentData);
- done();
- })
- .catch(done.fail);
- });
-
- it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => {
- const dispatch = jasmine.createSpy();
- const { state } = store;
- state.deploymentsEndpoint = '/error';
-
- mock.onGet(state.deploymentsEndpoint).reply(500);
-
- fetchDeploymentsData({ state, dispatch })
- .then(() => {
- expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataFailure');
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('fetchEnvironmentsData', () => {
- it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => {
- const dispatch = jasmine.createSpy();
- const { state } = store;
- state.environmentsEndpoint = '/success';
-
- mock.onGet(state.environmentsEndpoint).reply(200, {
- environments: environmentData,
- });
-
- fetchEnvironmentsData({ state, dispatch })
- .then(() => {
- expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData);
- done();
- })
- .catch(done.fail);
- });
-
- it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => {
- const dispatch = jasmine.createSpy();
- const { state } = store;
- state.environmentsEndpoint = '/error';
-
- mock.onGet(state.environmentsEndpoint).reply(500);
-
- fetchEnvironmentsData({ state, dispatch })
- .then(() => {
- expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('Set endpoints', () => {
- let mockedState;
-
- beforeEach(() => {
- mockedState = storeState();
- });
-
- it('should commit SET_ENDPOINTS mutation', done => {
- testAction(
- setEndpoints,
- {
- metricsEndpoint: 'additional_metrics.json',
- deploymentsEndpoint: 'deployments.json',
- environmentsEndpoint: 'deployments.json',
- },
- mockedState,
- [
- {
- type: types.SET_ENDPOINTS,
- payload: {
- metricsEndpoint: 'additional_metrics.json',
- deploymentsEndpoint: 'deployments.json',
- environmentsEndpoint: 'deployments.json',
- },
- },
- ],
- [],
- done,
- );
- });
- });
-
- describe('Set empty states', () => {
- let mockedState;
-
- beforeEach(() => {
- mockedState = storeState();
- });
-
- it('should commit SET_METRICS_ENDPOINT mutation', done => {
- testAction(
- setGettingStartedEmptyState,
- null,
- mockedState,
- [{ type: types.SET_GETTING_STARTED_EMPTY_STATE }],
- [],
- done,
- );
- });
- });
-
- describe('fetchDashboard', () => {
- let dispatch;
- let state;
- const response = metricsDashboardResponse;
-
- beforeEach(() => {
- dispatch = jasmine.createSpy();
- state = storeState();
- state.dashboardEndpoint = '/dashboard';
- });
-
- it('dispatches receive and success actions', done => {
- const params = {};
- mock.onGet(state.dashboardEndpoint).reply(200, response);
-
- fetchDashboard({ state, dispatch }, params)
- .then(() => {
- expect(dispatch).toHaveBeenCalledWith('requestMetricsDashboard');
- expect(dispatch).toHaveBeenCalledWith('receiveMetricsDashboardSuccess', {
- response,
- params,
- });
- done();
- })
- .catch(done.fail);
- });
-
- it('dispatches failure action', done => {
- const params = {};
- mock.onGet(state.dashboardEndpoint).reply(500);
-
- fetchDashboard({ state, dispatch }, params)
- .then(() => {
- expect(dispatch).toHaveBeenCalledWith(
- 'receiveMetricsDashboardFailure',
- new Error('Request failed with status code 500'),
- );
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('receiveMetricsDashboardSuccess', () => {
- let commit;
- let dispatch;
- let state;
-
- beforeEach(() => {
- commit = jasmine.createSpy();
- dispatch = jasmine.createSpy();
- state = storeState();
- });
-
- it('stores groups ', () => {
- const params = {};
- const response = metricsDashboardResponse;
-
- receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
-
- expect(commit).toHaveBeenCalledWith(
- types.RECEIVE_METRICS_DATA_SUCCESS,
- metricsDashboardResponse.dashboard.panel_groups,
- );
-
- expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params);
- });
-
- it('sets the dashboards loaded from the repository', () => {
- const params = {};
- const response = metricsDashboardResponse;
-
- response.all_dashboards = dashboardGitResponse;
- receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
-
- expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
- });
- });
-
- describe('receiveMetricsDashboardFailure', () => {
- let commit;
-
- beforeEach(() => {
- commit = jasmine.createSpy();
- });
-
- it('commits failure action', () => {
- receiveMetricsDashboardFailure({ commit });
-
- expect(commit).toHaveBeenCalledWith(types.RECEIVE_METRICS_DATA_FAILURE, undefined);
- });
-
- it('commits failure action with error', () => {
- receiveMetricsDashboardFailure({ commit }, 'uh-oh');
-
- expect(commit).toHaveBeenCalledWith(types.RECEIVE_METRICS_DATA_FAILURE, 'uh-oh');
- });
- });
-
- describe('fetchPrometheusMetrics', () => {
- let commit;
- let dispatch;
-
- beforeEach(() => {
- commit = jasmine.createSpy();
- dispatch = jasmine.createSpy();
- });
-
- it('commits empty state when state.groups is empty', done => {
- const state = storeState();
- const params = {};
-
- fetchPrometheusMetrics({ state, commit, dispatch }, params)
- .then(() => {
- expect(commit).toHaveBeenCalledWith(types.SET_NO_DATA_EMPTY_STATE);
- expect(dispatch).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
-
- it('dispatches fetchPrometheusMetric for each panel query', done => {
- const params = {};
- const state = storeState();
- state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups;
-
- const metric = state.dashboard.panel_groups[0].panels[0].metrics[0];
-
- fetchPrometheusMetrics({ state, commit, dispatch }, params)
- .then(() => {
- expect(dispatch.calls.count()).toEqual(3);
- expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', { metric, params });
- done();
- })
- .catch(done.fail);
-
- done();
- });
- });
-
- describe('fetchPrometheusMetric', () => {
- it('commits prometheus query result', done => {
- const commit = jasmine.createSpy();
- const params = {
- start: '2019-08-06T12:40:02.184Z',
- end: '2019-08-06T20:40:02.184Z',
- };
- const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
- const state = storeState();
-
- const data = metricsGroupsAPIResponse[0].panels[0].metrics[0];
- const response = { data };
- mock.onGet('http://test').reply(200, response);
-
- fetchPrometheusMetric({ state, commit }, { metric, params });
-
- setTimeout(() => {
- expect(commit).toHaveBeenCalledWith(types.SET_QUERY_RESULT, {
- metricId: metric.metric_id,
- result: data.result,
- });
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index a97608d6b8a..1256852c472 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -76,4 +76,25 @@ describe('Subscriptions', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSidebar');
});
+
+ describe('given project emails are disabled', () => {
+ const subscribeDisabledDescription = 'Notifications have been disabled';
+
+ beforeEach(() => {
+ vm = mountComponent(Subscriptions, {
+ subscribed: false,
+ projectEmailsDisabled: true,
+ subscribeDisabledDescription,
+ });
+ });
+
+ it('sets the correct display text', () => {
+ expect(vm.$el.textContent).toContain(subscribeDisabledDescription);
+ expect(vm.$refs.tooltip.dataset.originalTitle).toBe(subscribeDisabledDescription);
+ });
+
+ it('does not render the toggle button', () => {
+ expect(vm.$refs.toggleButton).toBeUndefined();
+ });
+ });
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 2339a090ccd..8f627fcc24d 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -30,6 +30,8 @@ issues:
- prometheus_alert_events
- self_managed_prometheus_alert_events
- zoom_meetings
+- vulnerability_links
+- related_vulnerabilities
events:
- author
- project
diff --git a/spec/lib/gitlab/import_export/group_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group_tree_saver_spec.rb
index c752c557d99..b856441981a 100644
--- a/spec/lib/gitlab/import_export/group_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group_tree_saver_spec.rb
@@ -39,12 +39,16 @@ describe Gitlab::ImportExport::GroupTreeSaver do
end
context 'when :export_fast_serialize feature is enabled' do
+ let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) }
+
before do
stub_feature_flags(export_fast_serialize: true)
+
+ expect(Gitlab::ImportExport::FastHashSerializer).to receive(:new).with(group, group_tree).and_return(serializer)
end
it 'uses FastHashSerializer' do
- expect_any_instance_of(Gitlab::ImportExport::FastHashSerializer).to receive(:execute).and_call_original
+ expect(serializer).to receive(:execute)
group_tree_saver.save
end
@@ -103,6 +107,18 @@ describe Gitlab::ImportExport::GroupTreeSaver do
expect(saved_group_json['badges']).not_to be_empty
end
+ context 'group children' do
+ let(:children) { group.children }
+
+ it 'exports group children' do
+ expect(saved_group_json['children'].length).to eq(children.count)
+ end
+
+ it 'exports group children of children' do
+ expect(saved_group_json['children'].first['children'].length).to eq(children.first.children.count)
+ end
+ end
+
context 'group members' do
let(:user2) { create(:user, email: 'group@member.com') }
let(:member_emails) do
@@ -146,6 +162,8 @@ describe Gitlab::ImportExport::GroupTreeSaver do
def setup_group
group = create(:group, description: 'description')
+ sub_group = create(:group, description: 'description', parent: group)
+ create(:group, description: 'description', parent: sub_group)
create(:milestone, group: group)
create(:group_badge, group: group)
group_label = create(:group_label, group: group)
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 8f3f45e159d..47e39e5fbe5 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -744,6 +744,12 @@ describe Environment, :use_clean_rails_memory_store_caching do
allow(environment).to receive(:deployment_platform).and_return(double)
end
+ context 'reactive cache configuration' do
+ it 'does not continue to spawn jobs' do
+ expect(described_class.reactive_cache_lifetime).to be < described_class.reactive_cache_refresh_interval
+ end
+ end
+
context 'reactive cache is empty' do
before do
stub_reactive_cache(environment, nil)
diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb
deleted file mode 100644
index 9451674161c..00000000000
--- a/spec/requests/user_avatar_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'Loading a user avatar' do
- let(:user) { create(:user, :with_avatar) }
-
- context 'when logged in' do
- # The exact query count will vary depending on the 2FA settings of the
- # instance, group, and user. Removing those extra 2FA queries in this case
- # may not be a good idea, so we just set up the ideal case.
- before do
- stub_application_setting(require_two_factor_authentication: true)
-
- login_as(create(:user, :two_factor))
- end
-
- # One each for: current user, avatar user, and upload record
- it 'only performs three SQL queries' do
- get user.avatar_url # Skip queries on first application load
-
- expect(response).to have_gitlab_http_status(200)
- expect { get user.avatar_url }.not_to exceed_query_limit(3)
- end
- end
-
- context 'when logged out' do
- # One each for avatar user and upload record
- it 'only performs two SQL queries' do
- get user.avatar_url # Skip queries on first application load
-
- expect(response).to have_gitlab_http_status(200)
- expect { get user.avatar_url }.not_to exceed_query_limit(2)
- end
- end
-end
diff --git a/spec/serializers/issuable_sidebar_extras_entity_spec.rb b/spec/serializers/issuable_sidebar_extras_entity_spec.rb
new file mode 100644
index 00000000000..a1a7c554b49
--- /dev/null
+++ b/spec/serializers/issuable_sidebar_extras_entity_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IssuableSidebarExtrasEntity do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:resource) { create(:issue, project: project) }
+ let(:request) { double('request', current_user: user) }
+
+ subject { described_class.new(resource, request: request).as_json }
+
+ it 'have subscribe attributes' do
+ expect(subject).to include(:participants,
+ :project_emails_disabled,
+ :subscribe_disabled_description,
+ :subscribed,
+ :assignees)
+ end
+end
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index c724a1a47b4..87fcd70a298 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -24,33 +24,63 @@ describe MergeRequests::FfMergeService do
context 'valid params' do
let(:service) { described_class.new(project, user, valid_merge_params) }
- before do
- allow(service).to receive(:execute_hooks)
-
+ def execute_ff_merge
perform_enqueued_jobs do
service.execute(merge_request)
end
end
+ before do
+ allow(service).to receive(:execute_hooks)
+ end
+
it "does not create merge commit" do
+ execute_ff_merge
+
source_branch_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
+
expect(source_branch_sha).to eq(target_branch_sha)
end
- it { expect(merge_request).to be_valid }
- it { expect(merge_request).to be_merged }
+ it 'keeps the merge request valid' do
+ expect { execute_ff_merge }
+ .not_to change { merge_request.valid? }
+ end
+
+ it 'updates the merge request to merged' do
+ expect { execute_ff_merge }
+ .to change { merge_request.merged? }
+ .from(false)
+ .to(true)
+ end
it 'sends email to user2 about merge of new merge_request' do
+ execute_ff_merge
+
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(merge_request.title)
end
it 'creates system note about merge_request merge' do
+ execute_ff_merge
+
note = merge_request.notes.last
expect(note.note).to include 'merged'
end
+
+ it 'does not update squash_commit_sha if it is not a squash' do
+ expect { execute_ff_merge }.not_to change { merge_request.squash_commit_sha }
+ end
+
+ it 'updates squash_commit_sha if it is a squash' do
+ merge_request.update!(squash: true)
+
+ expect { execute_ff_merge }
+ .to change { merge_request.squash_commit_sha }
+ .from(nil)
+ end
end
context 'error handling' do
@@ -83,6 +113,16 @@ describe MergeRequests::FfMergeService do
expect(merge_request.merge_error).to include(error_message)
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
+
+ it 'does not update squash_commit_sha if squash merge is not successful' do
+ merge_request.update!(squash: true)
+
+ expect(project.repository.raw).to receive(:ff_merge) do
+ raise 'Merge error'
+ end
+
+ expect { service.execute(merge_request) }.not_to change { merge_request.squash_commit_sha }
+ end
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 7a5e570558e..d7533f99683 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -66,6 +66,11 @@ RSpec.configure do |config|
config.infer_spec_type_from_file_location!
config.full_backtrace = !!ENV['CI']
+ unless ENV['CI']
+ # Re-run failures locally with `--only-failures`
+ config.example_status_persistence_file_path = './spec/examples.txt'
+ end
+
config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
location = metadata[:location]