diff options
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 @@ -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 Binary files differdeleted file mode 100644 index 73c2ecd182a..00000000000 --- a/doc/user/project/clusters/img/kubernetes_pod_logs_v12_4.png +++ /dev/null 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 Binary files differnew file mode 100644 index 00000000000..e54637e7218 --- /dev/null +++ b/doc/user/project/clusters/img/kubernetes_pod_logs_v12_5.png 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 Binary files differnew file mode 100644 index 00000000000..f113b0353f2 --- /dev/null +++ b/doc/user/project/clusters/img/sidebar_menu_pod_logs_v12_5.png 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 Binary files differnew file mode 100644 index 00000000000..18c17b910cd --- /dev/null +++ b/doc/user/project/integrations/img/grafana_panel_v12_5.png 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 Binary files differnew file mode 100644 index 00000000000..fae62dd50df --- /dev/null +++ b/doc/user/project/integrations/img/grafana_sharing_dialog_v12_5.png 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 Binary files differnew file mode 100644 index 00000000000..0036a916a12 --- /dev/null +++ b/doc/user/project/integrations/img/http_proxy_access_v12_5.png 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 Binary files differnew file mode 100644 index 00000000000..6cabe4193bd --- /dev/null +++ b/doc/user/project/integrations/img/rendered_grafana_embed_v12_5.png 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 Binary files differnew file mode 100644 index 00000000000..23503577327 --- /dev/null +++ b/doc/user/project/integrations/img/select_query_variables_v12_5.png 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] |