Commit 4c16d4ff authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 3b85f5e4
......@@ -40,7 +40,7 @@ AllCops:
- 'db/ci_migrate/*.rb' # since the `db/ci_migrate` is a symlinked to `db/migrate`
# Use absolute path to avoid orphan directories with changed workspace root.
CacheRootDirectory: <%= Dir.getwd %>/tmp
MaxFilesInCache: 25000
MaxFilesInCache: 30000
Cop/AvoidKeywordArgumentsInSidekiqWorkers:
Enabled: true
......
......@@ -522,7 +522,7 @@ gem 'lockbox', '~> 0.6.2'
gem 'valid_email', '~> 0.1'
# JSON
gem 'json', '~> 2.3.0'
gem 'json', '~> 2.5.1'
gem 'json_schemer', '~> 0.2.18'
gem 'oj', '~> 3.10.6'
gem 'multi_json', '~> 1.14.1'
......
......@@ -657,7 +657,7 @@ GEM
character_set (~> 1.4)
regexp_parser (~> 2.1)
regexp_property_values (~> 1.0)
json (2.3.0)
json (2.5.1)
json-jwt (1.13.0)
activesupport (>= 4.2)
aes_key_wrap
......@@ -1509,7 +1509,7 @@ DEPENDENCIES
ipaddress (~> 0.8.3)
jira-ruby (~> 2.1.4)
js_regex (~> 3.7)
json (~> 2.3.0)
json (~> 2.5.1)
json_schemer (~> 0.2.18)
jwt (~> 2.1.0)
kaminari (~> 1.0)
......
......@@ -36,7 +36,7 @@ export default {
return this.glFeatures.wipLimits && !this.isEpicBoard;
},
activeList() {
return this.boardLists[this.activeId];
return this.boardLists[this.activeId] || {};
},
activeListLabel() {
return this.activeList.label;
......@@ -81,9 +81,26 @@ export default {
v-bind="$attrs"
class="js-board-settings-sidebar gl-absolute"
:open="isSidebarOpen"
variant="sidebar"
@close="unsetActiveId"
>
<template #title>{{ $options.listSettingsText }}</template>
<template #title>
<h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">
{{ $options.listSettingsText }}
</h2>
</template>
<template #header>
<div v-if="canAdminList && activeList.id" class="gl-mt-3">
<gl-button
variant="danger"
category="secondary"
size="small"
data-testid="remove-list"
@click.stop="deleteBoard"
>{{ __('Remove list') }}
</gl-button>
</div>
</template>
<template v-if="isSidebarOpen">
<div v-if="boardListType === ListType.label">
<label class="js-list-label gl-display-block">{{ listTypeTitle }}</label>
......@@ -103,16 +120,6 @@ export default {
v-if="isWipLimitsOn"
:max-issue-count="activeList.maxIssueCount"
/>
<div v-if="canAdminList && !activeList.preset && activeList.id" class="gl-mt-4">
<gl-button
variant="danger"
category="secondary"
icon="remove"
data-testid="remove-list"
@click.stop="deleteBoard"
>{{ __('Remove list') }}
</gl-button>
</div>
</template>
</gl-drawer>
</mounting-portal>
......
......@@ -205,6 +205,8 @@ export default {
:items="clusters"
:fields="fields"
stacked="md"
head-variant="white"
thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
class="qa-clusters-table"
data-testid="cluster_list_table"
>
......
......@@ -88,7 +88,7 @@ export default {
<template>
<section data-testid="registry-settings-app">
<cleanup-policy-enabled-alert v-if="showCleanupPolicyOnAlert" :project-path="projectPath" />
<settings-block default-expanded>
<settings-block :collapsible="false">
<template #title> {{ __('Clean up image tags') }}</template>
<template #description>
<span data-testid="description">
......
......@@ -95,6 +95,9 @@ export default {
methods: {
...mapActions(['fetchDescriptionVersion', 'softDeleteDescriptionVersion']),
},
safeHtmlConfig: {
ADD_TAGS: ['use'], // to support icon SVGs
},
};
</script>
......@@ -104,7 +107,7 @@ export default {
:class="{ target: isTargetNote, 'pr-0': shouldShowDescriptionVersion }"
class="note system-note note-wrapper"
>
<div class="timeline-icon" v-html="iconHtml /* eslint-disable-line vue/no-v-html */"></div>
<div v-safe-html:[$options.safeHtmlConfig]="iconHtml" class="timeline-icon"></div>
<div class="timeline-content">
<div class="note-header">
<note-header :author="note.author" :created-at="note.created_at" :note-id="note.id">
......
import SettingsBlock from './settings_block.vue';
export default {
component: SettingsBlock,
title: 'vue_shared/components/settings/settings_block',
};
const Template = (args, { argTypes }) => ({
components: { SettingsBlock },
props: Object.keys(argTypes),
template: `
<settings-block v-bind="$props">
<template #title>Settings section title</template>
<template #description>Settings section description</template>
<template #default>
<p>Content</p>
<p>More content</p>
<p>Content</p>
<p>More content...</p>
<p>Content</p>
</template>
</settings-block>
`,
});
export const Default = Template.bind({});
<script>
import { GlButton } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { __ } from '~/locale';
export default {
......@@ -15,50 +17,99 @@ export default {
default: false,
required: false,
},
collapsible: {
type: Boolean,
default: true,
required: false,
},
},
data() {
return {
sectionExpanded: false,
// Non-collapsible sections should always be expanded.
// For collapsible sections, fall back to defaultExpanded.
sectionExpanded: !this.collapsible || this.defaultExpanded,
};
},
computed: {
expanded() {
return this.defaultExpanded || this.sectionExpanded;
},
toggleText() {
return this.expanded ? __('Collapse') : __('Expand');
const { collapseText, expandText } = this.$options.i18n;
return this.sectionExpanded ? collapseText : expandText;
},
settingsContentId() {
return uniqueId('settings_content_');
},
settingsLabelId() {
return uniqueId('settings_label_');
},
toggleButtonAriaLabel() {
const { collapseAriaLabel, expandAriaLabel } = this.$options.i18n;
return this.sectionExpanded ? collapseAriaLabel : expandAriaLabel;
},
ariaExpanded() {
return String(this.sectionExpanded);
},
},
methods: {
toggleSectionExpanded() {
this.sectionExpanded = !this.sectionExpanded;
if (this.sectionExpanded) {
this.$refs.settingsContent.focus();
}
},
},
i18n: {
collapseText: __('Collapse'),
expandText: __('Expand'),
collapseAriaLabel: __('Collapse settings section'),
expandAriaLabel: __('Expand settings section'),
},
};
</script>
<template>
<section class="settings" :class="{ 'no-animate': !slideAnimated, expanded }">
<section class="settings" :class="{ 'no-animate': !slideAnimated, expanded: sectionExpanded }">
<div class="settings-header">
<h4>
<span
v-if="collapsible"
:id="settingsLabelId"
role="button"
tabindex="0"
class="gl-cursor-pointer"
data-testid="section-title"
:aria-controls="settingsContentId"
:aria-expanded="ariaExpanded"
data-testid="section-title-button"
@click="toggleSectionExpanded"
@keydown.enter.space="toggleSectionExpanded"
>
<slot name="title"></slot>
</span>
<template v-else>
<slot name="title"></slot>
</template>
</h4>
<gl-button @click="toggleSectionExpanded">
<gl-button
v-if="collapsible"
:aria-controls="settingsContentId"
:aria-expanded="ariaExpanded"
:aria-label="toggleButtonAriaLabel"
@click="toggleSectionExpanded"
>
{{ toggleText }}
</gl-button>
<p>
<slot name="description"></slot>
</p>
</div>
<div class="settings-content">
<div
:id="settingsContentId"
ref="settingsContent"
:aria-labelledby="settingsLabelId"
tabindex="-1"
role="region"
class="settings-content"
>
<slot></slot>
</div>
</section>
......
......@@ -13,6 +13,13 @@ class PendingBuild < Ci::ApplicationRecord
scope :ref_protected, -> { where(protected: true) }
scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) }
scope :with_instance_runners, -> { where(instance_runners_enabled: true) }
scope :for_tags, ->(tag_ids) do
if tag_ids.present?
where('ci_pending_builds.tag_ids <@ ARRAY[?]::int[]', Array.wrap(tag_ids))
else
where("ci_pending_builds.tag_ids = '{}'")
end
end
class << self
def upsert_from_build!(build)
......
......@@ -13,6 +13,9 @@ class Agent < ApplicationRecord
has_many :group_authorizations, class_name: 'Clusters::Agents::GroupAuthorization'
has_many :authorized_groups, class_name: '::Group', through: :group_authorizations, source: :group
has_many :project_authorizations, class_name: 'Clusters::Agents::ProjectAuthorization'
has_many :authorized_projects, class_name: '::Project', through: :project_authorizations, source: :project
scope :ordered_by_name, -> { order(:name) }
scope :with_name, -> (name) { where(name: name) }
......
# frozen_string_literal: true
module Clusters
module Agents
class ProjectAuthorization < ApplicationRecord
self.table_name = 'agent_project_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :project, class_name: '::Project', optional: false
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
end
end
end
......@@ -57,7 +57,7 @@ class Namespace < ApplicationRecord
has_one :admin_note, inverse_of: :namespace
accepts_nested_attributes_for :admin_note, update_only: true
validates :owner, presence: true, if: ->(n) { n.type.nil? }
validates :owner, presence: true, if: ->(n) { n.owner_required? }
validates :name,
presence: true,
length: { maximum: 255 }
......@@ -266,6 +266,10 @@ def user?
type.nil? || type == Namespaces::UserNamespace.sti_name || !(group? || project?)
end
def owner_required?
user?
end
def find_fork_of(project)
return unless project.fork_network
......
......@@ -4,6 +4,8 @@ module Namespaces
class ProjectNamespace < Namespace
has_one :project, foreign_key: :project_namespace_id, inverse_of: :project_namespace
validates :project, presence: true
def self.sti_name
'Project'
end
......
......@@ -47,7 +47,7 @@ def builds_for_group_runner
def builds_for_project_runner
relation = new_builds
.where(project: runner.projects.without_deleted.with_builds_enabled)
.where(project: runner_projects_relation)
order(relation)
end
......@@ -87,6 +87,14 @@ def strategy
end
end
end
def runner_projects_relation
if ::Feature.enabled?(:ci_pending_builds_project_runners_decoupling, runner, default_enabled: :yaml)
runner.runner_projects.select(:project_id)
else
runner.projects.without_deleted.with_builds_enabled
end
end
end
end
end
......
......@@ -24,7 +24,7 @@ def builds_for_group_runner
def builds_matching_tag_ids(relation, ids)
if ::Feature.enabled?(:ci_queueing_denormalize_tags_information, runner, default_enabled: :yaml)
relation.where('tag_ids <@ ARRAY[?]::int[]', runner.tags_ids)
relation.for_tags(runner.tags_ids)
else
relation.merge(CommitStatus.matches_tag_ids(ids, table: 'ci_pending_builds', column: 'build_id'))
end
......
......@@ -5,9 +5,10 @@ module Agents
class RefreshAuthorizationService
include Gitlab::Utils::StrongMemoize
AUTHORIZED_GROUP_LIMIT = 100
AUTHORIZED_ENTITY_LIMIT = 100
delegate :project, to: :agent, private: true
delegate :root_ancestor, to: :project, private: true
def initialize(agent, config:)
@agent = agent
......@@ -15,6 +16,30 @@ def initialize(agent, config:)
end
def execute
refresh_projects!
refresh_groups!
true
end
private
attr_reader :agent, :config
def refresh_projects!
if allowed_project_configurations.present?
project_ids = allowed_project_configurations.map { |config| config.fetch(:project_id) }
agent.with_lock do
agent.project_authorizations.upsert_all(allowed_project_configurations, unique_by: [:agent_id, :project_id])
agent.project_authorizations.where.not(project_id: project_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord
end
else
agent.project_authorizations.delete_all(:delete_all)
end
end
def refresh_groups!
if allowed_group_configurations.present?
group_ids = allowed_group_configurations.map { |config| config.fetch(:group_id) }
......@@ -25,35 +50,57 @@ def execute
else
agent.group_authorizations.delete_all(:delete_all)
end
true
end
private
def allowed_project_configurations
strong_memoize(:allowed_project_configurations) do
project_entries = extract_config_entries(entity: 'projects')
attr_reader :agent, :config
if project_entries
allowed_projects.where_full_path_in(project_entries.keys).map do |project|
{ project_id: project.id, config: project_entries[project.full_path] }
end
end
end
end
def allowed_group_configurations
strong_memoize(:allowed_group_configurations) do
group_entries = config.dig('ci_access', 'groups')&.first(AUTHORIZED_GROUP_LIMIT)
group_entries = extract_config_entries(entity: 'groups')
if group_entries
groups_by_path = group_entries.index_by { |config| config.delete('id') }
allowed_groups.where_full_path_in(groups_by_path.keys).map do |group|
{ group_id: group.id, config: groups_by_path[group.full_path] }
allowed_groups.where_full_path_in(group_entries.keys).map do |group|
{ group_id: group.id, config: group_entries[group.full_path] }
end
end
end
end
def extract_config_entries(entity:)
config.dig('ci_access', entity)
&.first(AUTHORIZED_ENTITY_LIMIT)
&.index_by { |config| config.delete('id') }
end
def allowed_projects
if group_root_ancestor?
root_ancestor.all_projects
else
::Project.none
end
end
def allowed_groups
if project.root_ancestor.group?
project.root_ancestor.self_and_descendants
if group_root_ancestor?
root_ancestor.self_and_descendants
else
::Group.none
end
end
def group_root_ancestor?
root_ancestor.group?
end
end
end
end
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'rails'
require 'png_quantizator'
require 'parallel'
require 'rainbow/ext/string'
require_relative '../tooling/lib/tooling/images'
return if Rails.env.production?
def usage
puts <<~EOF
Usage: #{$0} [compress|lint] [PATH..]
compress Compress all documentation PNG images using pngquant.
lint Checks that all documentation PNG images have been compressed with pngquant.
PATH One or more files or directories. If empty, `doc/**/*.png` is used.
EOF
end
command = ARGV.shift
paths = ARGV.presence || ['doc']
files = paths.flat_map do |path|
File.directory?(path) ? Dir.glob(File.join(path, '/**/*.png')) : path
end
case command
when 'compress'
puts "Compressing #{files.size} PNG files"
Parallel.each(files) do |file|
was_uncompressed, savings = Tooling::Image.compress_image(file)
if was_uncompressed
puts "#{file} was reduced by #{savings} bytes"
end
end
when 'lint'
puts "Checking #{files.size} PNG files"
uncompressed_files = Parallel.map(files) do |file|
is_uncompressed, _ = Tooling::Image.compress_image(file, true)
if is_uncompressed
puts "Uncompressed file detected: ".color(:red) + file
file
end
end.compact
if uncompressed_files.empty?
puts "All documentation images are optimally compressed!".color(:green)
else
warn(
"The #{uncompressed_files.size} image(s) above have not been optimally compressed using pngquant.".color(:red),
'Please run "bin/pngquant compress" and commit the result.'
)
abort
end
else
usage
end
description: "Show alert integrations list"
category: "`Alert Integrations`"
action: view_alert_integrations_list
label_description: ""
property_description: ""
value_description: ""
extra_properties:
identifiers:
product_section: ops
product_stage: monitor
product_group: group::monitor
product_category:
milestone: "13.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44549
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate
description: "Click 2FA codes related buttons: copy, download, print..."
category: default
action: click_button
label_description: "Action button name"
property_description: ""
value_description: ""
extra_properties:
identifiers:
product_section: dev
product_stage: manage
product_group: group::compliance
product_category:
milestone: "13.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49510
distributions: