Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9ce66d4dcf
commit
a0b26c6df5
2
Gemfile
2
Gemfile
|
@ -149,7 +149,7 @@ gem 'wikicloth', '0.8.1'
|
|||
gem 'asciidoctor', '~> 2.0.10'
|
||||
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
|
||||
gem 'asciidoctor-plantuml', '~> 0.0.12'
|
||||
gem 'rouge', '~> 3.19.0'
|
||||
gem 'rouge', '~> 3.20.0'
|
||||
gem 'truncato', '~> 0.7.11'
|
||||
gem 'bootstrap_form', '~> 4.2.0'
|
||||
gem 'nokogiri', '~> 1.10.9'
|
||||
|
|
|
@ -891,7 +891,7 @@ GEM
|
|||
rexml (3.2.4)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (3.19.0)
|
||||
rouge (3.20.0)
|
||||
rqrcode (0.7.0)
|
||||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
|
@ -1356,7 +1356,7 @@ DEPENDENCIES
|
|||
request_store (~> 1.5)
|
||||
responders (~> 3.0)
|
||||
retriable (~> 3.1.2)
|
||||
rouge (~> 3.19.0)
|
||||
rouge (~> 3.20.0)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-parameterized
|
||||
rspec-rails (~> 4.0.0)
|
||||
|
|
|
@ -32,7 +32,8 @@ import {
|
|||
} from '../constants';
|
||||
import AlertStatus from './alert_status.vue';
|
||||
|
||||
const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center';
|
||||
const tdClass =
|
||||
'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap';
|
||||
const thClass = 'gl-hover-bg-blue-50';
|
||||
const bodyTrClass =
|
||||
'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200';
|
||||
|
@ -60,40 +61,34 @@ export default {
|
|||
key: 'severity',
|
||||
label: s__('AlertManagement|Severity'),
|
||||
tdClass: `${tdClass} rounded-top text-capitalize`,
|
||||
thClass,
|
||||
thClass: `${thClass} gl-w-eighth`,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: 'startedAt',
|
||||
label: s__('AlertManagement|Start time'),
|
||||
thClass: `${thClass} js-started-at`,
|
||||
tdClass,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: 'endedAt',
|
||||
label: s__('AlertManagement|End time'),
|
||||
thClass,
|
||||
thClass: `${thClass} js-started-at w-15p`,
|
||||
tdClass,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
label: s__('AlertManagement|Alert'),
|
||||
thClass: `${thClass} w-30p gl-pointer-events-none`,
|
||||
thClass: `${thClass} gl-pointer-events-none`,
|
||||
tdClass,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
key: 'eventCount',
|
||||
label: s__('AlertManagement|Events'),
|
||||
thClass: `${thClass} text-right gl-pr-9 w-3rem`,
|
||||
thClass: `${thClass} text-right gl-w-12`,
|
||||
tdClass: `${tdClass} text-md-right`,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: 'assignees',
|
||||
label: s__('AlertManagement|Assignees'),
|
||||
thClass: 'gl-w-eighth',
|
||||
tdClass,
|
||||
},
|
||||
{
|
||||
|
@ -377,6 +372,7 @@ export default {
|
|||
:sort-desc.sync="sortDesc"
|
||||
:sort-by.sync="sortBy"
|
||||
sort-icon-left
|
||||
fixed
|
||||
@row-clicked="navigateToAlertDetails"
|
||||
@sort-changed="fetchSortedData"
|
||||
>
|
||||
|
@ -399,16 +395,12 @@ export default {
|
|||
<time-ago v-if="item.startedAt" :time="item.startedAt" />
|
||||
</template>
|
||||
|
||||
<template #cell(endedAt)="{ item }">
|
||||
<time-ago v-if="item.endedAt" :time="item.endedAt" />
|
||||
</template>
|
||||
|
||||
<template #cell(eventCount)="{ item }">
|
||||
{{ item.eventCount }}
|
||||
</template>
|
||||
|
||||
<template #cell(title)="{ item }">
|
||||
<div class="gl-max-w-full text-truncate">{{ item.title }}</div>
|
||||
<div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div>
|
||||
</template>
|
||||
|
||||
<template #cell(assignees)="{ item }">
|
||||
|
|
|
@ -8,6 +8,7 @@ fragment AlertDetailItem on AlertManagementAlert {
|
|||
service
|
||||
description
|
||||
updatedAt
|
||||
endedAt
|
||||
details
|
||||
notes {
|
||||
nodes {
|
||||
|
|
|
@ -4,7 +4,6 @@ fragment AlertListItem on AlertManagementAlert {
|
|||
severity
|
||||
status
|
||||
startedAt
|
||||
endedAt
|
||||
eventCount
|
||||
issueIid
|
||||
assignees {
|
||||
|
|
|
@ -65,18 +65,10 @@ const getSeriesLabel = (queryLabel, metricAttributes) => {
|
|||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const makeDataSeries = (queryResults, defaultConfig) =>
|
||||
queryResults
|
||||
.map(result => {
|
||||
// NaN values may disrupt avg., max. & min. calculations in the legend, filter them out
|
||||
const data = result.values.filter(([, value]) => !Number.isNaN(value));
|
||||
if (!data.length) {
|
||||
return null;
|
||||
}
|
||||
const series = { data };
|
||||
return {
|
||||
...defaultConfig,
|
||||
...series,
|
||||
name: getSeriesLabel(defaultConfig.name, result.metric),
|
||||
};
|
||||
})
|
||||
.filter(series => series !== null);
|
||||
queryResults.map(result => {
|
||||
return {
|
||||
...defaultConfig,
|
||||
data: result.values,
|
||||
name: getSeriesLabel(defaultConfig.name, result.metric),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
.alert-management-list {
|
||||
// Migrate this to gitlab-ui
|
||||
.gl-w-12 {
|
||||
width: $gl-spacing-scale-12;
|
||||
}
|
||||
|
||||
// these styles need to be deleted once GlTable component looks in GitLab same as in @gitlab/ui
|
||||
table {
|
||||
color: $gray-700;
|
||||
|
|
|
@ -30,7 +30,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
before_action :set_last_commit_sha, only: [:edit, :update]
|
||||
|
||||
before_action only: :show do
|
||||
push_frontend_feature_flag(:code_navigation, @project)
|
||||
push_frontend_feature_flag(:code_navigation, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
|
||||
end
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:deploy_from_footer, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:single_mr_diff_view, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
|
||||
push_frontend_feature_flag(:code_navigation, @project)
|
||||
push_frontend_feature_flag(:code_navigation, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:merge_ref_head_comments, @project)
|
||||
push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true)
|
||||
|
|
|
@ -110,7 +110,9 @@ class IssuableFinder
|
|||
|
||||
def group
|
||||
strong_memoize(:group) do
|
||||
if params[:group_id].present?
|
||||
if params[:group_id].is_a?(Group)
|
||||
params[:group_id]
|
||||
elsif params[:group_id].present?
|
||||
Group.find(params[:group_id])
|
||||
else
|
||||
nil
|
||||
|
|
|
@ -27,19 +27,14 @@ class IssuesFinder
|
|||
end
|
||||
|
||||
def user_can_see_all_confidential_issues?
|
||||
return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues)
|
||||
|
||||
return @user_can_see_all_confidential_issues = false if current_user.blank?
|
||||
return @user_can_see_all_confidential_issues = true if current_user.can_read_all_resources?
|
||||
|
||||
@user_can_see_all_confidential_issues =
|
||||
if project? && project
|
||||
project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
|
||||
elsif group
|
||||
group.max_member_access_for_user(current_user) >= CONFIDENTIAL_ACCESS_LEVEL
|
||||
strong_memoize(:user_can_see_all_confidential_issues) do
|
||||
parent = project? ? project : group
|
||||
if parent
|
||||
Ability.allowed?(current_user, :read_confidential_issues, parent)
|
||||
else
|
||||
false
|
||||
Ability.allowed?(current_user, :read_all_resources)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_cannot_see_confidential_issues?
|
||||
|
|
|
@ -9,30 +9,31 @@ module Mutations
|
|||
end
|
||||
|
||||
def resolve_issuable(type:, parent_path:, iid:)
|
||||
parent = resolve_issuable_parent(type, parent_path)
|
||||
key = type == :merge_request ? :iids : :iid
|
||||
args = { key => iid.to_s }
|
||||
parent = ::Gitlab::Graphql::Lazy.force(resolve_issuable_parent(type, parent_path))
|
||||
return unless parent.present?
|
||||
|
||||
resolver = issuable_resolver(type, parent, context)
|
||||
ready, early_return = resolver.ready?(**args)
|
||||
|
||||
return early_return unless ready
|
||||
|
||||
resolver.resolve(**args)
|
||||
finder = issuable_finder(type, iids: [iid])
|
||||
Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).find_all.first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def issuable_resolver(type, parent, context)
|
||||
resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
|
||||
|
||||
resolver_class.single.new(object: parent, context: context, field: nil)
|
||||
def issuable_finder(type, args)
|
||||
case type
|
||||
when :merge_request
|
||||
MergeRequestsFinder.new(current_user, args)
|
||||
when :issue
|
||||
IssuesFinder.new(current_user, args)
|
||||
else
|
||||
raise "Unsupported type: #{type}"
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_issuable_parent(type, parent_path)
|
||||
return unless parent_path.present?
|
||||
return unless type == :issue || type == :merge_request
|
||||
|
||||
resolve_project(full_path: parent_path) if parent_path.present?
|
||||
resolve_project(full_path: parent_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,5 +83,10 @@ module Resolvers
|
|||
def current_user
|
||||
context[:current_user]
|
||||
end
|
||||
|
||||
# Overridden in sub-classes (see .single, .last)
|
||||
def select_result(results)
|
||||
results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,16 +11,10 @@ module ResolvesMergeRequests
|
|||
end
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
args[:iids] = Array.wrap(args[:iids]) if args[:iids]
|
||||
args.compact!
|
||||
mr_finder = MergeRequestsFinder.new(current_user, args.compact)
|
||||
finder = Gitlab::Graphql::Loaders::IssuableLoader.new(project, mr_finder)
|
||||
|
||||
if project && args.keys == [:iids]
|
||||
batch_load_merge_requests(args[:iids])
|
||||
else
|
||||
args[:project_id] ||= project
|
||||
|
||||
apply_lookahead(MergeRequestsFinder.new(current_user, args).execute)
|
||||
end.then(&(single? ? :first : :itself))
|
||||
select_result(finder.batching_find_all { |query| apply_lookahead(query) })
|
||||
end
|
||||
|
||||
def ready?(**args)
|
||||
|
@ -35,22 +29,6 @@ module ResolvesMergeRequests
|
|||
|
||||
private
|
||||
|
||||
def batch_load_merge_requests(iids)
|
||||
iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def batch_load(iid)
|
||||
BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
|
||||
query = args[:key].merge_requests.where(iid: iids)
|
||||
|
||||
apply_lookahead(query).each do |mr|
|
||||
loader.call(mr.iid.to_s, mr)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def unconditional_includes
|
||||
[:target_project]
|
||||
end
|
||||
|
|
|
@ -63,18 +63,13 @@ module Resolvers
|
|||
parent = object.respond_to?(:sync) ? object.sync : object
|
||||
return Issue.none if parent.nil?
|
||||
|
||||
if parent.is_a?(Group)
|
||||
args[:group_id] = parent.id
|
||||
else
|
||||
args[:project_id] = parent.id
|
||||
end
|
||||
|
||||
# Will need to be be made group & namespace aware with
|
||||
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
|
||||
args[:iids] ||= [args[:iid]].compact
|
||||
args[:attempt_project_search_optimizations] = args[:search].present?
|
||||
args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
|
||||
args[:attempt_project_search_optimizations] = true if args[:search].present?
|
||||
|
||||
issues = IssuesFinder.new(context[:current_user], args).execute
|
||||
finder = IssuesFinder.new(current_user, args)
|
||||
issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all
|
||||
|
||||
if non_stable_cursor_sort?(args[:sort])
|
||||
# Certain complex sorts are not supported by the stable cursor pagination yet.
|
||||
|
|
|
@ -51,6 +51,8 @@ module Ci
|
|||
has_many :latest_builds, -> { latest }, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'Ci::Build'
|
||||
has_many :downloadable_artifacts, -> { not_expired.downloadable }, through: :latest_builds, source: :job_artifacts
|
||||
|
||||
has_many :messages, class_name: 'Ci::PipelineMessage', inverse_of: :pipeline
|
||||
|
||||
# Merge requests for which the current pipeline is running against
|
||||
# the merge request's latest commit.
|
||||
has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest'
|
||||
|
@ -634,6 +636,12 @@ module Ci
|
|||
yaml_errors.present?
|
||||
end
|
||||
|
||||
def add_error_message(content)
|
||||
return unless Gitlab::Ci::Features.store_pipeline_messages?(project)
|
||||
|
||||
messages.error.build(content: content)
|
||||
end
|
||||
|
||||
# Manually set the notes for a Ci::Pipeline
|
||||
# There is no ActiveRecord relation between Ci::Pipeline and notes
|
||||
# as they are related to a commit sha. This method helps importing
|
||||
|
@ -957,7 +965,7 @@ module Ci
|
|||
stages.find_by!(name: name)
|
||||
end
|
||||
|
||||
def error_messages
|
||||
def full_error_messages
|
||||
errors ? errors.full_messages.to_sentence : ""
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class PipelineMessage < ApplicationRecord
|
||||
extend Gitlab::Ci::Model
|
||||
|
||||
MAX_CONTENT_LENGTH = 10_000
|
||||
|
||||
belongs_to :pipeline
|
||||
|
||||
validates :content, presence: true
|
||||
|
||||
before_save :truncate_long_content
|
||||
|
||||
enum severity: { error: 0, warning: 1 }
|
||||
|
||||
private
|
||||
|
||||
def truncate_long_content
|
||||
return if content.length <= MAX_CONTENT_LENGTH
|
||||
|
||||
self.content = content.truncate(MAX_CONTENT_LENGTH)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -167,6 +167,7 @@ class Project < ApplicationRecord
|
|||
has_one :youtrack_service
|
||||
has_one :custom_issue_tracker_service
|
||||
has_one :bugzilla_service
|
||||
has_one :gitlab_issue_tracker_service, inverse_of: :project
|
||||
has_one :external_wiki_service
|
||||
has_one :prometheus_service, inverse_of: :project
|
||||
has_one :mock_ci_service
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GitlabIssueTrackerService < IssueTrackerService
|
||||
include Gitlab::Routing
|
||||
|
||||
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
default_value_for :default, true
|
||||
|
||||
def title
|
||||
'GitLab'
|
||||
end
|
||||
|
||||
def description
|
||||
s_('IssueTracker|GitLab issue tracker')
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'gitlab'
|
||||
end
|
||||
|
||||
def project_url
|
||||
project_issues_url(project)
|
||||
end
|
||||
|
||||
def new_issue_url
|
||||
new_project_issue_url(project)
|
||||
end
|
||||
|
||||
def issue_url(iid)
|
||||
project_issue_url(project, id: iid)
|
||||
end
|
||||
|
||||
def issue_tracker_path
|
||||
project_issues_path(project)
|
||||
end
|
||||
|
||||
def new_issue_path
|
||||
new_project_issue_path(project)
|
||||
end
|
||||
|
||||
def issue_path(iid)
|
||||
project_issue_path(project, id: iid)
|
||||
end
|
||||
end
|
|
@ -55,9 +55,11 @@ class Service < ApplicationRecord
|
|||
validates :instance, uniqueness: { scope: :type }, if: -> { instance? }
|
||||
validate :validate_is_instance_or_template
|
||||
|
||||
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
|
||||
scope :issue_trackers, -> { where(category: 'issue_tracker') }
|
||||
scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :without_defaults, -> { where(default: false) }
|
||||
scope :by_type, -> (type) { where(type: type) }
|
||||
scope :by_active_flag, -> (flag) { where(active: flag) }
|
||||
scope :templates, -> { where(template: true, type: available_services_types) }
|
||||
|
@ -75,7 +77,7 @@ class Service < ApplicationRecord
|
|||
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
|
||||
scope :deployment_hooks, -> { where(deployment_events: true, active: true) }
|
||||
scope :alert_hooks, -> { where(alert_events: true, active: true) }
|
||||
scope :external_issue_trackers, -> { issue_trackers.active }
|
||||
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
|
||||
scope :deployment, -> { where(category: 'deployment') }
|
||||
|
||||
default_value_for :category, 'common'
|
||||
|
|
|
@ -58,6 +58,8 @@ class BasePolicy < DeclarativePolicy::Base
|
|||
rule { admin }.enable :read_all_resources
|
||||
|
||||
rule { default }.enable :read_cross_project
|
||||
|
||||
condition(:is_gitlab_com) { ::Gitlab.dev_env_or_com? }
|
||||
end
|
||||
|
||||
BasePolicy.prepend_if_ee('EE::BasePolicy')
|
||||
|
|
|
@ -67,6 +67,10 @@ class GroupPolicy < BasePolicy
|
|||
enable :update_max_artifacts_size
|
||||
end
|
||||
|
||||
rule { can?(:read_all_resources) }.policy do
|
||||
enable :read_confidential_issues
|
||||
end
|
||||
|
||||
rule { has_projects }.policy do
|
||||
enable :read_group
|
||||
end
|
||||
|
|
|
@ -176,6 +176,7 @@ class ProjectPolicy < BasePolicy
|
|||
rule { guest | admin }.enable :read_project_for_iids
|
||||
|
||||
rule { admin }.enable :update_max_artifacts_size
|
||||
rule { can?(:read_all_resources) }.enable :read_confidential_issues
|
||||
|
||||
rule { guest }.enable :guest_access
|
||||
rule { reporter }.enable :reporter_access
|
||||
|
@ -257,6 +258,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_prometheus
|
||||
enable :read_metrics_dashboard_annotation
|
||||
enable :metrics_dashboard
|
||||
enable :read_confidential_issues
|
||||
end
|
||||
|
||||
# We define `:public_user_access` separately because there are cases in gitlab-ee
|
||||
|
|
|
@ -95,7 +95,7 @@ module Ci
|
|||
def execute!(*args, &block)
|
||||
execute(*args, &block).tap do |pipeline|
|
||||
unless pipeline.persisted?
|
||||
raise CreateError, pipeline.error_messages
|
||||
raise CreateError, pipeline.full_error_messages
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
|
||||
.help-block
|
||||
= _('Specify an e-mail address regex pattern to identify default internal users.')
|
||||
= link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
|
||||
= link_to _('More information'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'),
|
||||
target: '_blank'
|
||||
.form-group
|
||||
= f.label :user_show_add_ssh_key_message, _('Prompt users to upload SSH keys'), class: 'label-bold'
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
= f.text_field :default_artifacts_expire_in, class: 'form-control'
|
||||
.form-text.text-muted
|
||||
= _("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>.").html_safe
|
||||
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
|
||||
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration-core-only')
|
||||
.form-group
|
||||
= f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
|
||||
= f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
%h4
|
||||
= _('Variables')
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want.')
|
||||
= _('You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>.').html_safe
|
||||
= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'variables')
|
||||
= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables')
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
%h4
|
||||
= _('Variables')
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
||||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
= value
|
||||
%p.masking-validation-error.gl-field-error.hide
|
||||
= s_("CiVariables|Cannot use Masked Variable with current value")
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'masked-variables'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'mask-a-custom-variable'), target: '_blank', rel: 'noopener noreferrer'
|
||||
- unless only_key_value
|
||||
.ci-variable-body-item.ci-variable-protected-item.table-section.section-20.mr-0.border-top-0
|
||||
.append-right-default
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
label_class: 'label-bold' }
|
||||
.form-text.text-muted
|
||||
= s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
|
||||
= link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank'
|
||||
= link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank'
|
||||
|
||||
.form-group
|
||||
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
|
||||
|
|
|
@ -33,5 +33,5 @@
|
|||
.flash-notice
|
||||
.flash-text
|
||||
= s_("PrometheusService|To set up automatic monitoring, add the environment variable %{variable} to exporter's queries." % { variable: "<code>$CI_ENVIRONMENT_SLUG</code>" }).html_safe
|
||||
= link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels')
|
||||
= link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus', anchor: 'query-variables')
|
||||
%ul.list-unstyled.metrics-list.js-missing-var-metrics-list
|
||||
|
|
|
@ -40,18 +40,18 @@
|
|||
= form.radio_button :deploy_strategy, 'continuous', class: 'form-check-input'
|
||||
= form.label :deploy_strategy_continuous, class: 'form-check-label' do
|
||||
= s_('CICD|Continuous deployment to production')
|
||||
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-deploy'), target: '_blank'
|
||||
= link_to icon('question-circle'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank'
|
||||
|
||||
.form-check
|
||||
= form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input'
|
||||
= form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do
|
||||
= s_('CICD|Continuous deployment to production using timed incremental rollout')
|
||||
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production-premium'), target: '_blank'
|
||||
= link_to icon('question-circle'), help_page_path('topics/autodevops/customize.md', anchor: 'timed-incremental-rollout-to-production-premium'), target: '_blank'
|
||||
|
||||
.form-check
|
||||
= form.radio_button :deploy_strategy, 'manual', class: 'form-check-input'
|
||||
= form.label :deploy_strategy_manual, class: 'form-check-label' do
|
||||
= s_('CICD|Automatic deployment to staging, manual deployment to production')
|
||||
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production-premium'), target: '_blank'
|
||||
= link_to icon('question-circle'), help_page_path('topics/autodevops/customize.md', anchor: 'incremental-rollout-to-production-premium'), target: '_blank'
|
||||
|
||||
= f.submit _('Save changes'), class: "btn btn-success prepend-top-15", data: { qa_selector: 'save_changes_button' }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Stop removing NaN values from monitoring data series
|
||||
merge_request: 35086
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update UI links to docs in core features
|
||||
merge_request: 35488
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose snippets_size in ProjectStatistics Entity
|
||||
merge_request: 35316
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Store pipeline creation errors and warnings into Ci::PipelineMessage
|
||||
merge_request: 33762
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve alert list spacing
|
||||
merge_request: 35400
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateCiPipelineMessagesTable < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
ERROR_SEVERITY = 0
|
||||
MAX_CONTENT_LENGTH = 10_000
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
create_table :ci_pipeline_messages do |t|
|
||||
t.integer :severity, null: false, default: ERROR_SEVERITY, limit: 2
|
||||
t.references :pipeline, index: true, null: false, foreign_key: { to_table: :ci_pipelines, on_delete: :cascade }, type: :integer
|
||||
t.text :content, null: false
|
||||
end
|
||||
end
|
||||
|
||||
add_text_limit :ci_pipeline_messages, :content, MAX_CONTENT_LENGTH
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :ci_pipeline_messages
|
||||
end
|
||||
end
|
|
@ -9936,6 +9936,23 @@ CREATE SEQUENCE public.ci_pipeline_chat_data_id_seq
|
|||
|
||||
ALTER SEQUENCE public.ci_pipeline_chat_data_id_seq OWNED BY public.ci_pipeline_chat_data.id;
|
||||
|
||||
CREATE TABLE public.ci_pipeline_messages (
|
||||
id bigint NOT NULL,
|
||||
severity smallint DEFAULT 0 NOT NULL,
|
||||
pipeline_id integer NOT NULL,
|
||||
content text NOT NULL,
|
||||
CONSTRAINT check_58ca2981b2 CHECK ((char_length(content) <= 10000))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.ci_pipeline_messages_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE public.ci_pipeline_messages_id_seq OWNED BY public.ci_pipeline_messages.id;
|
||||
|
||||
CREATE TABLE public.ci_pipeline_schedule_variables (
|
||||
id integer NOT NULL,
|
||||
key character varying NOT NULL,
|
||||
|
@ -16293,6 +16310,8 @@ ALTER TABLE ONLY public.ci_job_variables ALTER COLUMN id SET DEFAULT nextval('pu
|
|||
|
||||
ALTER TABLE ONLY public.ci_pipeline_chat_data ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_chat_data_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY public.ci_pipeline_messages ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_messages_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY public.ci_pipeline_schedule_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedule_variables_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY public.ci_pipeline_schedules ALTER COLUMN id SET DEFAULT nextval('public.ci_pipeline_schedules_id_seq'::regclass);
|
||||
|
@ -17203,6 +17222,9 @@ ALTER TABLE ONLY public.ci_job_variables
|
|||
ALTER TABLE ONLY public.ci_pipeline_chat_data
|
||||
ADD CONSTRAINT ci_pipeline_chat_data_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public.ci_pipeline_messages
|
||||
ADD CONSTRAINT ci_pipeline_messages_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public.ci_pipeline_schedule_variables
|
||||
ADD CONSTRAINT ci_pipeline_schedule_variables_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -18605,6 +18627,8 @@ CREATE INDEX index_ci_pipeline_chat_data_on_chat_name_id ON public.ci_pipeline_c
|
|||
|
||||
CREATE UNIQUE INDEX index_ci_pipeline_chat_data_on_pipeline_id ON public.ci_pipeline_chat_data USING btree (pipeline_id);
|
||||
|
||||
CREATE INDEX index_ci_pipeline_messages_on_pipeline_id ON public.ci_pipeline_messages USING btree (pipeline_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_pipeline_schedule_variables_on_schedule_id_and_key ON public.ci_pipeline_schedule_variables USING btree (pipeline_schedule_id, key);
|
||||
|
||||
CREATE INDEX index_ci_pipeline_schedules_on_next_run_at_and_active ON public.ci_pipeline_schedules USING btree (next_run_at, active);
|
||||
|
@ -21844,6 +21868,9 @@ ALTER TABLE ONLY public.packages_conan_metadata
|
|||
ALTER TABLE ONLY public.vulnerability_feedback
|
||||
ADD CONSTRAINT fk_rails_8c77e5891a FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY public.ci_pipeline_messages
|
||||
ADD CONSTRAINT fk_rails_8d3b04e3e1 FOREIGN KEY (pipeline_id) REFERENCES public.ci_pipelines(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY public.approval_merge_request_rules_approved_approvers
|
||||
ADD CONSTRAINT fk_rails_8dc94cff4d FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -23417,6 +23444,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200622070620
|
||||
20200622095419
|
||||
20200622103836
|
||||
20200622104923
|
||||
20200622235737
|
||||
20200623000148
|
||||
20200623000320
|
||||
|
|
|
@ -175,7 +175,8 @@ When the user is authenticated and `simple` is not set this returns something li
|
|||
"wiki_size" : 0,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"packages_size": 0
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0
|
||||
},
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
|
@ -277,7 +278,8 @@ When the user is authenticated and `simple` is not set this returns something li
|
|||
"wiki_size" : 0,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"packages_size": 0
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0
|
||||
},
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
|
@ -426,7 +428,8 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
|
|||
"wiki_size" : 0,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"packages_size": 0
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0
|
||||
},
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
|
@ -528,7 +531,8 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
|
|||
"wiki_size" : 0,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"packages_size": 0
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0
|
||||
},
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
|
@ -899,7 +903,8 @@ GET /projects/:id
|
|||
"wiki_size" : 0,
|
||||
"lfs_objects_size": 0,
|
||||
"job_artifacts_size": 0,
|
||||
"packages_size": 0
|
||||
"packages_size": 0,
|
||||
"snippets_size": 0
|
||||
},
|
||||
"_links": {
|
||||
"self": "http://example.com/api/v4/projects",
|
||||
|
|
|
@ -118,21 +118,17 @@ The sort option and order is saved and used wherever you browse epics, including
|
|||
|
||||
## Make an epic confidential
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213068) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
|
||||
> - It's deployed behind a feature flag, disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-confidential-epics-premium-only). **(PREMIUM ONLY)**
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213068) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0 behind a feature flag, disabled by default.
|
||||
|
||||
When you're creating an epic, you can make it confidential by selecting the **Make this epic
|
||||
confidential** checkbox.
|
||||
|
||||
### Enable Confidential Epics **(PREMIUM ONLY)**
|
||||
### Enable confidential epics **(PREMIUM ONLY)**
|
||||
|
||||
The Confidential Epics feature is under development and not ready for production use.
|
||||
It's deployed behind a feature flag that is **disabled by default**.
|
||||
The confidential epics feature is under development and not ready for production use. It's deployed
|
||||
behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can enable it for your instance.
|
||||
can enable it for your self-managed instance.
|
||||
|
||||
To enable it:
|
||||
|
||||
|
|
|
@ -289,8 +289,43 @@ Once the Code Quality job has completed:
|
|||
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
|
||||
for the `code_quality` job.
|
||||
|
||||
## Extending functionality
|
||||
|
||||
### Using Analysis Plugins
|
||||
|
||||
Should there be a need to extend the default functionality provided by Code Quality, as stated in [Code Quality](#code-quality-starter), [Analysis Plugins](https://docs.codeclimate.com/docs/list-of-engines) are available.
|
||||
|
||||
For example, to use the [SonarJava analyzer](https://docs.codeclimate.com/docs/sonar-java),
|
||||
add a file named `.codeclimate.yml` containing the [enablement code](https://docs.codeclimate.com/docs/sonar-java#enable-the-plugin)
|
||||
for the plugin to the root of your repository:
|
||||
|
||||
```yaml
|
||||
version: "2"
|
||||
plugins:
|
||||
sonar-java:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
This adds SonarJava to the `plugins:` section of the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml)
|
||||
included in your project.
|
||||
|
||||
Changes to the `plugins:` section do not affect the `exclude_patterns` section of the
|
||||
defeault `.codeclimate.yml`. See the Code Climate documentation for
|
||||
[excluding files and folders](https://docs.codeclimate.com/docs/excluding-files-and-folders)
|
||||
for more details.
|
||||
|
||||
Here's [an example project](https://gitlab.com/jheimbuck_gl/jh_java_example_project) that uses Code Quality with a `.codeclimate.yml` file.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Changing the default configuration has no effect
|
||||
|
||||
A common issue is that the terms `Code Quality` (GitLab specific) and `Code Climate`
|
||||
(Engine used by GitLab) are very similar. You must add a **`.codeclimate.yml`** file
|
||||
to change the default configuration, **not** a `.codequality.yml` file. If you use
|
||||
the wrong filename, the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml)
|
||||
is still used.
|
||||
|
||||
### No Code Quality report is displayed in a Merge Request
|
||||
|
||||
This can be due to multiple reasons:
|
||||
|
|
|
@ -9,6 +9,7 @@ module API
|
|||
expose :wiki_size
|
||||
expose :lfs_objects_size
|
||||
expose :build_artifacts_size, as: :job_artifacts_size
|
||||
expose :snippets_size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,10 @@ module Banzai
|
|||
issue_url(issue, project)
|
||||
end
|
||||
|
||||
def projects_relation_for_paths(paths)
|
||||
super(paths).includes(:gitlab_issue_tracker_service)
|
||||
end
|
||||
|
||||
def parent_records(parent, ids)
|
||||
parent.issues.where(iid: ids.to_a)
|
||||
end
|
||||
|
|
|
@ -45,6 +45,11 @@ module Gitlab
|
|||
def self.release_generation_enabled?
|
||||
::Feature.enabled?(:ci_release_generation)
|
||||
end
|
||||
|
||||
# Remove in https://gitlab.com/gitlab-org/gitlab/-/issues/224199
|
||||
def self.store_pipeline_messages?(project)
|
||||
::Feature.enabled?(:ci_store_pipeline_messages, project, default_enabled: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,12 @@ module Gitlab
|
|||
pipeline.yaml_errors = message
|
||||
end
|
||||
|
||||
pipeline.add_error_message(message)
|
||||
pipeline.drop!(drop_reason) if drop_reason
|
||||
|
||||
# TODO: consider not to rely on AR errors directly as they can be
|
||||
# polluted with other unrelated errors (e.g. state machine)
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/220823
|
||||
pipeline.errors.add(:base, message)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,8 +30,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def authorized_find!(*args)
|
||||
object = find_object(*args)
|
||||
object = object.sync if object.respond_to?(:sync)
|
||||
object = Graphql::Lazy.force(find_object(*args))
|
||||
|
||||
authorize!(object)
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Graphql
|
||||
class Lazy
|
||||
# Force evaluation of a (possibly) lazy value
|
||||
def self.force(value)
|
||||
case value
|
||||
when ::BatchLoader::GraphQL
|
||||
value.sync
|
||||
when ::Concurrent::Promise
|
||||
value.execute.value
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,82 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Graphql
|
||||
module Loaders
|
||||
class IssuableLoader
|
||||
attr_reader :parent, :issuable_finder
|
||||
|
||||
BatchKey = Struct.new(:parent, :finder_class, :current_user)
|
||||
|
||||
def initialize(parent, issuable_finder)
|
||||
@parent = parent
|
||||
@issuable_finder = issuable_finder
|
||||
end
|
||||
|
||||
def batching_find_all(&with_query)
|
||||
if issuable_finder.params.keys == ['iids']
|
||||
batch_load_issuables(issuable_finder.params[:iids], with_query)
|
||||
else
|
||||
post_process(find_all, with_query)
|
||||
end
|
||||
end
|
||||
|
||||
def find_all
|
||||
issuable_finder.params[parent_param] = parent if parent
|
||||
|
||||
issuable_finder.execute
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parent_param
|
||||
case parent
|
||||
when Project
|
||||
:project_id
|
||||
when Group
|
||||
:group_id
|
||||
else
|
||||
raise "Unexpected parent: #{parent.class}"
|
||||
end
|
||||
end
|
||||
|
||||
def post_process(query, with_query)
|
||||
if with_query
|
||||
with_query.call(query)
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
def batch_load_issuables(iids, with_query)
|
||||
Array.wrap(iids).map { |iid| batch_load(iid, with_query) }
|
||||
end
|
||||
|
||||
def batch_load(iid, with_query)
|
||||
return if parent.nil?
|
||||
|
||||
BatchLoader::GraphQL
|
||||
.for([parent_param, iid.to_s])
|
||||
.batch(key: batch_key) do |params, loader, args|
|
||||
batch_key = args[:key]
|
||||
user = batch_key.current_user
|
||||
|
||||
params.group_by(&:first).each do |key, group|
|
||||
iids = group.map(&:second).uniq
|
||||
args = { key => batch_key.parent, iids: iids }
|
||||
query = batch_key.finder_class.new(user, args).execute
|
||||
|
||||
post_process(query, with_query).each do |item|
|
||||
loader.call([key, item.iid.to_s], item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def batch_key
|
||||
BatchKey.new(parent, issuable_finder.class, issuable_finder.current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -52,8 +52,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def duration(time_started)
|
||||
nanoseconds = Gitlab::Metrics::System.monotonic_time - time_started
|
||||
nanoseconds * 1000000
|
||||
Gitlab::Metrics::System.monotonic_time - time_started
|
||||
end
|
||||
|
||||
def default_initial_values(query)
|
||||
|
|
|
@ -1951,9 +1951,6 @@ msgstr ""
|
|||
msgid "AlertManagement|Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertManagement|End time"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertManagement|Events"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10295,6 +10292,9 @@ msgstr ""
|
|||
msgid "Geo allows you to replicate your GitLab instance to other geographical locations."
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo nodes are paused using a command run on the node"
|
||||
msgstr ""
|
||||
|
||||
msgid "GeoNodeStatusEvent|%{timeAgoStr} (%{pendingEvents} events)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10400,6 +10400,9 @@ msgstr ""
|
|||
msgid "GeoNodes|Replication slots"
|
||||
msgstr ""
|
||||
|
||||
msgid "GeoNodes|Replication status"
|
||||
msgstr ""
|
||||
|
||||
msgid "GeoNodes|Repositories"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12593,6 +12596,9 @@ msgstr ""
|
|||
msgid "IssueTracker|Custom issue tracker"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|GitLab issue tracker"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Redmine issue tracker"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19049,6 +19055,12 @@ msgstr ""
|
|||
msgid "Replication"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replication enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replication paused"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reply by email"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19424,9 +19436,6 @@ msgstr ""
|
|||
msgid "Resume"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resume replication"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resync"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -107,6 +107,12 @@ FactoryBot.define do
|
|||
issue_tracker
|
||||
end
|
||||
|
||||
factory :gitlab_issue_tracker_service do
|
||||
project
|
||||
active { true }
|
||||
issue_tracker
|
||||
end
|
||||
|
||||
trait :issue_tracker do
|
||||
transient do
|
||||
create_data { true }
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
|
|||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(web_ide_default: false, single_mr_diff_view: false, code_navigation: false)
|
||||
stub_feature_flags(web_ide_default: false, single_mr_diff_view: false)
|
||||
|
||||
target_project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
|
|
|
@ -13,10 +13,6 @@ RSpec.describe 'File blob', :js do
|
|||
wait_for_requests
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(code_navigation: false)
|
||||
end
|
||||
|
||||
context 'Ruby file' do
|
||||
before do
|
||||
visit_blob('files/ruby/popen.rb')
|
||||
|
|
|
@ -77,8 +77,6 @@ RSpec.describe 'Editing file blob', :js do
|
|||
|
||||
context 'from blob file path' do
|
||||
before do
|
||||
stub_feature_flags(code_navigation: false)
|
||||
|
||||
visit project_blob_path(project, tree_join(branch, file_path))
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ RSpec.describe 'User creates blob in new project', :js do
|
|||
|
||||
shared_examples 'creating a file' do
|
||||
before do
|
||||
stub_feature_flags(code_navigation: false)
|
||||
sign_in(user)
|
||||
visit project_path(project)
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(web_ide_default: false, code_navigation: false)
|
||||
stub_feature_flags(web_ide_default: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
|
|
|
@ -14,8 +14,6 @@ RSpec.describe 'Projects > Files > User deletes files', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(code_navigation: false)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@ RSpec.describe 'Projects > Files > User replaces files', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(code_navigation: false)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -289,7 +289,6 @@ describe('AlertManagementList', () => {
|
|||
iid: 1,
|
||||
status: 'acknowledged',
|
||||
startedAt: '2020-03-17T23:18:14.996Z',
|
||||
endedAt: '2020-04-17T23:18:14.996Z',
|
||||
severity: 'high',
|
||||
assignees: { nodes: [] },
|
||||
},
|
||||
|
@ -300,7 +299,7 @@ describe('AlertManagementList', () => {
|
|||
},
|
||||
loading: false,
|
||||
});
|
||||
expect(findDateFields().length).toBe(2);
|
||||
expect(findDateFields().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not display time ago dates when values not provided', () => {
|
||||
|
@ -312,7 +311,6 @@ describe('AlertManagementList', () => {
|
|||
iid: 1,
|
||||
status: 'acknowledged',
|
||||
startedAt: null,
|
||||
endedAt: null,
|
||||
severity: 'high',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -33,15 +33,6 @@ describe('monitor helper', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('excludes NaN values', () => {
|
||||
expect(
|
||||
monitorHelper.makeDataSeries(
|
||||
data({ metric: {}, values: [[1, 1], [2, NaN]] }),
|
||||
defaultConfig,
|
||||
),
|
||||
).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]);
|
||||
});
|
||||
|
||||
it('updates series name from templates', () => {
|
||||
const config = {
|
||||
...defaultConfig,
|
||||
|
|
|
@ -815,6 +815,50 @@ describe('normalizeQueryResponseData', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('processes a scalar result with a NaN result', () => {
|
||||
// Queries may return "NaN" string values.
|
||||
// e.g. when Prometheus cannot find a metric the query
|
||||
// `scalar(does_not_exist)` will return a "NaN" value.
|
||||
|
||||
const mockScalar = {
|
||||
resultType: 'scalar',
|
||||
result: [1435781451.781, 'NaN'],
|
||||
};
|
||||
|
||||
expect(normalizeQueryResponseData(mockScalar)).toEqual([
|
||||
{
|
||||
metric: {},
|
||||
value: ['2015-07-01T20:10:51.781Z', NaN],
|
||||
values: [['2015-07-01T20:10:51.781Z', NaN]],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('processes a matrix result with a "NaN" value', () => {
|
||||
// Queries may return "NaN" string values.
|
||||
const mockMatrix = {
|
||||
resultType: 'matrix',
|
||||
result: [
|
||||
{
|
||||
metric: {
|
||||
__name__: 'up',
|
||||
job: 'prometheus',
|
||||
instance: 'localhost:9090',
|
||||
},
|
||||
values: [[1435781430.781, '1'], [1435781460.781, 'NaN']],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(normalizeQueryResponseData(mockMatrix)).toEqual([
|
||||
{
|
||||
metric: { __name__: 'up', instance: 'localhost:9090', job: 'prometheus' },
|
||||
value: ['2015-07-01T20:11:00.781Z', NaN],
|
||||
values: [['2015-07-01T20:10:30.781Z', 1], ['2015-07-01T20:11:00.781Z', NaN]],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeCustomDashboardPath', () => {
|
||||
|
|
|
@ -3,26 +3,31 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::ResolvesIssuable do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:mutation_class) do
|
||||
Class.new(Mutations::BaseMutation) do
|
||||
include Mutations::ResolvesIssuable
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:project) { create(:project, :empty_repo) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:context) { { current_user: user } }
|
||||
let_it_be(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
|
||||
let(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
|
||||
let(:parent) { issuable.project }
|
||||
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
context 'with issues' do
|
||||
let(:issuable) { create(:issue, project: project) }
|
||||
let(:issuable) { issue }
|
||||
|
||||
it_behaves_like 'resolving an issuable in GraphQL', :issue
|
||||
end
|
||||
|
||||
context 'with merge requests' do
|
||||
let(:issuable) { create(:merge_request, source_project: project) }
|
||||
let(:issuable) { merge_request }
|
||||
|
||||
it_behaves_like 'resolving an issuable in GraphQL', :merge_request
|
||||
end
|
||||
|
|
|
@ -101,12 +101,10 @@ RSpec.describe Resolvers::IssuesResolver do
|
|||
end
|
||||
|
||||
it 'uses project search optimization' do
|
||||
expected_arguments = {
|
||||
expected_arguments = a_hash_including(
|
||||
search: 'foo',
|
||||
attempt_project_search_optimizations: true,
|
||||
iids: [],
|
||||
project_id: project.id
|
||||
}
|
||||
attempt_project_search_optimizations: true
|
||||
)
|
||||
expect(IssuesFinder).to receive(:new).with(anything, expected_arguments).and_call_original
|
||||
|
||||
resolve_issues(search: 'foo')
|
||||
|
@ -217,16 +215,32 @@ RSpec.describe Resolvers::IssuesResolver do
|
|||
expect(resolve_issues).to contain_exactly(issue1, issue2)
|
||||
end
|
||||
|
||||
it 'finds a specific issue with iid' do
|
||||
expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
|
||||
it 'finds a specific issue with iid', :request_store do
|
||||
result = batch_sync(max_queries: 2) { resolve_issues(iid: issue1.iid) }
|
||||
|
||||
expect(result).to contain_exactly(issue1)
|
||||
end
|
||||
|
||||
it 'finds a specific issue with iids' do
|
||||
expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
|
||||
it 'batches queries that only include IIDs', :request_store do
|
||||
result = batch_sync(max_queries: 2) do
|
||||
resolve_issues(iid: issue1.iid) + resolve_issues(iids: issue2.iid)
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(issue1, issue2)
|
||||
end
|
||||
|
||||
it 'finds a specific issue with iids', :request_store do
|
||||
result = batch_sync(max_queries: 2) do
|
||||
resolve_issues(iids: [issue1.iid])
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(issue1)
|
||||
end
|
||||
|
||||
it 'finds multiple issues with iids' do
|
||||
expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
|
||||
create(:issue, project: project, author: current_user)
|
||||
|
||||
expect(batch_sync { resolve_issues(iids: [issue1.iid, issue2.iid]) })
|
||||
.to contain_exactly(issue1, issue2)
|
||||
end
|
||||
|
||||
|
@ -238,7 +252,7 @@ RSpec.describe Resolvers::IssuesResolver do
|
|||
create(:issue, project: another_project, iid: iid)
|
||||
end
|
||||
|
||||
expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
|
||||
expect(batch_sync { resolve_issues(iids: iids) }).to contain_exactly(issue1, issue2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,9 +22,12 @@ RSpec.describe Resolvers::MergeRequestsResolver do
|
|||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
other_project.add_developer(current_user)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
let(:queries_per_project) { 3 }
|
||||
|
||||
context 'no arguments' do
|
||||
it 'returns all merge requests' do
|
||||
result = resolve_mr(project, {})
|
||||
|
@ -40,24 +43,34 @@ RSpec.describe Resolvers::MergeRequestsResolver do
|
|||
end
|
||||
|
||||
context 'by iid alone' do
|
||||
it 'batch-resolves by target project full path and individual IID' do
|
||||
result = batch_sync(max_queries: 2) do
|
||||
it 'batch-resolves by target project full path and individual IID', :request_store do
|
||||
# 1 query for project_authorizations, and 1 for merge_requests
|
||||
result = batch_sync(max_queries: queries_per_project) do
|
||||
[iid_1, iid_2].map { |iid| resolve_mr_single(project, iid) }
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(merge_request_1, merge_request_2)
|
||||
end
|
||||
|
||||
it 'batch-resolves by target project full path and IIDS' do
|
||||
result = batch_sync(max_queries: 2) do
|
||||
it 'batch-resolves by target project full path and IIDS', :request_store do
|
||||
result = batch_sync(max_queries: queries_per_project) do
|
||||
resolve_mr(project, iids: [iid_1, iid_2])
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(merge_request_1, merge_request_2)
|
||||
end
|
||||
|
||||
it 'batch-resolves by target project full path and IIDS, single or plural', :request_store do
|
||||
result = batch_sync(max_queries: queries_per_project) do
|
||||
[resolve_mr_single(project, merge_request_3.iid), *resolve_mr(project, iids: [iid_1, iid_2])]
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3)
|
||||
end
|
||||
|
||||
it 'can batch-resolve merge requests from different projects' do
|
||||
result = batch_sync(max_queries: 3) do
|
||||
# 2 queries for project_authorizations, and 2 for merge_requests
|
||||
result = batch_sync(max_queries: queries_per_project * 2) do
|
||||
resolve_mr(project, iids: iid_1) +
|
||||
resolve_mr(project, iids: iid_2) +
|
||||
resolve_mr(other_project, iids: other_iid)
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Graphql::Loaders::IssuableLoader do
|
||||
subject { described_class.new(parent, finder) }
|
||||
|
||||
let(:params) { HashWithIndifferentAccess.new }
|
||||
|
||||
describe '#find_all' do
|
||||
let(:finder) { double(:finder, params: params, execute: %i[x y z]) }
|
||||
|
||||
where(:factory, :param_name) do
|
||||
%i[project group].map { |thing| [thing, :"#{thing}_id"] }
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:parent) { build_stubbed(factory) }
|
||||
|
||||
it 'assignes the parent parameter, and batching_find_alls the finder' do
|
||||
expect(subject.find_all).to contain_exactly(:x, :y, :z)
|
||||
expect(params).to include(param_name => parent)
|
||||
end
|
||||
end
|
||||
|
||||
context 'the parent is of an unexpected type' do
|
||||
let(:parent) { build(:merge_request) }
|
||||
|
||||
it 'raises an error if we pass an unexpected parent' do
|
||||
expect { subject.find_all }.to raise_error(/Unexpected parent/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#batching_find_all' do
|
||||
context 'the finder params are anything other than [iids]' do
|
||||
let(:finder) { double(:finder, params: params, execute: [:foo]) }
|
||||
let(:parent) { build_stubbed(:project) }
|
||||
|
||||
it 'batching_find_alls the finder, setting the correct parent parameter' do
|
||||
expect(subject.batching_find_all).to eq([:foo])
|
||||
expect(params[:project_id]).to eq(parent)
|
||||
end
|
||||
|
||||
it 'allows a post-process block' do
|
||||
expect(subject.batching_find_all(&:first)).to eq(:foo)
|
||||
end
|
||||
end
|
||||
|
||||
context 'the finder params are exactly [iids]' do
|
||||
# Dumb finder class, that only implements what we need, and has
|
||||
# predictable query counts.
|
||||
let(:finder_class) do
|
||||
Class.new do
|
||||
attr_reader :current_user, :params
|
||||
|
||||
def initialize(user, args)
|
||||
@current_user = user
|
||||
@params = HashWithIndifferentAccess.new(args.to_h)
|
||||
end
|
||||
|
||||
def execute
|
||||
params[:project_id].issues.where(iid: params[:iids])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'batches requests' do
|
||||
issue_a = create(:issue)
|
||||
issue_b = create(:issue)
|
||||
issue_c = create(:issue, project: issue_a.project)
|
||||
proj_1 = issue_a.project
|
||||
proj_2 = issue_b.project
|
||||
user = create(:user, developer_projects: [proj_1, proj_2])
|
||||
|
||||
finder_a = finder_class.new(user, iids: [issue_a.iid])
|
||||
finder_b = finder_class.new(user, iids: [issue_b.iid])
|
||||
finder_c = finder_class.new(user, iids: [issue_c.iid])
|
||||
|
||||
results = []
|
||||
|
||||
expect do
|
||||
results.concat(described_class.new(proj_1, finder_a).batching_find_all)
|
||||
results.concat(described_class.new(proj_2, finder_b).batching_find_all)
|
||||
results.concat(described_class.new(proj_1, finder_c).batching_find_all)
|
||||
end.not_to exceed_query_limit(0)
|
||||
|
||||
expect do
|
||||
results = results.map(&:sync)
|
||||
end.not_to exceed_query_limit(2)
|
||||
|
||||
expect(results).to contain_exactly(issue_a, issue_b, issue_c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,9 +17,27 @@ RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
|
|||
end
|
||||
|
||||
context 'feature flag enabled by default' do
|
||||
let(:monotonic_time_before) { 42 }
|
||||
let(:monotonic_time_after) { 500 }
|
||||
let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
|
||||
|
||||
it 'enables the analyzer' do
|
||||
expect(subject.analyze?(anything)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns a duration in seconds' do
|
||||
allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2])
|
||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
|
||||
allow(Gitlab::GraphqlLogger).to receive(:info)
|
||||
|
||||
expected_duration = monotonic_time_duration
|
||||
memo = subject.initial_value(spy('query'))
|
||||
|
||||
subject.final_value(memo)
|
||||
|
||||
expect(memo).to have_key(:duration_s)
|
||||
expect(memo[:duration_s]).to eq(expected_duration)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -228,6 +228,7 @@ ci_pipelines:
|
|||
- latest_builds
|
||||
- daily_report_results
|
||||
- latest_builds_report_results
|
||||
- messages
|
||||
ci_refs:
|
||||
- project
|
||||
- ci_pipelines
|
||||
|
@ -349,6 +350,7 @@ project:
|
|||
- youtrack_service
|
||||
- custom_issue_tracker_service
|
||||
- bugzilla_service
|
||||
- gitlab_issue_tracker_service
|
||||
- external_wiki_service
|
||||
- mock_ci_service
|
||||
- mock_deployment_service
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::PipelineMessage do
|
||||
describe 'validations' do
|
||||
subject { described_class.new(pipeline: pipeline, content: content) }
|
||||
|
||||
let(:pipeline) { create(:ci_pipeline) }
|
||||
|
||||
context 'when message content is longer than the limit' do
|
||||
let(:content) { 'x' * (described_class::MAX_CONTENT_LENGTH + 1) }
|
||||
|
||||
it 'is truncated with ellipsis' do
|
||||
subject.save!
|
||||
|
||||
expect(subject.content).to end_with('x...')
|
||||
expect(subject.content.length).to eq(described_class::MAX_CONTENT_LENGTH)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message is not present' do
|
||||
let(:content) { '' }
|
||||
|
||||
it 'returns an error' do
|
||||
expect(subject.save).to be_falsey
|
||||
expect(subject.errors[:content]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message content is valid' do
|
||||
let(:content) { 'valid message content' }
|
||||
|
||||
it 'is saved with default error severity' do
|
||||
subject.save!
|
||||
|
||||
expect(subject.content).to eq(content)
|
||||
expect(subject.severity).to eq('error')
|
||||
expect(subject).to be_error
|
||||
end
|
||||
|
||||
it 'is persist the defined severity' do
|
||||
subject.severity = :warning
|
||||
|
||||
subject.save!
|
||||
|
||||
expect(subject.content).to eq(content)
|
||||
expect(subject.severity).to eq('warning')
|
||||
expect(subject).to be_warning
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2634,6 +2634,28 @@ RSpec.describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#add_error_message' do
|
||||
let(:pipeline) { build_stubbed(:ci_pipeline) }
|
||||
|
||||
it 'adds a new pipeline error message' do
|
||||
pipeline.add_error_message('The error message')
|
||||
|
||||
expect(pipeline.messages.map(&:content)).to contain_exactly('The error message')
|
||||
end
|
||||
|
||||
context 'when feature flag ci_store_pipeline_messages is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_store_pipeline_messages: false)
|
||||
end
|
||||
|
||||
it ' does not add pipeline error message' do
|
||||
pipeline.add_error_message('The error message')
|
||||
|
||||
expect(pipeline.messages).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_yaml_errors?' do
|
||||
context 'when yaml_errors is set' do
|
||||
before do
|
||||
|
@ -3198,8 +3220,8 @@ RSpec.describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#error_messages' do
|
||||
subject { pipeline.error_messages }
|
||||
describe '#full_error_messages' do
|
||||
subject { pipeline.full_error_messages }
|
||||
|
||||
before do
|
||||
pipeline.valid?
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabIssueTrackerService do
|
||||
describe "Associations" do
|
||||
it { is_expected.to belong_to :project }
|
||||
it { is_expected.to have_one :service_hook }
|
||||
end
|
||||
|
||||
describe 'Validations' do
|
||||
context 'when service is active' do
|
||||
subject { described_class.new(project: create(:project), active: true) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:issues_url) }
|
||||
it_behaves_like 'issue tracker service URL attribute', :issues_url
|
||||
end
|
||||
|
||||
context 'when service is inactive' do
|
||||
subject { described_class.new(project: create(:project), active: false) }
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:issues_url) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'project and issue urls' do
|
||||
let(:project) { create(:project) }
|
||||
let(:service) { project.create_gitlab_issue_tracker_service(active: true) }
|
||||
|
||||
context 'with absolute urls' do
|
||||
before do
|
||||
allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root")
|
||||
end
|
||||
|
||||
it 'gives the correct path' do
|
||||
expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues")
|
||||
expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues/new")
|
||||
expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues/432")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with relative urls' do
|
||||
before do
|
||||
allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root")
|
||||
end
|
||||
|
||||
it 'gives the correct path' do
|
||||
expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.full_path}/-/issues")
|
||||
expect(service.new_issue_path).to eq("/gitlab/root/#{project.full_path}/-/issues/new")
|
||||
expect(service.issue_path(432)).to eq("/gitlab/root/#{project.full_path}/-/issues/432")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -61,6 +61,7 @@ RSpec.describe Project do
|
|||
it { is_expected.to have_one(:youtrack_service) }
|
||||
it { is_expected.to have_one(:custom_issue_tracker_service) }
|
||||
it { is_expected.to have_one(:bugzilla_service) }
|
||||
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
|
||||
it { is_expected.to have_one(:external_wiki_service) }
|
||||
it { is_expected.to have_one(:project_feature) }
|
||||
it { is_expected.to have_one(:project_repository) }
|
||||
|
|
|
@ -508,7 +508,7 @@ RSpec.describe Service do
|
|||
|
||||
describe 'initialize service with no properties' do
|
||||
let(:service) do
|
||||
BugzillaService.create(
|
||||
GitlabIssueTrackerService.create(
|
||||
project: create(:project),
|
||||
project_url: 'http://gitlab.example.com'
|
||||
)
|
||||
|
|
|
@ -154,7 +154,7 @@ RSpec.describe GroupPolicy do
|
|||
context 'admin' do
|
||||
let(:current_user) { admin }
|
||||
|
||||
it do
|
||||
specify do
|
||||
expect_allowed(*read_group_permissions)
|
||||
expect_allowed(*guest_permissions)
|
||||
expect_allowed(*reporter_permissions)
|
||||
|
@ -162,6 +162,10 @@ RSpec.describe GroupPolicy do
|
|||
expect_allowed(*maintainer_permissions)
|
||||
expect_allowed(*owner_permissions)
|
||||
end
|
||||
|
||||
context 'with admin mode', :enable_admin_mode do
|
||||
specify { expect_allowed(*admin_permissions) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'private nested group use the highest access level from the group and inherited permissions' do
|
||||
|
|
|
@ -30,7 +30,7 @@ RSpec.describe ProjectPolicy do
|
|||
admin_issue admin_label admin_list read_commit_status read_build
|
||||
read_container_image read_pipeline read_environment read_deployment
|
||||
read_merge_request download_wiki_code read_sentry_issue read_metrics_dashboard_annotation
|
||||
metrics_dashboard
|
||||
metrics_dashboard read_confidential_issues
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -254,7 +254,10 @@ RSpec.describe API::Projects do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.first).to include 'statistics'
|
||||
|
||||
statistics = json_response.first['statistics']
|
||||
expect(statistics).to be_present
|
||||
expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
|
||||
end
|
||||
|
||||
it "does not include license by default" do
|
||||
|
|
|
@ -194,6 +194,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
|
||||
expect(head_pipeline).to be_persisted
|
||||
expect(head_pipeline.yaml_errors).to be_present
|
||||
expect(head_pipeline.messages).to be_present
|
||||
expect(merge_request.reload.head_pipeline).to eq head_pipeline
|
||||
end
|
||||
end
|
||||
|
@ -1695,6 +1696,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
expect(pipeline).to be_persisted
|
||||
expect(pipeline.builds).to be_empty
|
||||
expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'")
|
||||
expect(pipeline.messages.pluck(:content)).to contain_exactly("test_a: needs 'build_a'")
|
||||
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
|
||||
end
|
||||
end
|
||||
|
@ -1706,6 +1708,7 @@ RSpec.describe Ci::CreatePipelineService do
|
|||
expect(pipeline).not_to be_persisted
|
||||
expect(pipeline.builds).to be_empty
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
expect(pipeline.messages).not_to be_empty
|
||||
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,7 @@ RSpec.shared_context 'GroupPolicy context' do
|
|||
:update_default_branch_protection
|
||||
].compact
|
||||
end
|
||||
let(:admin_permissions) { %i[read_confidential_issues] }
|
||||
|
||||
before_all do
|
||||
group.add_guest(guest)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
|
||||
subject { mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: issuable.iid) }
|
||||
include GraphqlHelpers
|
||||
|
||||
let(:parent_path) { parent.full_path }
|
||||
let(:iid) { issuable.iid }
|
||||
|
||||
subject(:result) { mutation.resolve_issuable(type: type, parent_path: parent_path, iid: iid) }
|
||||
|
||||
context 'when user has access' do
|
||||
before do
|
||||
|
@ -9,37 +14,23 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
|
|||
end
|
||||
|
||||
it 'resolves issuable by iid' do
|
||||
result = type == :merge_request ? subject.sync : subject
|
||||
expect(result).to eq(issuable)
|
||||
end
|
||||
|
||||
it 'uses the correct Resolver to resolve issuable' do
|
||||
resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
|
||||
resolve_method = type == :epic ? :resolve_group : :resolve_project
|
||||
resolved_parent = mutation.send(resolve_method, full_path: parent.full_path)
|
||||
context 'the IID does not refer to a valid issuable' do
|
||||
let(:iid) { '100' }
|
||||
|
||||
allow(mutation).to receive(resolve_method)
|
||||
.with(full_path: parent.full_path)
|
||||
.and_return(resolved_parent)
|
||||
|
||||
expect(resolver_class.single).to receive(:new)
|
||||
.with(object: resolved_parent, context: context, field: nil)
|
||||
.and_call_original
|
||||
|
||||
subject
|
||||
it 'returns nil' do
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns nil if issuable is not found' do
|
||||
result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100")
|
||||
result = result.respond_to?(:sync) ? result.sync : result
|
||||
context 'the parent path is not present' do
|
||||
let(:parent_path) { '' }
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil if parent path is not present' do
|
||||
result = mutation.resolve_issuable(type: type, parent_path: "", iid: issuable.iid)
|
||||
|
||||
expect(result).to be_nil
|
||||
it 'returns nil' do
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'update namespace limit policy' do
|
||||
describe 'update_subscription_limit' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:policy) { :update_subscription_limit }
|
||||
|
||||
where(:role, :is_com, :allowed) do
|
||||
:user | true | false
|
||||
:owner | true | false
|
||||
:admin | true | true
|
||||
:user | false | false
|
||||
:owner | false | false
|
||||
:admin | false | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:current_user) { build_stubbed(role) }
|
||||
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(is_com)
|
||||
end
|
||||
|
||||
context 'when admin mode enabled', :enable_admin_mode do
|
||||
it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
|
||||
end
|
||||
|
||||
context 'when admin mode disabled' do
|
||||
it { is_expected.to be_disallowed(policy) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue