Add latest changes from gitlab-org/gitlab@master
|
@ -59,6 +59,7 @@ eslint-report.html
|
|||
/public/uploads.*
|
||||
/public/uploads/
|
||||
/shared/artifacts/
|
||||
/spec/examples.txt
|
||||
/rails_best_practices_output.html
|
||||
/tags
|
||||
/vendor/bundle/*
|
||||
|
|
|
@ -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
|
||||
|
|
6
Gemfile
|
@ -64,7 +64,7 @@ gem 'u2f', '~> 0.2.1'
|
|||
|
||||
# GitLab Pages
|
||||
gem 'validates_hostname', '~> 1.0.6'
|
||||
gem 'rubyzip', '~> 1.2.2', require: 'zip'
|
||||
gem 'rubyzip', '~> 1.3.0', require: 'zip'
|
||||
# GitLab Pages letsencrypt support
|
||||
gem 'acme-client', '~> 2.0.2'
|
||||
|
||||
|
@ -72,7 +72,7 @@ gem 'acme-client', '~> 2.0.2'
|
|||
gem 'browser', '~> 2.5'
|
||||
|
||||
# GPG
|
||||
gem 'gpgme', '~> 2.0.18'
|
||||
gem 'gpgme', '~> 2.0.19'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -151,7 +151,7 @@ gem 'asciidoctor-plantuml', '0.0.9'
|
|||
gem 'rouge', '~> 3.11.0'
|
||||
gem 'truncato', '~> 0.7.11'
|
||||
gem 'bootstrap_form', '~> 4.2.0'
|
||||
gem 'nokogiri', '~> 1.10.4'
|
||||
gem 'nokogiri', '~> 1.10.5'
|
||||
gem 'escape_utils', '~> 1.1'
|
||||
|
||||
# Calendar rendering
|
||||
|
|
10
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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refactor disabled sidebar notifications to Vue
|
||||
merge_request: 20007
|
||||
author: minghuan lei
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update the DB schema to allow linking between Vulnerabilities and Issues
|
||||
merge_request: 19852
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds a copy button next to package metadata on the details page
|
||||
merge_request: 19881
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update squash_commit_sha only on successful merge
|
||||
merge_request: 19688
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add dead jobs to Sidekiq metrics API
|
||||
merge_request: 19350
|
||||
author: Marco Peterseil
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix sub group export to export direct children
|
||||
merge_request: 20172
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Users can verify SAML configuration and view SamlResponse XML
|
||||
merge_request: 18362
|
||||
author:
|
||||
type: added
|
|
@ -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
|
15
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 | | | | ✓ | ✓ |
|
||||
|
|
Before Width: | Height: | Size: 384 KiB |
After Width: | Height: | Size: 179 KiB |
After Width: | Height: | Size: 13 KiB |
|
@ -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.
|
||||
|
|
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 7.2 KiB |
|
@ -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:
|
||||
|
|
|
@ -36,7 +36,8 @@ module API
|
|||
{
|
||||
processed: stats.processed,
|
||||
failed: stats.failed,
|
||||
enqueued: stats.enqueued
|
||||
enqueued: stats.enqueued,
|
||||
dead: stats.dead_size
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -90,6 +90,14 @@ describe ApplicationController do
|
|||
let(:format) { :html }
|
||||
|
||||
it_behaves_like 'setting gon variables'
|
||||
|
||||
context 'for peek requests' do
|
||||
before do
|
||||
request.path = '/-/peek'
|
||||
end
|
||||
|
||||
it_behaves_like 'not setting gon variables'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with json format' do
|
||||
|
@ -97,12 +105,6 @@ describe ApplicationController do
|
|||
|
||||
it_behaves_like 'not setting gon variables'
|
||||
end
|
||||
|
||||
context 'with atom format' do
|
||||
let(:format) { :atom }
|
||||
|
||||
it_behaves_like 'not setting gon variables'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'session expiration' do
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,6 +30,8 @@ issues:
|
|||
- prometheus_alert_events
|
||||
- self_managed_prometheus_alert_events
|
||||
- zoom_meetings
|
||||
- vulnerability_links
|
||||
- related_vulnerabilities
|
||||
events:
|
||||
- author
|
||||
- project
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|