Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-14 12:10:13 +00:00
parent 0e0ec3ddd5
commit b82c4935ec
107 changed files with 932 additions and 1250 deletions

View File

@ -1588,6 +1588,7 @@ Gitlab/NamespacedClass:
- 'app/models/list_user_preference.rb'
- 'app/models/member.rb'
- 'app/models/members/group_member.rb'
- 'app/models/members/last_group_owner_assigner.rb'
- 'app/models/members/project_member.rb'
- 'app/models/members_preloader.rb'
- 'app/models/merge_request.rb'

View File

@ -323,7 +323,7 @@ export function isAbsolute(url) {
* @param {String} url
*/
export function isRootRelative(url) {
return /^\//.test(url);
return /^\/(?!\/)/.test(url);
}
/**

View File

@ -0,0 +1,41 @@
<script>
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
export default {
name: 'FileSha',
components: {
DetailsRow,
ClipboardButton,
},
props: {
sha: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
},
i18n: {
copyButtonTitle: s__('PackageRegistry|Copy SHA'),
},
};
</script>
<template>
<details-row dashed>
<div class="gl-px-4">
{{ title }}:
{{ sha }}
<clipboard-button
:text="sha"
:title="$options.i18n.copyButtonTitle"
category="tertiary"
size="small"
/>
</div>
</details-row>
</template>

View File

@ -1,8 +1,9 @@
<script>
import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui';
import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
import { last } from 'lodash';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
import FileSha from '~/packages/details/components/file_sha.vue';
import Tracking from '~/tracking';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@ -15,8 +16,10 @@ export default {
GlIcon,
GlDropdown,
GlDropdownItem,
GlButton,
FileIcon,
TimeAgoTooltip,
FileSha,
},
mixins: [Tracking.mixin()],
props: {
@ -76,6 +79,9 @@ export default {
formatSize(size) {
return numberToHumanSize(size);
},
hasDetails(item) {
return item.file_sha256 || item.file_md5 || item.file_sha1;
},
},
i18n: {
deleteFile: __('Delete file'),
@ -91,7 +97,15 @@ export default {
:items="filesTableRows"
:tbody-tr-attr="{ 'data-testid': 'file-row' }"
>
<template #cell(name)="{ item }">
<template #cell(name)="{ item, toggleDetails, detailsShowing }">
<gl-button
v-if="hasDetails(item)"
:icon="detailsShowing ? 'angle-up' : 'angle-down'"
:aria-label="detailsShowing ? __('Collapse') : __('Expand')"
category="tertiary"
size="small"
@click="toggleDetails"
/>
<gl-link
:href="item.download_path"
class="gl-text-gray-500"
@ -131,6 +145,21 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
</template>
<template #row-details="{ item }">
<div
class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100"
>
<file-sha
v-if="item.file_sha256"
data-testid="sha-256"
title="SHA-256"
:sha="item.file_sha256"
/>
<file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" />
<file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" />
</div>
</template>
</gl-table>
</div>
</template>

View File

@ -38,6 +38,10 @@ export default {
type: String,
required: true,
},
restrictedVisibilityLevels: {
type: Array,
required: true,
},
},
};
</script>
@ -66,6 +70,7 @@ export default {
:project-path="projectPath"
:project-description="projectDescription"
:project-visibility="projectVisibility"
:restricted-visibility-levels="restrictedVisibilityLevels"
/>
</div>
</div>

View File

@ -95,6 +95,10 @@ export default {
type: String,
required: true,
},
restrictedVisibilityLevels: {
type: Array,
required: true,
},
},
data() {
const form = {
@ -111,7 +115,7 @@ export default {
required: false,
skipValidation: true,
}),
visibility: initFormField({ value: this.projectVisibility }),
visibility: initFormField({ value: this.getInitialVisibilityValue() }),
},
};
return {
@ -134,13 +138,28 @@ export default {
visibilityLevelCap() {
return Math.min(this.projectVisibilityLevel, this.namespaceVisibilityLevel);
},
restrictedVisibilityLevelsSet() {
return new Set(this.restrictedVisibilityLevels);
},
allowedVisibilityLevels() {
return Object.entries(VISIBILITY_LEVEL).reduce((levels, [levelName, levelValue]) => {
if (levelValue <= this.visibilityLevelCap) {
levels.push(levelName);
}
return levels;
}, []);
const allowedLevels = Object.entries(VISIBILITY_LEVEL).reduce(
(levels, [levelName, levelValue]) => {
if (
!this.restrictedVisibilityLevelsSet.has(levelValue) &&
levelValue <= this.visibilityLevelCap
) {
levels.push(levelName);
}
return levels;
},
[],
);
if (!allowedLevels.length) {
return [PRIVATE_VISIBILITY];
}
return allowedLevels;
},
visibilityLevels() {
return [
@ -173,7 +192,8 @@ export default {
watch: {
// eslint-disable-next-line func-names
'form.fields.namespace.value': function () {
this.form.fields.visibility.value = PRIVATE_VISIBILITY;
this.form.fields.visibility.value =
this.restrictedVisibilityLevels.length !== 0 ? null : PRIVATE_VISIBILITY;
},
// eslint-disable-next-line func-names
'form.fields.name.value': function (newVal) {
@ -191,6 +211,9 @@ export default {
isVisibilityLevelDisabled(visibility) {
return !this.allowedVisibilityLevels.includes(visibility);
},
getInitialVisibilityValue() {
return this.restrictedVisibilityLevels.length !== 0 ? null : this.projectVisibility;
},
async onSubmit() {
this.form.showValidation = true;
@ -340,6 +363,7 @@ export default {
v-model="form.fields.visibility.value"
data-testid="fork-visibility-radio-group"
name="visibility"
:aria-label="__('visibility')"
required
>
<gl-form-radio

View File

@ -16,6 +16,7 @@ if (gon.features.forkProjectForm) {
projectPath,
projectDescription,
projectVisibility,
restrictedVisibilityLevels,
} = mountElement.dataset;
// eslint-disable-next-line no-new
@ -38,6 +39,7 @@ if (gon.features.forkProjectForm) {
projectPath,
projectDescription,
projectVisibility,
restrictedVisibilityLevels: JSON.parse(restrictedVisibilityLevels),
},
});
},

View File

@ -8,6 +8,12 @@ import refQuery from './queries/ref.query.graphql';
const fetchpromises = {};
const resolvers = {};
let maxOffset;
let nextOffset;
let currentPath;
function setNextOffset(offset) {
nextOffset = offset || null;
}
export function resolveCommit(commits, path, { resolve, entry }) {
const commit = commits.find(
@ -24,7 +30,17 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset
maxOffset = _maxOffset;
}
if (Number(offset) > maxOffset) {
if (!currentPath || currentPath !== path) {
// ensures the nextOffset is reset if the user changed directories
setNextOffset(null);
}
currentPath = path;
const offsetNumber = Number(offset);
if (!nextOffset && offsetNumber > maxOffset) {
setNextOffset(offsetNumber - 25); // ensures commit data is fetched for newly added rows that need data from the previous request (requests are made in batches of 25).
return Promise.resolve();
}
@ -47,7 +63,7 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset
path.replace(/^\//, ''),
)}`,
{
params: { format: 'json', offset },
params: { format: 'json', offset: nextOffset || offset },
},
)
.then(({ data: newData, headers }) => {
@ -66,10 +82,12 @@ export function fetchLogsTree(client, path, offset, resolver = null, _maxOffset
delete fetchpromises[path];
if (headerLogsOffset) {
setNextOffset(null);
fetchLogsTree(client, path, headerLogsOffset);
} else {
delete resolvers[path];
maxOffset = null;
setNextOffset(null);
}
});

View File

@ -8,7 +8,8 @@ export default {
props: {
icon: {
type: String,
required: true,
required: false,
default: null,
},
padding: {
type: String,
@ -34,7 +35,7 @@ export default {
class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all"
:class="[padding, borderClass]"
>
<gl-icon :name="icon" class="gl-mr-4" />
<gl-icon v-if="icon" :name="icon" class="gl-mr-4" />
<span>
<slot></slot>
</span>

View File

@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def cluster_application_params
params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :modsecurity_enabled, :modsecurity_mode, :host, :port, :protocol, :waf_log_enabled, :cilium_log_enabled)
params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :host, :port, :protocol, :cilium_log_enabled)
end
def cluster_application_destroy_params

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class Projects::Analytics::CycleAnalytics::SummaryController < Projects::ApplicationController
include CycleAnalyticsParams
respond_to :json
feature_category :planning_analytics
before_action :authorize_read_cycle_analytics!
def show
render json: project_level.summary
end
private
def project_level
@project_level ||= Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(allowed_params))
end
def allowed_params
params.permit(:created_after, :created_before)
end
end
Projects::Analytics::CycleAnalytics::SummaryController.prepend_mod_with('Projects::Analytics::CycleAnalytics::SummaryController')

View File

@ -53,7 +53,7 @@ module Projects
end
def cycle_analytics
@cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(cycle_analytics_project_params))
@cycle_analytics ||= ::Analytics::CycleAnalytics::ProjectLevel.new(project: project, options: options(cycle_analytics_project_params))
end
end
end

View File

@ -14,7 +14,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
feature_category :planning_analytics
def show
@cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params))
@cycle_analytics = Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(cycle_analytics_project_params))
respond_to do |format|
format.html do

View File

@ -176,11 +176,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def retry
if Gitlab::Ci::Features.background_pipeline_retry_endpoint?(@project)
::Ci::RetryPipelineWorker.perform_async(pipeline.id, current_user.id) # rubocop:disable CodeReuse/Worker
else
pipeline.retry_failed(current_user)
end
::Ci::RetryPipelineWorker.perform_async(pipeline.id, current_user.id) # rubocop:disable CodeReuse/Worker
respond_to do |format|
format.html do

View File

@ -4,6 +4,9 @@ module Ci
class RunnersFinder < UnionFinder
include Gitlab::Allowable
ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
DEFAULT_SORT = 'created_at_desc'
def initialize(current_user:, group: nil, params:)
@params = params
@group = group
@ -24,11 +27,7 @@ module Ci
end
def sort_key
if @params[:sort] == 'contacted_asc'
'contacted_asc'
else
'created_date'
end
ALLOWED_SORTS.include?(@params[:sort]) ? @params[:sort] : DEFAULT_SORT
end
private

View File

@ -17,6 +17,10 @@ module Mutations
required: false,
description: 'Indicates if the latest artifact should be kept for this project.'
argument :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Indicates CI job tokens generated in this project have restricted access to resources.'
field :ci_cd_settings,
Types::Ci::CiCdSettingType,
null: false,

View File

@ -16,6 +16,9 @@ module Types
field :keep_latest_artifact, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Whether to keep the latest builds artifacts.',
method: :keep_latest_artifacts_available?
field :job_token_scope_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates CI job tokens generated in this project have restricted access to resources.',
method: :job_token_scope_enabled?
field :project, Types::ProjectType, null: true,
description: 'Project the CI/CD settings belong to.'
end

View File

@ -7,7 +7,9 @@ module Types
description 'Values for sorting runners'
value 'CONTACTED_ASC', 'Ordered by contacted_at in ascending order.', value: :contacted_asc
value 'CREATED_DESC', 'Ordered by created_date in descending order.', value: :created_date
value 'CONTACTED_DESC', 'Ordered by contacted_at in descending order.', value: :contacted_desc
value 'CREATED_ASC', 'Ordered by created_at in ascending order.', value: :created_at_asc
value 'CREATED_DESC', 'Ordered by created_at in descending order.', value: :created_at_desc
end
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Analytics
module CycleAnalytics
class ProjectLevel
attr_reader :project, :options
def initialize(project:, options:)
@project = project
@options = options.merge(project: project)
end
def summary
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
options: options,
current_user: options[:current_user]).data
end
def permissions(user:)
Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
end
def stats
@stats ||= default_stage_names.map do |stage_name|
self[stage_name].as_json
end
end
def [](stage_name)
::CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options)
end
private
def build_stage(stage_name)
stage_params = stage_params_by_name(stage_name).merge(project: project)
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
end
def stage_params_by_name(name)
Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name)
end
def default_stage_names
Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names
end
end
end
end

View File

@ -134,6 +134,8 @@ module Ci
end
scope :order_contacted_at_asc, -> { order(contacted_at: :asc) }
scope :order_contacted_at_desc, -> { order(contacted_at: :desc) }
scope :order_created_at_asc, -> { order(created_at: :asc) }
scope :order_created_at_desc, -> { order(created_at: :desc) }
scope :with_tags, -> { preload(:tags) }
@ -190,8 +192,13 @@ module Ci
end
def self.order_by(order)
if order == 'contacted_asc'
case order
when 'contacted_asc'
order_contacted_at_asc
when 'contacted_desc'
order_contacted_at_desc
when 'created_at_asc'
order_created_at_asc
else
order_created_at_desc
end

View File

@ -12,11 +12,13 @@ module Clusters
include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
include IgnorableColumns
default_value_for :version, VERSION
default_value_for :port, 514
default_value_for :protocol, :tcp
default_value_for :waf_log_enabled, false
ignore_column :waf_log_enabled, remove_with: '14.2', remove_after: '2021-07-22'
enum protocol: { tcp: 0, udp: 1 }
@ -48,9 +50,7 @@ module Clusters
private
def has_at_least_one_log_enabled?
if !waf_log_enabled && !cilium_log_enabled
errors.add(:base, _("At least one logging option is required to be enabled"))
end
errors.add(:base, _("At least one logging option is required to be enabled")) unless cilium_log_enabled
end
def content_values
@ -113,7 +113,6 @@ module Clusters
def path_to_logs
path = []
path << "/var/log/containers/*#{Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log" if waf_log_enabled
path << "/var/log/containers/*#{CILIUM_CONTAINER_NAME}*.log" if cilium_log_enabled
path.join(',')
end

View File

@ -7,10 +7,6 @@ module Clusters
class Ingress < ApplicationRecord
VERSION = '1.40.2'
INGRESS_CONTAINER_NAME = 'nginx-ingress-controller'
MODSECURITY_LOG_CONTAINER_NAME = 'modsecurity-log'
MODSECURITY_MODE_LOGGING = "DetectionOnly"
MODSECURITY_MODE_BLOCKING = "On"
MODSECURITY_OWASP_RULES_FILE = "/etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf"
self.table_name = 'clusters_applications_ingress'
@ -20,22 +16,18 @@ module Clusters
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
include UsageStatistics
include IgnorableColumns
default_value_for :ingress_type, :nginx
default_value_for :modsecurity_enabled, true
default_value_for :version, VERSION
default_value_for :modsecurity_mode, :logging
ignore_column :modsecurity_enabled, remove_with: '14.2', remove_after: '2021-07-22'
ignore_column :modsecurity_mode, remove_with: '14.2', remove_after: '2021-07-22'
enum ingress_type: {
nginx: 1
}
enum modsecurity_mode: { logging: 0, blocking: 1 }
scope :modsecurity_not_installed, -> { where(modsecurity_enabled: nil) }
scope :modsecurity_enabled, -> { where(modsecurity_enabled: true) }
scope :modsecurity_disabled, -> { where(modsecurity_enabled: false) }
FETCH_IP_ADDRESS_DELAY = 30.seconds
state_machine :status do
@ -92,96 +84,13 @@ module Clusters
private
def specification
return {} unless modsecurity_enabled
{
"controller" => {
"config" => {
"enable-modsecurity" => "true",
"enable-owasp-modsecurity-crs" => "false",
"modsecurity-snippet" => modsecurity_snippet_content,
"modsecurity.conf" => modsecurity_config_content
},
"extraContainers" => [
{
"name" => MODSECURITY_LOG_CONTAINER_NAME,
"image" => "busybox",
"args" => [
"/bin/sh",
"-c",
"tail -F /var/log/modsec/audit.log"
],
"volumeMounts" => [
{
"name" => "modsecurity-log-volume",
"mountPath" => "/var/log/modsec",
"readOnly" => true
}
],
"livenessProbe" => {
"exec" => {
"command" => [
"ls",
"/var/log/modsec/audit.log"
]
}
}
}
],
"extraVolumeMounts" => [
{
"name" => "modsecurity-template-volume",
"mountPath" => "/etc/nginx/modsecurity/modsecurity.conf",
"subPath" => "modsecurity.conf"
},
{
"name" => "modsecurity-log-volume",
"mountPath" => "/var/log/modsec"
}
],
"extraVolumes" => [
{
"name" => "modsecurity-template-volume",
"configMap" => {
"name" => "ingress-#{INGRESS_CONTAINER_NAME}",
"items" => [
{
"key" => "modsecurity.conf",
"path" => "modsecurity.conf"
}
]
}
},
{
"name" => "modsecurity-log-volume",
"emptyDir" => {}
}
]
}
}
end
def modsecurity_config_content
File.read(modsecurity_config_file_path)
end
def modsecurity_config_file_path
Rails.root.join('vendor', 'ingress', 'modsecurity.conf')
end
def content_values
YAML.load_file(chart_values_file).deep_merge!(specification)
YAML.load_file(chart_values_file)
end
def application_jupyter_installed?
cluster.application_jupyter&.installed?
end
def modsecurity_snippet_content
sec_rule_engine = logging? ? MODSECURITY_MODE_LOGGING : MODSECURITY_MODE_BLOCKING
"SecRuleEngine #{sec_rule_engine}\nInclude #{MODSECURITY_OWASP_RULES_FILE}"
end
end
end
end

View File

@ -138,7 +138,6 @@ module Clusters
scope :gcp_installed, -> { gcp_provided.joins(:provider_gcp).merge(Clusters::Providers::Gcp.with_status(:created)) }
scope :aws_installed, -> { aws_provided.joins(:provider_aws).merge(Clusters::Providers::Aws.with_status(:created)) }
scope :with_enabled_modsecurity, -> { joins(:application_ingress).merge(::Clusters::Applications::Ingress.modsecurity_enabled) }
scope :with_available_elasticstack, -> { joins(:application_elastic_stack).merge(::Clusters::Applications::ElasticStack.available) }
scope :with_available_cilium, -> { joins(:application_cilium).merge(::Clusters::Applications::Cilium.available) }
scope :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct }

View File

@ -1,48 +0,0 @@
# frozen_string_literal: true
module CycleAnalytics
class ProjectLevel
attr_reader :project, :options
def initialize(project, options:)
@project = project
@options = options.merge(project: project)
end
def summary
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
from: options[:from],
to: options[:to],
current_user: options[:current_user]).data
end
def permissions(user:)
Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
end
def stats
@stats ||= default_stage_names.map do |stage_name|
self[stage_name].as_json
end
end
def [](stage_name)
CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options)
end
private
def build_stage(stage_name)
stage_params = stage_params_by_name(stage_name).merge(project: project)
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
end
def stage_params_by_name(name)
Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name)
end
def default_stage_names
Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names
end
end
end

View File

@ -1,46 +1,44 @@
# frozen_string_literal: true
module Members
class LastGroupOwnerAssigner
def initialize(group, members)
@group = group
@members = members
end
class LastGroupOwnerAssigner
def initialize(group, members)
@group = group
@members = members
end
def execute
@last_blocked_owner = no_owners_in_heirarchy? && group.single_blocked_owner?
@group_single_owner = owners.size == 1
def execute
@last_blocked_owner = no_owners_in_heirarchy? && group.single_blocked_owner?
@group_single_owner = owners.size == 1
members.each { |member| set_last_owner(member) }
end
members.each { |member| set_last_owner(member) }
end
private
private
attr_reader :group, :members, :last_blocked_owner, :group_single_owner
attr_reader :group, :members, :last_blocked_owner, :group_single_owner
def no_owners_in_heirarchy?
owners.empty?
end
def no_owners_in_heirarchy?
owners.empty?
end
def set_last_owner(member)
member.last_owner = member.id.in?(owner_ids) && group_single_owner
member.last_blocked_owner = member.id.in?(blocked_owner_ids) && last_blocked_owner
end
def set_last_owner(member)
member.last_owner = member.id.in?(owner_ids) && group_single_owner
member.last_blocked_owner = member.id.in?(blocked_owner_ids) && last_blocked_owner
end
def owner_ids
@owner_ids ||= owners.where(id: member_ids).ids
end
def owner_ids
@owner_ids ||= owners.where(id: member_ids).ids
end
def blocked_owner_ids
@blocked_owner_ids ||= group.blocked_owners.where(id: member_ids).ids
end
def blocked_owner_ids
@blocked_owner_ids ||= group.blocked_owners.where(id: member_ids).ids
end
def member_ids
@members_ids ||= members.pluck(:id)
end
def member_ids
@members_ids ||= members.pluck(:id)
end
def owners
@owners ||= group.members_with_parents.owners.load
end
def owners
@owners ||= group.members_with_parents.owners.load
end
end

View File

@ -10,15 +10,12 @@ class ClusterApplicationEntity < Grape::Entity
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
expose :stack, if: -> (e, _) { e.respond_to?(:stack) }
expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall
expose :available_domains, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:available_domains) }
expose :pages_domain, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:pages_domain) }
expose :modsecurity_mode, if: -> (e, _) { e.respond_to?(:modsecurity_mode) }
expose :host, if: -> (e, _) { e.respond_to?(:host) }
expose :port, if: -> (e, _) { e.respond_to?(:port) }
expose :protocol, if: -> (e, _) { e.respond_to?(:protocol) }
expose :waf_log_enabled, if: -> (e, _) { e.respond_to?(:waf_log_enabled) }
expose :cilium_log_enabled, if: -> (e, _) { e.respond_to?(:cilium_log_enabled) }
end

View File

@ -4,7 +4,7 @@ class MemberSerializer < BaseSerializer
entity MemberEntity
def represent(members, opts = {})
Members::LastGroupOwnerAssigner.new(opts[:group], members).execute unless opts[:source].is_a?(Project)
LastGroupOwnerAssigner.new(opts[:group], members).execute unless opts[:source].is_a?(Project)
super(members, opts)
end

View File

@ -29,14 +29,6 @@ module Clusters
application.stack = params[:stack]
end
if application.has_attribute?(:modsecurity_enabled)
application.modsecurity_enabled = params[:modsecurity_enabled] || false
end
if application.has_attribute?(:modsecurity_mode)
application.modsecurity_mode = params[:modsecurity_mode] || 0
end
apply_fluentd_related_attributes(application)
if application.respond_to?(:oauth_application)

View File

@ -3,13 +3,11 @@
%fieldset
.form-group
= f.label :diff_max_patch_bytes, 'Maximum diff patch size (Bytes)', class: 'label-light'
= f.label :diff_max_patch_bytes, 'Maximum diff patch size in bytes', class: 'label-light'
= f.number_field :diff_max_patch_bytes, class: 'form-control gl-form-input'
%span.form-text.text-muted
Diff files surpassing this limit will be presented as 'too large'
and won't be expandable.
Collapse diffs larger than this size, and show a 'too large' message instead.
= link_to sprite_icon('question-o'),
help_page_path('user/admin_area/diff_limits',
anchor: 'maximum-diff-patch-size')
help_page_path('user/admin_area/diff_limits')
= f.submit _('Save changes'), class: 'gl-button btn btn-confirm'

View File

@ -31,7 +31,7 @@
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Diff content limits')
= _('Set size limits for displaying diffs in the browser.')
.settings-content
= render 'diff_limits'

View File

@ -10,7 +10,8 @@
project_name: @project.name,
project_path: @project.path,
project_description: @project.description,
project_visibility: @project.visibility } }
project_visibility: @project.visibility,
restricted_visibility_levels: Gitlab::CurrentSettings.restricted_visibility_levels.to_json } }
- else
.row.gl-mt-3
.col-lg-3

View File

@ -37,10 +37,12 @@
data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
- if source_level < target_level
.gl-alert.gl-alert-warning.gl-mt-4
= sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= visibilityMismatchString
%br
= _('Review the target project before submitting to avoid exposing %{source} changes.') % { source: source_visibility }
.gl-alert.gl-alert-warning.gl-alert-not-dismissible.gl-max-content.gl-mt-4
.gl-alert-container
.gl-alert-content{ role: 'alert' }
= sprite_icon('warning', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= visibilityMismatchString
%br
= _('Review the target project before submitting to avoid exposing %{source} changes.') % { source: source_visibility }
%hr

View File

@ -15,8 +15,6 @@ module SshKeys
# rubocop: disable CodeReuse/ActiveRecord
def perform
return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml)
order = Gitlab::Pagination::Keyset::Order.build([
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'expires_at_utc',

View File

@ -12,8 +12,6 @@ module SshKeys
idempotent!
def perform
return unless ::Feature.enabled?(:ssh_key_expiration_email_notification, default_enabled: :yaml)
# rubocop:disable CodeReuse/ActiveRecord
User.with_ssh_key_expiring_soon.find_each(batch_size: 10_000) do |user|
with_context(user: user) do

View File

@ -1,8 +0,0 @@
---
name: background_pipeline_retry_endpoint
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61270
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330915
milestone: '13.12'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: ci_dynamic_child_pipeline
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23790
rollout_issue_url:
milestone: '12.9'
type: development
group: group::pipeline execution
default_enabled: true

View File

@ -1,8 +1,8 @@
---
name: remove_release_notes_from_tags_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63392
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290311
name: sidekiq_load_balancing_rotate_up_to_date_replica
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63413/
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333153
milestone: '14.0'
type: development
group: group::release
group: group::memory
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: ssh_key_expiration_email_notification
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56888
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326386
milestone: '13.11'
type: development
group: group::compliance
default_enabled: true

View File

@ -1,16 +0,0 @@
---
key_path: usage_activity_by_stage_monthly.secure.dependency_scanning_scans
description: ''
product_section: ''
product_stage: ''
product_group: ''
product_category: ''
value_type: number
status: data_available
time_frame: 28d
data_source:
distribution:
- ce
tier:
- free
skip_validation: true

View File

@ -1,17 +0,0 @@
---
key_path: counts.license_management_jobs
description: Name on the GitLab license
product_section: growth
product_stage: growth
product_group: group::product intelligence
product_category: collection
value_type: number
status: data_available
time_frame: none
data_source: database
distribution:
- ce
tier:
- premium
- ultimate
skip_validation: true

View File

@ -273,6 +273,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :value_streams, only: [:index] do
resources :stages, only: [:index]
end
resource :summary, controller: :summary, only: :show
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddRunnersCreatedAtIndex < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_concurrent_index :ci_runners, [:created_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_created_at_and_id_desc'
add_concurrent_index :ci_runners, [:created_at, :id], order: { created_at: :desc, id: :desc }, name: 'index_ci_runners_on_created_at_desc_and_id_desc'
end
def down
remove_concurrent_index :ci_runners, [:created_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_created_at_and_id_desc'
remove_concurrent_index :ci_runners, [:created_at, :id], order: { created_at: :desc, id: :desc }, name: 'index_ci_runners_on_created_at_desc_and_id_desc'
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ReplaceRunnersContactedAtIndex < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
OLD_INDEX_NAME = 'index_ci_runners_on_contacted_at'
def up
add_concurrent_index :ci_runners, [:contacted_at, :id], order: { id: :desc }, name: 'index_ci_runners_on_contacted_at_and_id_desc', using: 'btree'
add_concurrent_index :ci_runners, [:contacted_at, :id], order: { contacted_at: :desc, id: :desc }, name: 'index_ci_runners_on_contacted_at_desc_and_id_desc', using: 'btree'
remove_concurrent_index_by_name :ci_runners, OLD_INDEX_NAME
end
def down
remove_concurrent_index_by_name :ci_runners, 'index_ci_runners_on_contacted_at_and_id_desc'
remove_concurrent_index_by_name :ci_runners, 'index_ci_runners_on_contacted_at_desc_and_id_desc'
add_concurrent_index :ci_runners, :contacted_at, name: OLD_INDEX_NAME, using: 'btree'
end
end

View File

@ -0,0 +1 @@
fc500e4dd555a6baad91ad3c9fb8a2f8541e1613dd64afdbdd28b19447a28caf

View File

@ -0,0 +1 @@
4dcf6277439e8abe52534540100fa621fedcecb3eaf71ad5685ac0230cd2e5bb

View File

@ -22904,7 +22904,13 @@ CREATE INDEX index_ci_runner_projects_on_project_id ON ci_runner_projects USING
CREATE INDEX index_ci_runner_projects_on_runner_id ON ci_runner_projects USING btree (runner_id);
CREATE INDEX index_ci_runners_on_contacted_at ON ci_runners USING btree (contacted_at);
CREATE INDEX index_ci_runners_on_contacted_at_and_id_desc ON ci_runners USING btree (contacted_at, id DESC);
CREATE INDEX index_ci_runners_on_contacted_at_desc_and_id_desc ON ci_runners USING btree (contacted_at DESC, id DESC);
CREATE INDEX index_ci_runners_on_created_at_and_id_desc ON ci_runners USING btree (created_at, id DESC);
CREATE INDEX index_ci_runners_on_created_at_desc_and_id_desc ON ci_runners USING btree (created_at DESC, id DESC);
CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops);

View File

@ -63,8 +63,11 @@ need to restart GitLab to apply a new file hook.
If a file hook executes with non-zero exit code or GitLab fails to execute it, a
message is logged to:
- `gitlab-rails/plugin.log` in an Omnibus installation.
- `log/plugin.log` in a source installation.
- `gitlab-rails/file_hook.log` in an Omnibus installation.
- `log/file_hook.log` in a source installation.
NOTE:
Before 14.0 release, the file name was `plugin.log`
## Creating file hooks

View File

@ -766,6 +766,7 @@ Input type: `CiCdSettingsUpdateInput`
| ---- | ---- | ----------- |
| <a id="mutationcicdsettingsupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcicdsettingsupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Full Path of the project the settings belong to. |
| <a id="mutationcicdsettingsupdatejobtokenscopeenabled"></a>`jobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI job tokens generated in this project have restricted access to resources. |
| <a id="mutationcicdsettingsupdatekeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Indicates if the latest artifact should be kept for this project. |
| <a id="mutationcicdsettingsupdatemergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Indicates if merge pipelines are enabled for the project. |
| <a id="mutationcicdsettingsupdatemergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Indicates if merge trains are enabled for the project. |
@ -11853,6 +11854,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectcicdsettingjobtokenscopeenabled"></a>`jobTokenScopeEnabled` | [`Boolean`](#boolean) | Indicates CI job tokens generated in this project have restricted access to resources. |
| <a id="projectcicdsettingkeeplatestartifact"></a>`keepLatestArtifact` | [`Boolean`](#boolean) | Whether to keep the latest builds artifacts. |
| <a id="projectcicdsettingmergepipelinesenabled"></a>`mergePipelinesEnabled` | [`Boolean`](#boolean) | Whether merge pipelines are enabled. |
| <a id="projectcicdsettingmergetrainsenabled"></a>`mergeTrainsEnabled` | [`Boolean`](#boolean) | Whether merge trains are enabled. |
@ -13841,7 +13843,9 @@ Values for sorting runners.
| Value | Description |
| ----- | ----------- |
| <a id="cirunnersortcontacted_asc"></a>`CONTACTED_ASC` | Ordered by contacted_at in ascending order. |
| <a id="cirunnersortcreated_desc"></a>`CREATED_DESC` | Ordered by created_date in descending order. |
| <a id="cirunnersortcontacted_desc"></a>`CONTACTED_DESC` | Ordered by contacted_at in descending order. |
| <a id="cirunnersortcreated_asc"></a>`CREATED_ASC` | Ordered by created_at in ascending order. |
| <a id="cirunnersortcreated_desc"></a>`CREATED_DESC` | Ordered by created_at in descending order. |
### `CiRunnerStatus`

View File

@ -123,7 +123,6 @@ Parameters:
| `tag_name` | string | yes | The name of a tag |
| `ref` | string | yes | Create tag using commit SHA, another tag name, or branch name |
| `message` | string | no | Creates annotated tag |
| `release_description` | string | no | This parameter is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/tags?tag_name=test&ref=master"
@ -149,10 +148,7 @@ Example response:
"committer_email": "jack@example.com",
"committed_date": "2012-05-28T04:42:42-07:00"
},
"release": {
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
},
"release": null,
"name": "v1.0.0",
"target": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": null,
@ -182,82 +178,3 @@ Parameters:
| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `tag_name` | string | yes | The name of a tag |
## Create a new release
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766)
for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311)
in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead.
Add release notes to the existing Git tag. If there
already exists a release for the given tag, status code `409` is returned.
```plaintext
POST /projects/:id/repository/tags/:tag_name/release
```
Parameters:
| Attribute | Type | Required | Description |
| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `tag_name` | string | yes | The name of a tag |
Request body:
- `description` (required) - Release notes with Markdown support
```json
{
"description": "Amazing release. Wow"
}
```
Response:
```json
{
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
```
## Update a release
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766)
for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311)
in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead.
Updates the release notes of a given release.
```plaintext
PUT /projects/:id/repository/tags/:tag_name/release
```
Parameters:
| Attribute | Type | Required | Description |
| ---------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `tag_name` | string | yes | The name of a tag |
Request body:
- `description` (required) - Release notes with Markdown support
```json
{
"description": "Amazing release. Wow"
}
```
Response:
```json
{
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
```

View File

@ -20,7 +20,7 @@ the feature categories in the [Secure](https://about.gitlab.com/stages-devops-li
- `AppSec::ContainerScanning`: Container Scanning code.
- `AppSec::Dast`: DAST code.
- `AppSec::DependencyScanning`: Dependency Scanning code.
- `AppSec::Fuzzing::Api`: API Fuzzing code.
- `AppSec::Fuzzing::API`: API Fuzzing code.
- `AppSec::Fuzzing::Coverage`: Coverage Fuzzing code.
- `AppSec::Fuzzing`: Shared fuzzing code.
- `AppSec::LicenseCompliance`: License Compliance code.

View File

@ -3288,15 +3288,15 @@ Tiers: `free`
### `counts.license_management_jobs`
Name on the GitLab license
Count of License Scanning jobs run
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/license/20210204124854_license_management_jobs.yml)
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210204124854_license_management_jobs.yml)
Group: `group::product intelligence`
Group: `group::composition analysis`
Status: `data_available`
Tiers: `premium`, `ultimate`
Tiers: `ultimate`
### `counts.licenses_list_views`
@ -17320,7 +17320,7 @@ Tiers:
### `usage_activity_by_stage.secure.dependency_scanning_scans`
Counts dependency scanning jobs
Total number of users running Dependency Scanning Scans
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175220_dependency_scanning_scans.yml)
@ -17416,7 +17416,7 @@ Tiers: `free`
### `usage_activity_by_stage.secure.user_dependency_scanning_jobs`
no idea, Count of Dependency Scanning jobs run, it implies user but AFAIK we don't track per user
Total number of users running Dependency Scanning jobs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175216_user_dependency_scanning_jobs.yml)
@ -17428,9 +17428,9 @@ Tiers: `ultimate`
### `usage_activity_by_stage.secure.user_license_management_jobs`
no idea, Count of License Scanning jobs run, it implies user but AFAIK we don't track per user
Total number of users running License Scanning jobs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/license/20210216175218_user_license_management_jobs.yml)
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175218_user_license_management_jobs.yml)
Group: `group::composition analysis`
@ -19324,7 +19324,7 @@ Tiers: `free`
### `usage_activity_by_stage_monthly.secure.dependency_scanning_pipeline`
no idea, what is this when did it get added? guess pipelines containing a DS job
Count of pipelines with successful Dependency Scanning jobs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175226_dependency_scanning_pipeline.yml)
@ -19336,15 +19336,15 @@ Tiers: `ultimate`
### `usage_activity_by_stage_monthly.secure.dependency_scanning_scans`
Missing description
Monthly number of users running Dependency Scanning Scans
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml)
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216183828_dependency_scanning_scans.yml)
Group: ``
Group: `group::composition analysis`
Status: `data_available`
Tiers: `free`
Tiers: `ultimate`
### `usage_activity_by_stage_monthly.secure.sast_pipeline`
@ -19456,7 +19456,7 @@ Tiers: `free`
### `usage_activity_by_stage_monthly.secure.user_dependency_scanning_jobs`
no idea, Count of Dependency Scanning jobs run, it implies user and monthly, but AFAIK we don't track per user
Monthly number of users creating Dependency Scanning jobs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175222_user_dependency_scanning_jobs.yml)
@ -19468,9 +19468,9 @@ Tiers: `ultimate`
### `usage_activity_by_stage_monthly.secure.user_license_management_jobs`
no idea, Count of License Scanning jobs run, it implies user and monthly, but AFAIK we don't track per user
Monthly number of users running License Scanning jobs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/license/20210216175224_user_license_management_jobs.yml)
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175224_user_license_management_jobs.yml)
Group: `group::composition analysis`

View File

@ -176,6 +176,7 @@ Users are notified of the following events:
| Event | Sent to | Settings level |
|------------------------------|---------------------|------------------------------|
| New SSH key added | User | Security email, always sent. |
| SSH key has expired | User | Security email, always sent. |
| New email added | User | Security email, always sent. |
| Email changed | User | Security email, always sent. |
| Password changed | User | Security email, always sent when user changes their own password |

View File

@ -23,7 +23,7 @@ module API
expose :issue_type,
as: :type,
format_with: :upcase,
documentation: { type: "String", desc: "One of #{Issue.issue_types.keys.map(&:upcase)}" }
documentation: { type: "String", desc: "One of #{::Issue.issue_types.keys.map(&:upcase)}" }
expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first

View File

@ -51,14 +51,12 @@ module API
end
desc 'Create a new repository tag' do
detail 'This optional release_description parameter was deprecated in GitLab 11.7.'
success Entities::Tag
end
params do
requires :tag_name, type: String, desc: 'The name of the tag'
requires :ref, type: String, desc: 'The commit sha or branch name'
optional :message, type: String, desc: 'Specifying a message creates an annotated tag'
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)'
end
post ':id/repository/tags', :release_orchestration do
deprecate_release_notes unless params[:release_description].blank?
@ -69,19 +67,6 @@ module API
.execute(params[:tag_name], params[:ref], params[:message])
if result[:status] == :success
# Release creation with Tags API was deprecated in GitLab 11.7
if params[:release_description].present?
release_create_params = {
tag: params[:tag_name],
name: params[:tag_name], # Name can be specified in new API
description: params[:release_description]
}
::Releases::CreateService
.new(user_project, current_user, release_create_params)
.execute
end
present result[:tag],
with: Entities::Tag,
project: user_project
@ -111,82 +96,6 @@ module API
end
end
end
desc 'Add a release note to a tag' do
detail 'This feature was deprecated in GitLab 11.7.'
success Entities::TagRelease
end
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
requires :description, type: String, desc: 'Release notes with markdown support'
end
post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
deprecate_release_notes
authorize_create_release!
##
# Legacy API does not support tag auto creation.
not_found!('Tag') unless user_project.repository.find_tag(params[:tag])
release_create_params = {
tag: params[:tag],
name: params[:tag], # Name can be specified in new API
description: params[:description]
}
result = ::Releases::CreateService
.new(user_project, current_user, release_create_params)
.execute
if result[:status] == :success
present result[:release], with: Entities::TagRelease
else
render_api_error!(result[:message], result[:http_status])
end
end
desc "Update a tag's release note" do
detail 'This feature was deprecated in GitLab 11.7.'
success Entities::TagRelease
end
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
requires :description, type: String, desc: 'Release notes with markdown support'
end
put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
deprecate_release_notes
authorize_update_release!
result = ::Releases::UpdateService
.new(user_project, current_user, declared_params(include_missing: false))
.execute
if result[:status] == :success
present result[:release], with: Entities::TagRelease
else
render_api_error!(result[:message], result[:http_status])
end
end
end
helpers do
def authorize_create_release!
authorize! :create_release, user_project
end
def authorize_update_release!
authorize! :update_release, release
end
def release
@release ||= user_project.releases.find_by_tag(params[:tag])
end
def deprecate_release_notes
return unless Feature.enabled?(:remove_release_notes_from_tags_api, user_project, default_enabled: :yaml)
render_api_error!("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311", 400)
end
end
end
end

View File

@ -28,11 +28,6 @@ module Gitlab
end
end
def matching?
super &&
Feature.enabled?(:ci_dynamic_child_pipeline, project, default_enabled: true)
end
private
def project

View File

@ -41,10 +41,6 @@ module Gitlab
def self.gldropdown_tags_enabled?
::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml)
end
def self.background_pipeline_retry_endpoint?(project)
::Feature.enabled?(:background_pipeline_retry_endpoint, project)
end
end
end
end

View File

@ -16,7 +16,7 @@ build-job: # This job runs in the build stage, which runs first.
stage: build
script:
- echo "Compiling the code..."
- echo "Compile complete.
- echo "Compile complete."
unit-test-job: # This job runs in the test stage.
stage: test # It only starts when the job in the build stage completes successfully.

View File

@ -3,10 +3,9 @@
module Gitlab
module CycleAnalytics
class StageSummary
def initialize(project, from:, to: nil, current_user:)
def initialize(project, options:, current_user:)
@project = project
@from = from
@to = to
@options = options
@current_user = current_user
end
@ -20,15 +19,15 @@ module Gitlab
private
def issue_stats
serialize(Summary::Issue.new(project: @project, from: @from, to: @to, current_user: @current_user))
serialize(Summary::Issue.new(project: @project, options: @options, current_user: @current_user))
end
def commit_stats
serialize(Summary::Commit.new(project: @project, from: @from, to: @to))
serialize(Summary::Commit.new(project: @project, options: @options))
end
def deployments_summary
@deployments_summary ||= Summary::Deploy.new(project: @project, from: @from, to: @to)
@deployments_summary ||= Summary::Deploy.new(project: @project, options: @options)
end
def deploy_stats
@ -39,8 +38,7 @@ module Gitlab
serialize(
Summary::DeploymentFrequency.new(
deployments: deployments_summary.value.raw_value,
from: @from,
to: @to),
options: @options),
with_unit: true
)
end
@ -50,8 +48,7 @@ module Gitlab
end
def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent(
summary_object, with_unit: with_unit)
AnalyticsSummarySerializer.new.represent(summary_object, with_unit: with_unit)
end
end
end

View File

@ -4,10 +4,9 @@ module Gitlab
module CycleAnalytics
module Summary
class Base
def initialize(project:, from:, to: nil)
def initialize(project:, options:)
@project = project
@from = from
@to = to
@options = options
end
def title

View File

@ -21,7 +21,7 @@ module Gitlab
def commits_count
return unless ref
@commits_count ||= gitaly_commit_client.commit_count(ref, after: @from, before: @to)
@commits_count ||= gitaly_commit_client.commit_count(ref, after: @options[:from], before: @options[:to])
end
def gitaly_commit_client

View File

@ -16,7 +16,7 @@ module Gitlab
def deployments_count
DeploymentsFinder
.new(project: @project, finished_after: @from, finished_before: @to, status: :success, order_by: :finished_at)
.new(project: @project, finished_after: @options[:from], finished_before: @options[:to], status: :success, order_by: :finished_at)
.execute
.count
end

View File

@ -6,10 +6,10 @@ module Gitlab
class DeploymentFrequency < Base
include SummaryHelper
def initialize(deployments:, from:, to: nil, project: nil)
def initialize(deployments:, options:, project: nil)
@deployments = deployments
super(project: project, from: from, to: to)
super(project: project, options: options)
end
def title
@ -17,7 +17,7 @@ module Gitlab
end
def value
@value ||= frequency(@deployments, @from, @to || Time.now)
@value ||= frequency(@deployments, @options[:from], @options[:to] || Time.current)
end
def unit

View File

@ -4,10 +4,9 @@ module Gitlab
module CycleAnalytics
module Summary
class Issue < Base
def initialize(project:, from:, to: nil, current_user:)
def initialize(project:, options:, current_user:)
@project = project
@from = from
@to = to
@options = options
@current_user = current_user
end
@ -23,10 +22,18 @@ module Gitlab
def issues_count
IssuesFinder
.new(@current_user, project_id: @project.id, created_after: @from, created_before: @to)
.new(@current_user, finder_params)
.execute
.count
end
def finder_params
@options.dup.tap do |hash|
hash[:created_after] = hash.delete(:from)
hash[:created_before] = hash.delete(:to)
hash[:project_id] = @project.id
end
end
end
end
end

View File

@ -176,6 +176,20 @@ module Gitlab
true
end
# Returns true if there was at least one host that has caught up with the given transaction.
# Similar to `#select_caught_up_hosts`, picks a random host, to rotate replicas we use.
# Unlike `#select_caught_up_hosts`, does not iterate over all hosts if finds any.
def select_up_to_date_host(location)
all_hosts = @host_list.hosts.shuffle
host = all_hosts.find { |host| host.caught_up?(location) }
return false unless host
RequestStore[CACHE_KEY] = host
true
end
def set_consistent_hosts_for_request(hosts)
RequestStore[VALID_HOSTS_CACHE_KEY] = hosts
end

View File

@ -59,7 +59,11 @@ module Gitlab
end
def replica_caught_up?(location)
load_balancer.host.caught_up?(location)
if Feature.enabled?(:sidekiq_load_balancing_rotate_up_to_date_replica)
load_balancer.select_up_to_date_host(location)
else
load_balancer.host.caught_up?(location)
end
end
end
end

View File

@ -5,7 +5,7 @@ module Gitlab
module MigrationHelpers
include Migrations::BackgroundMigrationHelpers
include DynamicModelHelpers
include Migrations::RenameTableHelpers
include RenameTableHelpers
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
MAX_IDENTIFIER_NAME_LENGTH = 63

View File

@ -3,7 +3,7 @@
module Gitlab
class FileHookLogger < Gitlab::Logger
def self.file_name_noext
'plugin'
'file_hook'
end
end
end

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Graphql
StandardGraphqlError = Class.new(StandardError)
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
# rubocop:disable Cop/CustomErrorClass
module Gitlab
module Graphql
class StandardGraphqlError < StandardError
end
end
end

View File

@ -11402,9 +11402,6 @@ msgstr ""
msgid "Didn't receive unlock instructions?"
msgstr ""
msgid "Diff content limits"
msgstr ""
msgid "Diff limits"
msgstr ""
@ -23339,6 +23336,9 @@ msgstr ""
msgid "PackageRegistry|Copy Pip command"
msgstr ""
msgid "PackageRegistry|Copy SHA"
msgstr ""
msgid "PackageRegistry|Copy add Gradle Groovy DSL repository command"
msgstr ""
@ -29645,6 +29645,9 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
msgid "Set size limits for displaying diffs in the browser."
msgstr ""
msgid "Set target branch"
msgstr ""
@ -39511,6 +39514,9 @@ msgstr ""
msgid "view the source"
msgstr ""
msgid "visibility"
msgstr ""
msgid "vulnerability"
msgid_plural "vulnerabilities"
msgstr[0] ""

View File

@ -18,6 +18,8 @@ class GitalyTestBuild
# If we have the binaries from the cache, we can skip building them again
if File.exist?(tmp_tests_gitaly_bin_dir)
GitalySetup::LOGGER.debug "Gitaly binary already built. Skip building...\n"
# We still need to install the gems in that case
install_gitaly_gems
else
abort 'gitaly build failed' unless build_gitaly
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:params) { { namespace_id: project.namespace.to_param, project_id: project.to_param, created_after: '2010-01-01', created_before: '2010-01-02' } }
before do
sign_in(user)
end
describe 'GET "show"' do
subject { get :show, params: params }
it 'succeeds' do
project.add_reporter(user)
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/summary')
end
context 'when analytics_disabled features are disabled' do
it 'renders 404' do
project.add_reporter(user)
project.project_feature.update!(analytics_access_level: ProjectFeature::DISABLED)
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user is not part of the project' do
it 'renders 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

View File

@ -878,19 +878,6 @@ RSpec.describe Projects::PipelinesController do
expect(::Ci::RetryPipelineWorker).to have_received(:perform_async).with(pipeline.id, user.id)
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(background_pipeline_retry_endpoint: false)
end
it 'retries the pipeline without returning any content' do
post_retry
expect(response).to have_gitlab_http_status(:no_content)
expect(build.reload).to be_retried
end
end
context 'when builds are disabled' do
let(:feature) { ProjectFeature::DISABLED }

View File

@ -96,26 +96,7 @@ FactoryBot.define do
end
factory :clusters_applications_ingress, class: 'Clusters::Applications::Ingress' do
modsecurity_enabled { false }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :modsecurity_blocking do
modsecurity_enabled { true }
modsecurity_mode { :blocking }
end
trait :modsecurity_logging do
modsecurity_enabled { true }
modsecurity_mode { :logging }
end
trait :modsecurity_disabled do
modsecurity_enabled { false }
end
trait :modsecurity_not_installed do
modsecurity_enabled { nil }
end
end
factory :clusters_applications_cert_manager, class: 'Clusters::Applications::CertManager' do
@ -153,7 +134,6 @@ FactoryBot.define do
factory :clusters_applications_fluentd, class: 'Clusters::Applications::Fluentd' do
host { 'example.com' }
waf_log_enabled { true }
cilium_log_enabled { true }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end

View File

@ -47,6 +47,7 @@ FactoryBot.define do
import_last_error { nil }
forward_deployment_enabled { nil }
restrict_user_defined_variables { nil }
ci_job_token_scope_enabled { nil }
end
before(:create) do |project, evaluator|
@ -88,6 +89,7 @@ FactoryBot.define do
project.merge_trains_enabled = evaluator.merge_trains_enabled unless evaluator.merge_trains_enabled.nil?
project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil?
project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil?
project.ci_job_token_scope_enabled = evaluator.ci_job_token_scope_enabled unless evaluator.ci_job_token_scope_enabled.nil?
if evaluator.import_status
import_state = project.import_state || project.build_import_state

View File

@ -51,23 +51,55 @@ RSpec.describe Ci::RunnersFinder do
end
context 'sort' do
context 'without sort param' do
it 'sorts by created_at' do
runner1 = create :ci_runner, created_at: '2018-07-12 07:00'
runner2 = create :ci_runner, created_at: '2018-07-12 08:00'
runner3 = create :ci_runner, created_at: '2018-07-12 09:00'
let_it_be(:runner1) { create :ci_runner, created_at: '2018-07-12 07:00', contacted_at: 1.minute.ago }
let_it_be(:runner2) { create :ci_runner, created_at: '2018-07-12 08:00', contacted_at: 3.minutes.ago }
let_it_be(:runner3) { create :ci_runner, created_at: '2018-07-12 09:00', contacted_at: 2.minutes.ago }
expect(described_class.new(current_user: admin, params: {}).execute).to eq [runner3, runner2, runner1]
subject do
described_class.new(current_user: admin, params: params).execute
end
shared_examples 'sorts by created_at descending' do
it 'sorts by created_at descending' do
is_expected.to eq [runner3, runner2, runner1]
end
end
context 'with sort param' do
it 'sorts by specified attribute' do
runner1 = create :ci_runner, contacted_at: 1.minute.ago
runner2 = create :ci_runner, contacted_at: 3.minutes.ago
runner3 = create :ci_runner, contacted_at: 2.minutes.ago
context 'without sort param' do
let(:params) { {} }
expect(described_class.new(current_user: admin, params: { sort: 'contacted_asc' }).execute).to eq [runner2, runner3, runner1]
it_behaves_like 'sorts by created_at descending'
end
%w(created_date created_at_desc).each do |sort|
context "with sort param equal to #{sort}" do
let(:params) { { sort: sort } }
it_behaves_like 'sorts by created_at descending'
end
end
context 'with sort param equal to created_at_asc' do
let(:params) { { sort: 'created_at_asc' } }
it 'sorts by created_at ascending' do
is_expected.to eq [runner1, runner2, runner3]
end
end
context 'with sort param equal to contacted_asc' do
let(:params) { { sort: 'contacted_asc' } }
it 'sorts by contacted_at ascending' do
is_expected.to eq [runner2, runner3, runner1]
end
end
context 'with sort param equal to contacted_desc' do
let(:params) { { sort: 'contacted_desc' } }
it 'sorts by contacted_at descending' do
is_expected.to eq [runner1, runner3, runner2]
end
end
end
@ -246,8 +278,8 @@ RSpec.describe Ci::RunnersFinder do
subject { described_class.new(current_user: user, group: group, params: params).sort_key }
context 'no params' do
it 'returns created_date' do
expect(subject).to eq('created_date')
it 'returns created_at_desc' do
expect(subject).to eq('created_at_desc')
end
end

View File

@ -0,0 +1,21 @@
{
"type": "array",
"items": {
"minItems": 2,
"maxItems": 3,
"type": "object",
"required": ["value", "title"],
"properties": {
"value": {
"type": "string"
},
"title": {
"type": "string"
},
"unit": {
"type": "string"
}
},
"additionalProperties": false
}
}

View File

@ -42,7 +42,6 @@
"host": {"type": ["string", "null"]},
"port": {"type": ["integer", "514"]},
"protocol": {"type": ["integer", "0"]},
"waf_log_enabled": {"type": ["boolean", "true"]},
"cilium_log_enabled": {"type": ["boolean", "true"]},
"update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" },

View File

@ -471,6 +471,7 @@ describe('URL utility', () => {
${'notaurl'} | ${false}
${'../relative_url'} | ${false}
${'<a></a>'} | ${false}
${'//other-host.test'} | ${false}
`('returns $valid for $url', ({ url, valid }) => {
expect(urlUtils.isRootRelative(url)).toBe(valid);
});

View File

@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FileSha renders 1`] = `
<div
class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all gl-py-2 gl-border-b-solid gl-border-gray-100 gl-border-b-1"
>
<!---->
<span>
<div
class="gl-px-4"
>
bar:
foo
<gl-button-stub
aria-label="Copy this value"
buttontextclasses=""
category="tertiary"
data-clipboard-text="foo"
icon="copy-to-clipboard"
size="small"
title="Copy SHA"
variant="default"
/>
</div>
</span>
</div>
`;

View File

@ -0,0 +1,33 @@
import { shallowMount } from '@vue/test-utils';
import FileSha from '~/packages/details/components/file_sha.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
describe('FileSha', () => {
let wrapper;
const defaultProps = { sha: 'foo', title: 'bar' };
function createComponent() {
wrapper = shallowMount(FileSha, {
propsData: {
...defaultProps,
},
stubs: {
ClipboardButton,
DetailsRow,
},
});
}
afterEach(() => {
wrapper.destroy();
});
it('renders', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
});

View File

@ -1,5 +1,6 @@
import { GlDropdown } from '@gitlab/ui';
import { GlDropdown, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue/';
import stubChildren from 'helpers/stub_children';
import component from '~/packages/details/components/package_files.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@ -20,6 +21,8 @@ describe('Package Files', () => {
const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton);
const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`);
const createComponent = ({ packageFiles = npmFiles, canDelete = true } = {}) => {
wrapper = mount(component, {
@ -181,4 +184,76 @@ describe('Package Files', () => {
});
});
});
describe('additional details', () => {
describe('details toggle button', () => {
it('exists', () => {
createComponent();
expect(findFirstToggleDetailsButton().exists()).toBe(true);
});
it('is hidden when no details is present', () => {
const [{ ...noShaFile }] = npmFiles;
noShaFile.file_sha256 = null;
noShaFile.file_md5 = null;
noShaFile.file_sha1 = null;
createComponent({ packageFiles: [noShaFile] });
expect(findFirstToggleDetailsButton().exists()).toBe(false);
});
it('toggles the details row', async () => {
createComponent();
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
findFirstToggleDetailsButton().vm.$emit('click');
await nextTick();
expect(findFirstRowShaComponent('sha-256').exists()).toBe(true);
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-up');
findFirstToggleDetailsButton().vm.$emit('click');
await nextTick();
expect(findFirstRowShaComponent('sha-256').exists()).toBe(false);
expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
});
});
describe('file shas', () => {
const showShaFiles = () => {
findFirstToggleDetailsButton().vm.$emit('click');
return nextTick();
};
it.each`
selector | title | sha
${'sha-256'} | ${'SHA-256'} | ${'file_sha256'}
${'md5'} | ${'MD5'} | ${'file_md5'}
${'sha-1'} | ${'SHA-1'} | ${'file_sha1'}
`('has a $title row', async ({ selector, title, sha }) => {
createComponent();
await showShaFiles();
expect(findFirstRowShaComponent(selector).props()).toMatchObject({
title,
sha,
});
});
it('does not display a row when the data is missing', async () => {
const [{ ...missingMd5 }] = npmFiles;
missingMd5.file_md5 = null;
createComponent({ packageFiles: [missingMd5] });
await showShaFiles();
expect(findFirstRowShaComponent('md5').exists()).toBe(false);
});
});
});
});

View File

@ -79,6 +79,9 @@ export const npmFiles = [
pipelines: [
{ id: 1, project: { commit_url: 'http://foo.bar' }, git_commit_message: 'foo bar baz?' },
],
file_sha256: 'file_sha256',
file_md5: 'file_md5',
file_sha1: 'file_sha1',
},
];

View File

@ -13,6 +13,7 @@ describe('App component', () => {
projectPath: 'project-name',
projectDescription: 'some project description',
projectVisibility: 'private',
restrictedVisibilityLevels: [],
};
const createComponent = (props = {}) => {

View File

@ -1,4 +1,5 @@
import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio, GlFormSelect } from '@gitlab/ui';
import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { getByRole, getAllByRole } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
@ -44,6 +45,7 @@ describe('ForkForm component', () => {
projectPath: 'project-name',
projectDescription: 'some project description',
projectVisibility: 'private',
restrictedVisibilityLevels: [],
};
const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => {
@ -68,6 +70,7 @@ describe('ForkForm component', () => {
stubs: {
GlFormInputGroup,
GlFormInput,
GlFormRadioGroup,
GlFormRadio,
},
});
@ -89,7 +92,7 @@ describe('ForkForm component', () => {
axiosMock.restore();
});
const findFormSelect = () => wrapper.find(GlFormSelect);
const findFormSelectOptions = () => wrapper.find('select[name="namespace"]').findAll('option');
const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]');
const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]');
const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]');
@ -230,7 +233,7 @@ describe('ForkForm component', () => {
expect(wrapper.findAll(GlFormRadio)).toHaveLength(3);
});
it('resets the visibility to default "private" when the namespace is changed', async () => {
describe('when the namespace is changed', () => {
const namespaces = [
{
visibility: 'private',
@ -243,42 +246,114 @@ describe('ForkForm component', () => {
},
];
mockGetRequest();
createComponent(
{
projectVisibility: 'public',
},
{
namespaces,
},
);
beforeEach(() => {
mockGetRequest();
});
expect(wrapper.vm.form.fields.visibility.value).toBe('public');
findFormSelect().vm.$emit('input', namespaces[1]);
it('resets the visibility to default "private"', async () => {
createFullComponent({ projectVisibility: 'public' }, { namespaces });
await wrapper.vm.$nextTick();
expect(wrapper.vm.form.fields.visibility.value).toBe('public');
await findFormSelectOptions().at(1).setSelected();
expect(wrapper.vm.form.fields.visibility.value).toBe('private');
await wrapper.vm.$nextTick();
expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true);
});
it('sets the visibility to be null when restrictedVisibilityLevels is set', async () => {
createFullComponent({ restrictedVisibilityLevels: [10] }, { namespaces });
await findFormSelectOptions().at(1).setSelected();
await wrapper.vm.$nextTick();
const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i });
const visibilityRadios = getAllByRole(container, 'radio');
expect(visibilityRadios.filter((e) => e.checked)).toHaveLength(0);
});
});
it.each`
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'}
${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'}
${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'}
${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'}
${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'}
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined}
project | restrictedVisibilityLevels
${'private'} | ${[]}
${'internal'} | ${[]}
${'public'} | ${[]}
${'private'} | ${[0]}
${'private'} | ${[10]}
${'private'} | ${[20]}
${'private'} | ${[0, 10]}
${'private'} | ${[0, 20]}
${'private'} | ${[10, 20]}
${'private'} | ${[0, 10, 20]}
${'internal'} | ${[0]}
${'internal'} | ${[10]}
${'internal'} | ${[20]}
${'internal'} | ${[0, 10]}
${'internal'} | ${[0, 20]}
${'internal'} | ${[10, 20]}
${'internal'} | ${[0, 10, 20]}
${'public'} | ${[0]}
${'public'} | ${[10]}
${'public'} | ${[0, 10]}
${'public'} | ${[0, 20]}
${'public'} | ${[10, 20]}
${'public'} | ${[0, 10, 20]}
`('checks the correct radio button', async ({ project, restrictedVisibilityLevels }) => {
mockGetRequest();
createFullComponent({
projectVisibility: project,
restrictedVisibilityLevels,
});
if (restrictedVisibilityLevels.length === 0) {
expect(wrapper.find('[name="visibility"]:checked').attributes('value')).toBe(project);
} else {
expect(wrapper.find('[name="visibility"]:checked').exists()).toBe(false);
}
});
it.each`
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled | restrictedVisibilityLevels
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined} | ${[]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0]}
${'internal'} | ${'internal'} | ${'true'} | ${undefined} | ${'true'} | ${[0]}
${'public'} | ${'public'} | ${'true'} | ${undefined} | ${undefined} | ${[0]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10]}
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10]}
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${undefined} | ${[10]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[20]}
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[20]}
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[20]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
`(
'sets appropriate radio button disabled state',
async ({ project, namespace, privateIsDisabled, internalIsDisabled, publicIsDisabled }) => {
async ({
project,
namespace,
privateIsDisabled,
internalIsDisabled,
publicIsDisabled,
restrictedVisibilityLevels,
}) => {
mockGetRequest();
createComponent(
{
projectVisibility: project,
restrictedVisibilityLevels,
},
{
form: { fields: { namespace: { value: { visibility: namespace } } } },

View File

@ -69,6 +69,16 @@ describe('fetchLogsTree', () => {
mock.restore();
});
it('persists the offset for a given page if offset is larger than maximum offset', async () => {
await fetchLogsTree(client, 'path', '1000', resolver, 900).then(() => {});
await fetchLogsTree(client, 'path', '1100', resolver, 1200).then(() => {
expect(axios.get).toHaveBeenCalledWith('/gitlab-org/gitlab-foss/-/refs/main/logs_tree/path', {
params: { format: 'json', offset: 975 },
});
});
});
it('does not call axios get if offset is larger than the maximum offset', () =>
fetchLogsTree(client, '', '1000', resolver, 900).then(() => {
expect(axios.get).not.toHaveBeenCalled();

View File

@ -50,13 +50,29 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) }
end
context "set to :created_date" do
context "set to :contacted_desc" do
let(:args) do
{ sort: :created_date }
{ sort: :contacted_desc }
end
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) }
end
context "set to :created_at_desc" do
let(:args) do
{ sort: :created_at_desc }
end
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) }
end
context "set to :created_at_asc" do
let(:args) do
{ sort: :created_at_asc }
end
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) }
end
end
context 'when type is filtered' do

View File

@ -4,14 +4,15 @@ require 'spec_helper'
RSpec.describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) }
let(:options) { { from: 1.day.ago, current_user: user } }
let(:options) { { from: 1.day.ago } }
let(:args) { { options: options, current_user: user } }
let(:user) { create(:user, :admin) }
before do
project.add_maintainer(user)
end
let(:stage_summary) { described_class.new(project, **options).data }
let(:stage_summary) { described_class.new(project, **args).data }
describe "#new_issues" do
subject { stage_summary.first }
@ -117,11 +118,11 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
before do
project.add_guest(guest_user)
options.merge!({ current_user: guest_user })
args.merge!({ current_user: guest_user })
end
it 'does not include commit stats' do
data = described_class.new(project, **options).data
data = described_class.new(project, **args).data
expect(includes_commits?(data)).to be_falsy
end

View File

@ -459,7 +459,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
expect(hosts).to all(receive(:caught_up?).with(location).and_return(false))
end
it 'returns true and has does not set the valid hosts' do
it 'returns false and does not set the valid hosts' do
expect(subject).to be false
expect(valid_host_list).to be_nil
end
@ -487,4 +487,36 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
end
end
describe '#select_caught_up_hosts' do
let(:location) { 'AB/12345'}
let(:hosts) { lb.host_list.hosts }
let(:set_host) { RequestStore[described_class::CACHE_KEY] }
subject { lb.select_up_to_date_host(location) }
context 'when none of the replicas are caught up' do
before do
expect(hosts).to all(receive(:caught_up?).with(location).and_return(false))
end
it 'returns false and does not update the host thread-local variable' do
expect(subject).to be false
expect(set_host).to be_nil
end
end
context 'when any of the replicas is caught up' do
before do
# `allow` for non-caught up host, because we may not even check it, if will find the caught up one earlier
allow(hosts[0]).to receive(:caught_up?).with(location).and_return(false)
expect(hosts[1]).to receive(:caught_up?).with(location).and_return(true)
end
it 'returns true and sets host thread-local variable' do
expect(subject).to be true
expect(set_host).to eq(hosts[1])
end
end
end
end

View File

@ -126,7 +126,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
context 'when replica is not up to date' do
before do
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :release_host)
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :host, :caught_up?).and_return(false)
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :select_up_to_date_host).and_return(false)
end
around do |example|
@ -157,6 +157,20 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
expect(job[:database_chosen]).to eq('primary')
end
end
context 'replica selection mechanism feature flag rollout' do
before do
stub_feature_flags(sidekiq_load_balancing_rotate_up_to_date_replica: false)
end
it 'uses different implmentation' do
expect(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer, :host, :caught_up?).and_return(false)
expect do
process_job(job)
end.to raise_error(Sidekiq::JobRetry::Skip)
end
end
end
end

View File

@ -394,7 +394,7 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end
shared_context 'LoadBalancing setup' do
let(:development_db_config) { ActiveRecord::Base.configurations.default_hash("development").with_indifferent_access }
let(:development_db_config) { ActiveRecord::Base.configurations.configs_for(env_name: 'development').first.configuration_hash }
let(:hosts) { [development_db_config[:host]] }
let(:model) do
Class.new(ApplicationRecord) do

View File

@ -38,22 +38,6 @@ RSpec.describe Gitlab::Usage::Metrics::NameSuggestion do
end
context 'joined relations' do
context 'counted attribute comes from joined relation' do
it_behaves_like 'name suggestion' do
let(:operation) { :distinct_count }
let(:column) { ::Deployment.arel_table[:environment_id] }
let(:relation) do
::Clusters::Applications::Ingress.modsecurity_enabled.logging
.joins(cluster: :deployments)
.merge(::Clusters::Cluster.enabled)
.merge(Deployment.success)
end
let(:constraints) { /'\(clusters_applications_ingress\.modsecurity_enabled = TRUE AND clusters_applications_ingress\.modsecurity_mode = \d+ AND clusters.enabled = TRUE AND deployments.status = \d+\)'/ }
let(:name_suggestion) { /count_distinct_environment_id_from_<adjective describing\: #{constraints}>_deployments_<with>_<adjective describing\: #{constraints}>_clusters_<having>_<adjective describing\: #{constraints}>_clusters_applications_ingress/ }
end
end
context 'counted attribute comes from source relation' do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)

View File

@ -3,9 +3,8 @@
require 'spec_helper'
RSpec.describe Clusters::Applications::Fluentd do
let(:waf_log_enabled) { true }
let(:cilium_log_enabled) { true }
let(:fluentd) { create(:clusters_applications_fluentd, waf_log_enabled: waf_log_enabled, cilium_log_enabled: cilium_log_enabled) }
let(:fluentd) { create(:clusters_applications_fluentd, cilium_log_enabled: cilium_log_enabled) }
include_examples 'cluster application core specs', :clusters_applications_fluentd
include_examples 'cluster application status specs', :clusters_applications_fluentd
@ -51,13 +50,11 @@ RSpec.describe Clusters::Applications::Fluentd do
end
describe '#values' do
let(:modsecurity_log_path) { "/var/log/containers/*#{Clusters::Applications::Ingress::MODSECURITY_LOG_CONTAINER_NAME}*.log" }
let(:cilium_log_path) { "/var/log/containers/*#{described_class::CILIUM_CONTAINER_NAME}*.log" }
subject { fluentd.values }
context 'with both logs variables set to false' do
let(:waf_log_enabled) { false }
context 'with cilium_log_enabled set to false' do
let(:cilium_log_enabled) { false }
it "raises ActiveRecord::RecordInvalid" do
@ -65,18 +62,8 @@ RSpec.describe Clusters::Applications::Fluentd do
end
end
context 'with both logs variables set to true' do
it { is_expected.to include("#{modsecurity_log_path},#{cilium_log_path}") }
end
context 'with waf_log_enabled set to true' do
let(:cilium_log_enabled) { false }
it { is_expected.to include(modsecurity_log_path) }
end
context 'with cilium_log_enabled set to true' do
let(:waf_log_enabled) { false }
let(:cilium_log_enabled) { true }
it { is_expected.to include(cilium_log_path) }
end

View File

@ -172,94 +172,4 @@ RSpec.describe Clusters::Applications::Ingress do
expect(values).to include('clusterIP')
end
end
describe '#values' do
subject { ingress }
context 'when modsecurity_enabled is enabled' do
before do
allow(subject).to receive(:modsecurity_enabled).and_return(true)
end
it 'includes modsecurity module enablement' do
expect(subject.values).to include("enable-modsecurity: 'true'")
end
it 'includes modsecurity core ruleset enablement set to false' do
expect(subject.values).to include("enable-owasp-modsecurity-crs: 'false'")
end
it 'includes modsecurity snippet with information related to security rules' do
expect(subject.values).to include("SecRuleEngine DetectionOnly")
expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}")
end
context 'when modsecurity_mode is set to :blocking' do
before do
subject.blocking!
end
it 'includes modsecurity snippet with information related to security rules' do
expect(subject.values).to include("SecRuleEngine On")
expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}")
end
end
it 'includes modsecurity.conf content' do
expect(subject.values).to include('modsecurity.conf')
# Includes file content from Ingress#modsecurity_config_content
expect(subject.values).to include('SecAuditLog')
expect(subject.values).to include('extraVolumes')
expect(subject.values).to include('extraVolumeMounts')
end
it 'includes modsecurity sidecar container' do
expect(subject.values).to include('modsecurity-log-volume')
expect(subject.values).to include('extraContainers')
end
it 'executes command to tail modsecurity logs with -F option' do
args = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'args')
expect(args).to eq(['/bin/sh', '-c', 'tail -F /var/log/modsec/audit.log'])
end
it 'includes livenessProbe for modsecurity sidecar container' do
probe_config = YAML.safe_load(subject.values).dig('controller', 'extraContainers', 0, 'livenessProbe')
expect(probe_config).to eq('exec' => { 'command' => ['ls', '/var/log/modsec/audit.log'] })
end
end
context 'when modsecurity_enabled is disabled' do
before do
allow(subject).to receive(:modsecurity_enabled).and_return(false)
end
it 'excludes modsecurity module enablement' do
expect(subject.values).not_to include('enable-modsecurity')
end
it 'excludes modsecurity core ruleset enablement' do
expect(subject.values).not_to include('enable-owasp-modsecurity-crs')
end
it 'excludes modsecurity.conf content' do
expect(subject.values).not_to include('modsecurity.conf')
# Excludes file content from Ingress#modsecurity_config_content
expect(subject.values).not_to include('SecAuditLog')
expect(subject.values).not_to include('extraVolumes')
expect(subject.values).not_to include('extraVolumeMounts')
end
it 'excludes modsecurity sidecar container' do
expect(subject.values).not_to include('modsecurity-log-volume')
expect(subject.values).not_to include('extraContainers')
end
end
end
end

View File

@ -196,28 +196,6 @@ RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
describe '.with_enabled_modsecurity' do
subject { described_class.with_enabled_modsecurity }
let_it_be(:cluster) { create(:cluster) }
context 'cluster has ingress application with enabled modsecurity' do
let!(:application) { create(:clusters_applications_ingress, :installed, :modsecurity_logging, cluster: cluster) }
it { is_expected.to include(cluster) }
end
context 'cluster has ingress application with disabled modsecurity' do
let!(:application) { create(:clusters_applications_ingress, :installed, :modsecurity_disabled, cluster: cluster) }
it { is_expected.not_to include(cluster) }
end
context 'cluster does not have ingress application' do
it { is_expected.not_to include(cluster) }
end
end
describe '.with_available_elasticstack' do
subject { described_class.with_available_elasticstack }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Members::LastGroupOwnerAssigner do
RSpec.describe LastGroupOwnerAssigner do
describe "#execute" do
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:group) { create(:group) }

View File

@ -47,6 +47,7 @@ RSpec.describe 'Getting Ci Cd Setting' do
expect(settings_data['mergePipelinesEnabled']).to eql project.ci_cd_settings.merge_pipelines_enabled?
expect(settings_data['mergeTrainsEnabled']).to eql project.ci_cd_settings.merge_trains_enabled?
expect(settings_data['keepLatestArtifact']).to eql project.keep_latest_artifacts_available?
expect(settings_data['jobTokenScopeEnabled']).to eql project.ci_cd_settings.job_token_scope_enabled?
end
end
end

View File

@ -5,8 +5,16 @@ require 'spec_helper'
RSpec.describe 'CiCdSettingsUpdate' do
include GraphqlHelpers
let_it_be(:project) { create(:project, keep_latest_artifact: true) }
let(:variables) { { full_path: project.full_path, keep_latest_artifact: false } }
let_it_be(:project) { create(:project, keep_latest_artifact: true, ci_job_token_scope_enabled: true) }
let(:variables) do
{
full_path: project.full_path,
keep_latest_artifact: false,
job_token_scope_enabled: false
}
end
let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) }
context 'when unauthorized' do
@ -45,6 +53,26 @@ RSpec.describe 'CiCdSettingsUpdate' do
expect(project.keep_latest_artifact).to eq(false)
end
it 'updates job_token_scope_enabled' do
post_graphql_mutation(mutation, current_user: user)
project.reload
expect(response).to have_gitlab_http_status(:success)
expect(project.ci_job_token_scope_enabled).to eq(false)
end
it 'does not update job_token_scope_enabled if not specified' do
variables.except!(:job_token_scope_enabled)
post_graphql_mutation(mutation, current_user: user)
project.reload
expect(response).to have_gitlab_http_status(:success)
expect(project.ci_job_token_scope_enabled).to eq(true)
end
context 'when bad arguments are provided' do
let(:variables) { { full_path: '', keep_latest_artifact: false } }

View File

@ -358,26 +358,6 @@ RSpec.describe API::Tags do
expect(json_response['message']).to eq('Target foo is invalid')
end
context 'when release_description is passed' do
it 'returns error' do
post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311")
end
it 'creates a new tag with release if feature is enabled' do
stub_feature_flags(remove_release_notes_from_tags_api: false)
post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/tag')
expect(json_response['name']).to eq(tag_name)
expect(json_response['release']['description']).to eq('Wow')
end
end
context 'annotated tag' do
it 'creates a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
@ -449,148 +429,4 @@ RSpec.describe API::Tags do
end
end
end
describe 'POST /projects/:id/repository/tags/:tag_name/release' do
let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" }
let(:description) { 'Awesome release!' }
before do
stub_feature_flags(remove_release_notes_from_tags_api: false)
end
shared_examples_for 'repository new release' do
it 'creates description for existing git tag' do
post api(route, user), params: { description: description }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/release/tag_release')
expect(json_response['tag_name']).to eq(tag_name)
expect(json_response['description']).to eq(description)
end
it 'returns error if feature is removed' do
stub_feature_flags(remove_release_notes_from_tags_api: true)
post api(route, user), params: { description: description }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311")
end
context 'when tag does not exist' do
let(:tag_name) { 'unknown' }
it_behaves_like '404 response' do
let(:request) { post api(route, current_user), params: { description: description } }
let(:message) { '404 Tag Not Found' }
end
end
context 'when repository is disabled' do
include_context 'disabled repository'
it_behaves_like '403 response' do
let(:request) { post api(route, current_user), params: { description: description } }
end
end
end
context 'when authenticated', 'as a maintainer' do
let(:current_user) { user }
it_behaves_like 'repository new release'
context 'requesting with the escaped project full path' do
let(:project_id) { CGI.escape(project.full_path) }
it_behaves_like 'repository new release'
end
context 'on tag with existing release' do
let!(:release) { create(:release, :legacy, project: project, tag: tag_name, description: description) }
it 'returns 409 if there is already a release' do
post api(route, user), params: { description: description }
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']).to eq('Release already exists')
end
end
end
end
describe 'PUT id/repository/tags/:tag_name/release' do
let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/release" }
let(:description) { 'Awesome release!' }
let(:new_description) { 'The best release!' }
before do
stub_feature_flags(remove_release_notes_from_tags_api: false)
end
shared_examples_for 'repository update release' do
context 'on tag with existing release' do
let!(:release) do
create(:release,
:legacy,
project: project,
tag: tag_name,
description: description)
end
it 'updates the release description' do
put api(route, current_user), params: { description: new_description }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['tag_name']).to eq(tag_name)
expect(json_response['description']).to eq(new_description)
end
it 'returns error if feature is removed' do
stub_feature_flags(remove_release_notes_from_tags_api: true)
put api(route, current_user), params: { description: new_description }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response["message"]).to eq("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311")
end
end
context 'when tag does not exist' do
let(:tag_name) { 'unknown' }
it_behaves_like '403 response' do
let(:request) { put api(route, current_user), params: { description: new_description } }
let(:message) { '403 Forbidden' }
end
end
context 'when repository is disabled' do
include_context 'disabled repository'
it_behaves_like '403 response' do
let(:request) { put api(route, current_user), params: { description: new_description } }
end
end
end
context 'when authenticated', 'as a maintainer' do
let(:current_user) { user }
it_behaves_like 'repository update release'
context 'requesting with the escaped project full path' do
let(:project_id) { CGI.escape(project.full_path) }
it_behaves_like 'repository update release'
end
context 'when release does not exist' do
it_behaves_like '403 response' do
let(:request) { put api(route, current_user), params: { description: new_description } }
let(:message) { '403 Forbidden' }
end
end
end
end
end

View File

@ -12,7 +12,7 @@ RSpec.describe AnalyticsSummarySerializer do
let(:resource) do
Gitlab::CycleAnalytics::Summary::Issue
.new(project: double, from: 1.day.ago, current_user: user)
.new(project: double, options: { from: 1.day.ago }, current_user: user)
end
before do
@ -36,7 +36,7 @@ RSpec.describe AnalyticsSummarySerializer do
context 'when representing with unit' do
let(:resource) do
Gitlab::CycleAnalytics::Summary::DeploymentFrequency
.new(deployments: 10, from: 1.day.ago)
.new(deployments: 10, options: { from: 1.day.ago })
end
subject { described_class.new.represent(resource, with_unit: true) }

Some files were not shown because too many files have changed in this diff Show More