Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-01-09 21:35:37 +00:00
parent 1cbefa896f
commit be3c4986f5
52 changed files with 866 additions and 346 deletions

View File

@ -1,5 +1,5 @@
<script>
import { GlCollapsibleListbox, GlDrawer, GlTooltipDirective } from '@gitlab/ui';
import { GlAlert, GlButton, GlCollapsibleListbox, GlDrawer, GlTooltipDirective } from '@gitlab/ui';
import UserAvatar from '~/vue_shared/components/users_table/user_avatar.vue';
import {
ACCESS_LEVEL_DEFAULT_STRING,
@ -8,13 +8,15 @@ import {
} from '~/organizations/shared/constants';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
import { createAlert } from '~/alert';
export default {
name: 'UserDetailsDrawer',
components: {
GlAlert,
GlButton,
GlCollapsibleListbox,
GlDrawer,
UserAvatar,
@ -27,10 +29,15 @@ export default {
title: s__('Organization|Organization user details'),
roleListboxLabel: s__('Organization|Organization role'),
disabledRoleListboxTooltipText: s__('Organization|Organizations must have at least one owner.'),
removeSelfAsOwnerWarning: s__(
'Organization|If you proceed with this change you will lose your owner permissions for this organization, including access to this page.',
),
errorMessage: s__(
'Organization|An error occurred updating the organization role. Please try again.',
),
successMessage: s__('Organization|Organization role was updated successfully.'),
save: __('Save'),
cancel: __('Cancel'),
},
roleListboxItems: [
{
@ -60,12 +67,21 @@ export default {
drawerHeaderHeight() {
return getContentWrapperHeight();
},
isChangingRole() {
return this.initialAccessLevel !== this.selectedAccessLevel;
},
roleListboxDisabled() {
return this.user?.isLastOwner;
},
roleListboxTooltip() {
return this.roleListboxDisabled ? this.$options.i18n.disabledRoleListboxTooltipText : null;
},
showRemoveSelfAsOwnerWarning() {
const isUserCurrentUser = this.user?.id === window.gon?.current_user_id;
const isOwnerRoleSelected = this.selectedAccessLevel === ACCESS_LEVEL_OWNER_STRING;
return isUserCurrentUser && !isOwnerRoleSelected && this.isChangingRole;
},
},
watch: {
user(value) {
@ -83,7 +99,7 @@ export default {
this.$toast.show(this.$options.i18n.successMessage);
this.$emit('role-change');
},
async onRoleSelect() {
async save() {
this.setLoading(true);
try {
@ -113,6 +129,9 @@ export default {
this.setLoading(false);
}
},
cancel() {
this.selectedAccessLevel = this.initialAccessLevel;
},
close() {
this.$emit('close');
},
@ -135,9 +154,8 @@ export default {
</template>
<template #default>
<div>
<user-avatar :user="user" :admin-user-path="paths.adminUser" />
</div>
<div>
<user-avatar class="gl-mt-3" :user="user" :admin-user-path="paths.adminUser" />
<hr />
<h5>{{ $options.i18n.roleListboxLabel }}</h5>
<div
v-gl-tooltip="{ disabled: !roleListboxTooltip, title: roleListboxTooltip }"
@ -151,10 +169,26 @@ export default {
:disabled="roleListboxDisabled"
:items="$options.roleListboxItems"
:loading="loading"
@select="onRoleSelect"
/>
</div>
</div>
</template>
<template #footer>
<div
v-if="isChangingRole"
class="gl-flex gl-flex-col gl-gap-4"
data-testid="user-details-drawer-footer"
>
<gl-alert v-if="showRemoveSelfAsOwnerWarning" variant="warning" :dismissible="false">{{
$options.i18n.removeSelfAsOwnerWarning
}}</gl-alert>
<div class="gl-flex gl-gap-3">
<gl-button variant="confirm" :disabled="loading" @click="save">{{
$options.i18n.save
}}</gl-button>
<gl-button :disabled="loading" @click="cancel">{{ $options.i18n.cancel }}</gl-button>
</div>
</div>
</template>
</gl-drawer>
</template>

View File

@ -111,7 +111,7 @@ export default {
</script>
<template>
<div class="gl-flex gl-gap-2" @click.prevent>
<div class="gl-flex gl-gap-2 gl-self-start" @click.prevent>
<toggle-snoozed-status
v-if="glFeatures.todosSnoozing"
:todo="todo"

View File

@ -77,6 +77,7 @@ module Namespaces
def by_feature_availability(items)
items = items.with_issues_available_for_user(current_user) if params[:with_issues_enabled].present?
items = items.with_namespace_domain_pages if params[:with_namespace_domain_pages].present?
if params[:with_merge_requests_enabled].present?
items = items.with_merge_requests_available_for_user(current_user)
end

View File

@ -21,6 +21,8 @@ class PersonalAccessTokensFinder
tokens = by_revoked_state(tokens)
tokens = by_created_before(tokens)
tokens = by_created_after(tokens)
tokens = by_expires_before(tokens)
tokens = by_expires_after(tokens)
tokens = by_last_used_before(tokens)
tokens = by_last_used_after(tokens)
tokens = by_search(tokens)
@ -113,6 +115,18 @@ class PersonalAccessTokensFinder
tokens.created_after(params[:created_after])
end
def by_expires_before(tokens)
return tokens unless params[:expires_before]
tokens.expires_before(params[:expires_before])
end
def by_expires_after(tokens)
return tokens unless params[:expires_after]
tokens.expires_after(params[:expires_after])
end
def by_last_used_before(tokens)
return tokens unless params[:last_used_before]

View File

@ -46,6 +46,10 @@ module Resolvers
required: false,
description: "Return only projects with merge requests enabled."
argument :with_namespace_domain_pages, GraphQL::Types::Boolean,
required: false,
description: "Return only projects that use the namespace domain for pages projects."
type Types::ProjectType, null: true
def resolve(args)
@ -83,7 +87,8 @@ module Resolvers
search: args[:search],
ids: parse_gids(args[:ids]),
with_issues_enabled: args[:with_issues_enabled],
with_merge_requests_enabled: args[:with_merge_requests_enabled]
with_merge_requests_enabled: args[:with_merge_requests_enabled],
with_namespace_domain_pages: args[:with_namespace_domain_pages]
}
end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
module Integrations
module Base
module Assembla
extend ActiveSupport::Concern
class_methods do
def title
'Assembla'
end
def description
_('Manage projects.')
end
def to_param
'assembla'
end
def supported_events
%w[push]
end
end
included do
validates :token, presence: true, if: :activated?
field :token,
type: :password,
description: -> { s_('The authentication token.') },
non_empty_password_title: -> { s_('ProjectService|Enter new token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: '',
required: true
field :subdomain,
description: -> { s_('The subdomain setting.') },
exposes_secrets: true,
placeholder: ''
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
url = "https://atlas.assembla.com/spaces/#{URI.encode_www_form_component(subdomain)}/github_tool?secret_key=#{URI.encode_www_form_component(token)}"
body = { payload: data }
Gitlab::HTTP.post(
url,
body: Gitlab::Json.dump(body),
headers: { 'Content-Type' => 'application/json' }
)
end
end
end
end

View File

@ -2,44 +2,6 @@
module Integrations
class Assembla < Integration
validates :token, presence: true, if: :activated?
field :token,
type: :password,
description: -> { s_('The authentication token.') },
non_empty_password_title: -> { s_('ProjectService|Enter new token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: '',
required: true
field :subdomain,
description: -> { s_('The subdomain setting.') },
exposes_secrets: true,
placeholder: ''
def self.title
'Assembla'
end
def self.description
_('Manage projects.')
end
def self.to_param
'assembla'
end
def self.supported_events
%w[push]
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}"
body = { payload: data }
Gitlab::HTTP.post(url, body: Gitlab::Json.dump(body), headers: { 'Content-Type' => 'application/json' })
end
include Integrations::Base::Assembla
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Integrations
module Instance
class Assembla < Integration
include Integrations::Base::Assembla
end
end
end

View File

@ -27,14 +27,8 @@ module Network
# https://gitlab.com/gitlab-org/gitlab-foss/issues/58013
Gitlab::GitalyClient.allow_n_plus_1_calls do
# Decorate with app/model/network/commit.rb
if Feature.enabled?(:use_list_commits_rpc_network_graph, @project, type: :gitlab_com_derisk)
list_commits(count_to_display_commit_in_center).map do |commit|
Network::Commit.new(commit)
end
else
find_commits(count_to_display_commit_in_center).map do |commit|
Network::Commit.new(commit)
end
list_commits(count_to_display_commit_in_center).map do |commit|
Network::Commit.new(commit)
end
end
end
@ -76,11 +70,7 @@ module Network
offset = -1
skip = 0
while offset == -1
tmp_commits = if Feature.enabled?(:use_list_commits_rpc_network_graph, @project, type: :gitlab_com_derisk)
list_commits(skip)
else
find_commits(skip)
end
tmp_commits = list_commits(skip)
if tmp_commits.present?
index = tmp_commits.index do |c|

View File

@ -49,6 +49,8 @@ class PersonalAccessToken < ApplicationRecord
scope :expiring_and_not_notified, ->(date) { where(["revoked = false AND expire_notification_delivered = false AND seven_days_notification_sent_at IS NULL AND expires_at >= CURRENT_DATE AND expires_at <= ?", date]) }
scope :expired_today_and_not_notified, -> { where(["revoked = false AND expires_at = CURRENT_DATE AND after_expiry_notification_delivered = false"]) }
scope :expired_before, ->(date) { expired.where(arel_table[:expires_at].lt(date)) }
scope :expires_before, ->(date) { where(arel_table[:expires_at].lteq(date)) }
scope :expires_after, ->(date) { where(arel_table[:expires_at].gteq(date)) }
scope :inactive, -> { where("revoked = true OR expires_at < CURRENT_DATE") }
scope :last_used_before_or_unused, ->(date) { where("personal_access_tokens.created_at < :date AND (last_used_at < :date OR last_used_at IS NULL)", date: date) }
scope :with_impersonation, -> { where(impersonation: true) }

View File

@ -817,6 +817,11 @@ class Project < ApplicationRecord
.where(project_pages_metadata: { project_id: nil })
end
scope :with_namespace_domain_pages, -> do
joins(:project_setting)
.where(project_setting: { pages_unique_domain_enabled: false })
end
scope :with_api_commit_entity_associations, -> {
preload(:project_feature, :route, namespace: [:route, :owner])
}
@ -2358,6 +2363,10 @@ class Project < ApplicationRecord
!(pages_metadatum&.onboarding_complete || pages_deployed?)
end
def pages_unique_domain_enabled?
project_setting.pages_unique_domain_enabled
end
def remove_private_deploy_keys
exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS (
@ -3353,7 +3362,7 @@ class Project < ApplicationRecord
end
def pages_domain_present?(domain_url)
pages_url == domain_url || pages_domains.exists?(domain: domain_url)
pages_url == domain_url || pages_domains.any? { |domain| domain.url == domain_url }
end
# overridden in EE

View File

@ -16,5 +16,6 @@
#js-storage-usage-app{ data: { project_path: @project.full_path } }
= render_if_exists 'projects/usage_quotas/transfer_tab_content'
= render_if_exists 'shared/usage_quotas/tabs_content/observability'
= render_if_exists 'projects/usage_quotas/pages'
= render 'shared/usage_quotas/index'

View File

@ -1,9 +0,0 @@
---
name: use_list_commits_rpc_network_graph
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/386449
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164081
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/484681
milestone: '17.4'
group: group::source code
type: gitlab_com_derisk
default_enabled: false

View File

@ -1287,8 +1287,8 @@ production: &base
# private_key_file: /home/git/gitlab/config/topology-service-key.pem
cell:
# id: 1
# name: cell-1
# id: null
# name: null
# skip_sequence_alteration: false
gitlab_kas:

View File

@ -1054,8 +1054,8 @@ Settings.topology_service['private_key_file'] ||= '/home/git/gitlab/config/topol
# Cells
#
Settings['cell'] ||= {}
Settings.cell['id'] ||= 1
Settings.cell['name'] ||= "cell-#{Settings.cell['id']}"
Settings.cell['id'] ||= nil
Settings.cell['name'] ||= nil
Settings.cell['skip_sequence_alteration'] ||= false
#

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
return if Gitlab::Utils.to_boolean(ENV['SKIP_CELL_CONFIG_VALIDATION'], default: false)
ValidationError = Class.new(StandardError)
if Gitlab.config.cell.id.present? && !Gitlab.config.topology_service.enabled
raise ValidationError, "Topology Service is not configured, but Cell ID is set"
end
if Gitlab.config.topology_service.enabled && Gitlab.config.cell.id.blank?
raise ValidationError, "Topology Service is enabled, but Cell ID is not set"
end

View File

@ -179,16 +179,13 @@ Settings = GitlabSettings.load(file, Rails.env) do
[[Gitlab::SidekiqConfig::WorkerMatcher::WILDCARD_MATCH, 'default']]
end
# This method dictates whether the GitLab instance is part of a cells cluster
def topology_service_enabled?
topology_service && topology_service.respond_to?(:enabled) && topology_service.enabled
end
def has_configured_cell?
cell && cell.respond_to?(:name) && cell.name.present?
end
def skip_sequence_alteration?
has_configured_cell? && cell.respond_to?(:skip_sequence_alteration) && cell.skip_sequence_alteration
cell.respond_to?(:skip_sequence_alteration) && cell.skip_sequence_alteration
end
private

View File

@ -2,6 +2,7 @@
table_name: instance_integrations
classes:
- Integrations::Instance::Integration
- Integrations::Instance::Assembla
feature_categories:
- integrations
description: Support 3rd party instance-wide integrations

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class UpdateWorkspacesDevfilePathNullable < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
def up
change_column_null :workspaces, :devfile_path, true
end
def down
change_column_null :workspaces, :devfile_path, false
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class RenameWorkspacesDevfileRefToProjectRef < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
def up
rename_column_concurrently :workspaces, :devfile_ref, :project_ref
end
def down
undo_rename_column_concurrently :workspaces, :devfile_ref, :project_ref
end
end

View File

@ -0,0 +1,25 @@
# 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 AddWorkflowDefinitionToWorkflows < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.8'
def up
with_lock_retries do
add_column :duo_workflows_workflows, :workflow_definition, :text, default: 'software_development',
null: false
end
add_text_limit :duo_workflows_workflows, :workflow_definition, 255
end
def down
with_lock_retries do
remove_column :duo_workflows_workflows, :workflow_definition
end
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class CleanupWorkspacesDevfileRefRename < Gitlab::Database::Migration[2.2]
milestone '17.8'
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :workspaces, :devfile_ref, :project_ref
end
def down
undo_cleanup_concurrent_column_rename :workspaces, :devfile_ref, :project_ref
end
end

View File

@ -0,0 +1 @@
2ab125f0a48dbece1a6e98b698f2f44734f07976e34909a502f7cfbb937d9c55

View File

@ -0,0 +1 @@
3de77a23770fa6eb0bfff996189e1f3b9748299fed8d01c0929423eba4570978

View File

@ -0,0 +1 @@
32e2325b1442e77a35af0544cdc9a6ea7fab37d4f9185264923dcdc574da758e

View File

@ -0,0 +1 @@
d037f2d4a30977b304f4df8da3374043d5e1fd15ddb7b066cf168c848148d825

View File

@ -12142,7 +12142,9 @@ CREATE TABLE duo_workflows_workflows (
status smallint DEFAULT 0 NOT NULL,
goal text,
agent_privileges smallint[] DEFAULT '{1,2}'::smallint[] NOT NULL,
CONSTRAINT check_5aedde451d CHECK ((char_length(goal) <= 4096))
workflow_definition text DEFAULT 'software_development'::text NOT NULL,
CONSTRAINT check_5aedde451d CHECK ((char_length(goal) <= 4096)),
CONSTRAINT check_ec723e2a1a CHECK ((char_length(workflow_definition) <= 255))
);
CREATE SEQUENCE duo_workflows_workflows_id_seq
@ -22773,8 +22775,7 @@ CREATE TABLE workspaces (
namespace text NOT NULL,
desired_state text NOT NULL,
actual_state text NOT NULL,
devfile_ref text NOT NULL,
devfile_path text NOT NULL,
devfile_path text,
devfile text,
processed_devfile text,
url text,
@ -22785,15 +22786,17 @@ CREATE TABLE workspaces (
url_query_string text,
workspaces_agent_config_version integer NOT NULL,
desired_config_generator_version integer,
project_ref text,
CONSTRAINT check_15543fb0fa CHECK ((char_length(name) <= 64)),
CONSTRAINT check_157d5f955c CHECK ((char_length(namespace) <= 64)),
CONSTRAINT check_2b401b0034 CHECK ((char_length(deployment_resource_version) <= 64)),
CONSTRAINT check_35e31ca320 CHECK ((desired_config_generator_version IS NOT NULL)),
CONSTRAINT check_72fee08424 CHECK ((char_length(project_ref) <= 256)),
CONSTRAINT check_77d1a2ff50 CHECK ((char_length(processed_devfile) <= 65535)),
CONSTRAINT check_8a0ab61b6b CHECK ((char_length(url_query_string) <= 256)),
CONSTRAINT check_8e363ee3ad CHECK ((char_length(devfile_ref) <= 256)),
CONSTRAINT check_8e4db5ffc2 CHECK ((char_length(actual_state) <= 32)),
CONSTRAINT check_9e42558c35 CHECK ((char_length(url) <= 1024)),
CONSTRAINT check_a758efdc89 CHECK ((project_ref IS NOT NULL)),
CONSTRAINT check_b70eddcbc1 CHECK ((char_length(desired_state) <= 32)),
CONSTRAINT check_dc58d56169 CHECK ((char_length(devfile_path) <= 2048)),
CONSTRAINT check_eb32879a3d CHECK ((char_length(devfile) <= 65535)),

View File

@ -671,3 +671,50 @@ Gitlab::HTTP.get(primary.internal_uri, allow_local_requests: true, limit: 10)
Make sure that the value of `internal_uri` is correct in the output above.
If the URL of the primary site is incorrect, double-check it in `/etc/gitlab/gitlab.rb`, and in **Admin > Geo > Sites**.
### Excessive database IO from Geo metrics collection
If you're experiencing high database load due to frequent Geo metrics collection, you can reduce the frequency of the `geo_metrics_update_worker` job. This adjustment can help alleviate database strain in large GitLab instances where metrics collection significantly impacts database performance.
Increasing the interval means that your Geo metrics are updated less frequently. This results in metrics being out-of-date for longer periods of time, which may impact your ability to monitor Geo replication in real-time. If metrics are out-of-date for more than 10 minutes, the site is arbitrarily marked as "Unhealthy" in the Admin Area.
The following example sets the job to run every 30 minutes. Adjust the cron schedule based on your needs.
::Tabs
:::TabTitle Linux package (Omnibus)
1. Add or modify the following setting in `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['geo_metrics_update_worker_cron'] = "*/30 * * * *"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
:::TabTitle Self-compiled (source)
1. Edit `/home/git/gitlab/config/gitlab.yml`:
```yaml
production: &base
ee_cron_jobs:
geo_metrics_update_worker:
cron: "*/30 * * * *"
```
1. Save the file and restart GitLab:
```shell
# For systems running systemd
sudo systemctl restart gitlab.target
# For systems running SysV init
sudo service gitlab restart
```
::EndTabs

View File

@ -11728,11 +11728,12 @@ Input type: `WorkspaceCreateInput`
| <a id="mutationworkspacecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkspacecreateclusteragentid"></a>`clusterAgentId` | [`ClustersAgentID!`](#clustersagentid) | GlobalID of the cluster agent the created workspace will be associated with. |
| <a id="mutationworkspacecreatedesiredstate"></a>`desiredState` | [`String!`](#string) | Desired state of the created workspace. |
| <a id="mutationworkspacecreatedevfilepath"></a>`devfilePath` | [`String!`](#string) | Project repo git path containing the devfile used to configure the workspace. |
| <a id="mutationworkspacecreatedevfileref"></a>`devfileRef` | [`String!`](#string) | Project repo git ref containing the devfile used to configure the workspace. |
| <a id="mutationworkspacecreatedevfilepath"></a>`devfilePath` | [`String`](#string) | Project path containing the devfile used to configure the workspace. If not provided, the GitLab default devfile is used. |
| <a id="mutationworkspacecreatedevfileref"></a>`devfileRef` **{warning-solid}** | [`String`](#string) | **Deprecated:** Argument is renamed to project_ref. Deprecated in GitLab 17.8. |
| <a id="mutationworkspacecreateeditor"></a>`editor` **{warning-solid}** | [`String`](#string) | **Deprecated:** Argument is not used. Deprecated in GitLab 17.5. |
| <a id="mutationworkspacecreatemaxhoursbeforetermination"></a>`maxHoursBeforeTermination` | [`Int!`](#int) | Maximum hours the workspace can exist before it is automatically terminated. |
| <a id="mutationworkspacecreateprojectid"></a>`projectId` | [`ProjectID!`](#projectid) | ID of the project that will provide the Devfile for the created workspace. |
| <a id="mutationworkspacecreateprojectref"></a>`projectRef` | [`String`](#string) | Project repo git ref. |
| <a id="mutationworkspacecreatevariables"></a>`variables` | [`[WorkspaceVariableInput!]`](#workspacevariableinput) | Variables to inject into the workspace. |
#### Fields
@ -23650,6 +23651,7 @@ A Duo Workflow.
| <a id="duoworkflowprojectid"></a>`projectId` | [`ProjectID!`](#projectid) | ID of the project. |
| <a id="duoworkflowupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the workflow was last updated. |
| <a id="duoworkflowuserid"></a>`userId` | [`UserID!`](#userid) | ID of the user. |
| <a id="duoworkflowworkflowdefinition"></a>`workflowDefinition` | [`String`](#string) | Duo Workflow type based on its capabilities. |
### `DuoWorkflowEnablement`
@ -23687,6 +23689,7 @@ Events that describe the history and progress of a Duo Workflow.
| <a id="duoworkfloweventmetadata"></a>`metadata` | [`JsonString`](#jsonstring) | Metadata associated with the event. |
| <a id="duoworkfloweventparenttimestamp"></a>`parentTimestamp` | [`Time`](#time) | Time of the parent event. |
| <a id="duoworkfloweventtimestamp"></a>`timestamp` | [`Time`](#time) | Time of the event. |
| <a id="duoworkfloweventworkflowdefinition"></a>`workflowDefinition` | [`String`](#string) | Duo Workflow type based on its capabilities. |
| <a id="duoworkfloweventworkflowgoal"></a>`workflowGoal` | [`String`](#string) | Goal of the workflow. |
| <a id="duoworkfloweventworkflowstatus"></a>`workflowStatus` | [`DuoWorkflowStatus`](#duoworkflowstatus) | Status of the workflow. |
@ -26110,6 +26113,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="groupprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by the criteria. |
| <a id="groupprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. |
| <a id="groupprojectswithmergerequestsenabled"></a>`withMergeRequestsEnabled` | [`Boolean`](#boolean) | Return only projects with merge requests enabled. |
| <a id="groupprojectswithnamespacedomainpages"></a>`withNamespaceDomainPages` | [`Boolean`](#boolean) | Return only projects that use the namespace domain for pages projects. |
##### `Group.releases`
@ -30352,6 +30356,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="namespaceprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by the criteria. |
| <a id="namespaceprojectswithissuesenabled"></a>`withIssuesEnabled` | [`Boolean`](#boolean) | Return only projects with issues enabled. |
| <a id="namespaceprojectswithmergerequestsenabled"></a>`withMergeRequestsEnabled` | [`Boolean`](#boolean) | Return only projects with merge requests enabled. |
| <a id="namespaceprojectswithnamespacedomainpages"></a>`withNamespaceDomainPages` | [`Boolean`](#boolean) | Return only projects that use the namespace domain for pages projects. |
##### `Namespace.remoteDevelopmentClusterAgents`
@ -38361,9 +38366,9 @@ Represents a remote development workspace.
| <a id="workspacedesiredstate"></a>`desiredState` | [`String!`](#string) | Desired state of the workspace. |
| <a id="workspacedesiredstateupdatedat"></a>`desiredStateUpdatedAt` | [`Time!`](#time) | Timestamp of the last update to the desired state. |
| <a id="workspacedevfile"></a>`devfile` | [`String!`](#string) | Source YAML of the devfile used to configure the workspace. |
| <a id="workspacedevfilepath"></a>`devfilePath` | [`String!`](#string) | Path to the devfile used to configure the workspace. |
| <a id="workspacedevfileref"></a>`devfileRef` | [`String!`](#string) | Git reference that contains the devfile used to configure the workspace. |
| <a id="workspacedevfileweburl"></a>`devfileWebUrl` | [`String!`](#string) | Web URL of the devfile used to configure the workspace. |
| <a id="workspacedevfilepath"></a>`devfilePath` | [`String`](#string) | Path to the devfile used to configure the workspace. |
| <a id="workspacedevfileref"></a>`devfileRef` **{warning-solid}** | [`String!`](#string) | **Deprecated** in GitLab 17.8. Field is renamed to project_ref. |
| <a id="workspacedevfileweburl"></a>`devfileWebUrl` **{warning-solid}** | [`String`](#string) | **Deprecated** in GitLab 17.8. Field is not used. |
| <a id="workspaceeditor"></a>`editor` **{warning-solid}** | [`String!`](#string) | **Deprecated** in GitLab 17.5. Field is not used. |
| <a id="workspaceforceincludeallresources"></a>`forceIncludeAllResources` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.6. **Status**: Experiment. Forces all resources to be included for the workspaceduring the next reconciliation with the agent. |
| <a id="workspaceid"></a>`id` | [`RemoteDevelopmentWorkspaceID!`](#remotedevelopmentworkspaceid) | Global ID of the workspace. |
@ -38372,6 +38377,7 @@ Represents a remote development workspace.
| <a id="workspacenamespace"></a>`namespace` | [`String!`](#string) | Namespace of the workspace in Kubernetes. |
| <a id="workspaceprocesseddevfile"></a>`processedDevfile` | [`String!`](#string) | Processed YAML of the devfile used to configure the workspace. |
| <a id="workspaceprojectid"></a>`projectId` | [`ID!`](#id) | ID of the project that contains the devfile for the workspace. |
| <a id="workspaceprojectref"></a>`projectRef` | [`String!`](#string) | Git reference that contains the devfile used to configure the workspace, and that will be cloned into the workspace. |
| <a id="workspacerespondedtoagentat"></a>`respondedToAgentAt` | [`Time`](#time) | Timestamp of the last response sent to the GitLab agent for Kubernetes for the workspace. |
| <a id="workspaceupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of the last update to any mutable workspace property. |
| <a id="workspaceurl"></a>`url` | [`String!`](#string) | URL of the workspace. |

View File

@ -25,101 +25,101 @@ Any dependencies are noted in the `Description` column for each permission.
## Admin
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`read_admin_dashboard`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171581) | Read-only access to admin dashboard | GitLab [17.6](https://gitlab.com/gitlab-org/gitlab/-/issues/501549) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Read-only access to admin dashboard | Read-only access to admin dashboard | [`read_admin_dashboard`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171581) | Instance | GitLab [17.6](https://gitlab.com/gitlab-org/gitlab/-/issues/501549) |
## Code review workflow
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`manage_merge_request_settings`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151586) | Configure merge request settings at the group or project level. Group actions include managing merge checks and approval settings. Project actions include managing MR configurations, approval rules and settings, and branch targets. In order to enable Suggested reviewers, the "Manage project access tokens" custom permission needs to be enabled. | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/443235) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage merge request approvals and settings | Configure merge request settings at the group or project level. Group actions include managing merge checks and approval settings. Project actions include managing MR configurations, approval rules and settings, and branch targets. In order to enable Suggested reviewers, the "Manage project access tokens" custom permission needs to be enabled. | [`manage_merge_request_settings`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151586) | Group,<br> Project | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/443235) |
## Compliance management
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144183) | Create, read, update, and delete compliance frameworks. Users with this permission can also assign a compliance framework label to a project, and set the default framework of a group. | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/411502) | | |
| [`read_compliance_dashboard`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175066) | Read compliance capabilities including adherence, violations, and frameworks for groups and projects. | GitLab [17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/465324) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage and assign compliance frameworks | Create, read, update, and delete compliance frameworks. Users with this permission can also assign a compliance framework label to a project, and set the default framework of a group. | [`admin_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144183) | Group,<br> Project | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/411502) |
| Read compliance dashboard | Read compliance capabilities including adherence, violations, and frameworks for groups and projects. | [`read_compliance_dashboard`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175066) | Group,<br> Project | GitLab [17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/465324) |
## Continuous delivery
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`manage_deploy_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151677) | Manage deploy tokens at the group or project level. | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/448843) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage deploy tokens | Manage deploy tokens at the group or project level. | [`manage_deploy_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151677) | Group,<br> Project | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/448843) |
## Groups and projects
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_group_member`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) | Add or remove users in a group, and assign roles to users. When assigning a role, users with this custom permission must select a role that has the same or fewer permissions as the default role used as the base for their custom role. | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) | | |
| [`archive_project`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) | Allows archiving of projects. | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) | | |
| [`remove_group`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145166) | Ability to delete or restore a group. This ability does not allow deleting top-level groups. Review the Retention period settings to prevent accidental deletion. | GitLab [16.10](https://gitlab.com/gitlab-org/gitlab/-/issues/425962) | | |
| [`remove_project`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139696) | Allows deletion of projects. | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/425959) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage group members | Add or remove users in a group, and assign roles to users. When assigning a role, users with this custom permission must select a role that has the same or fewer permissions as the default role used as the base for their custom role. | [`admin_group_member`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) | Group | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) |
| Archive project | Allows archiving of projects. | [`archive_project`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) | Project | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) |
| Delete group | Ability to delete or restore a group. This ability does not allow deleting top-level groups. Review the Retention period settings to prevent accidental deletion. | [`remove_group`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145166) | Group | GitLab [16.10](https://gitlab.com/gitlab-org/gitlab/-/issues/425962) |
| Delete project | Allows deletion of projects. | [`remove_project`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139696) | Project | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/425959) |
## Infrastructure as code
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_terraform_state`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140759) | Execute terraform commands, lock/unlock terraform state files, and remove file versions. | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/421789) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage Terraform state | Execute terraform commands, lock/unlock terraform state files, and remove file versions. | [`admin_terraform_state`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140759) | Project | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/421789) |
## Integrations
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_integrations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154601) | Create, read, update, and delete integrations with external applications. | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/460522) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage integrations | Create, read, update, and delete integrations with external applications. | [`admin_integrations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154601) | Group,<br> Project | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/460522) |
## Runner
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_runners`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151825) | Create, view, edit, and delete group or project Runners. Includes configuring Runner settings. | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/442851) | | |
| [`read_runners`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156798) | Allows read-only access to group or project runners, including the runner fleet dashboard. | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/468202) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage runners | Create, view, edit, and delete group or project Runners. Includes configuring Runner settings. | [`admin_runners`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151825) | Group,<br> Project | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/442851) |
| View runners | Allows read-only access to group or project runners, including the runner fleet dashboard. | [`read_runners`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156798) | Group,<br> Project | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/468202) |
## Secrets management
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_cicd_variables`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/143369) | Create, read, update, and delete CI/CD variables. | GitLab [16.10](https://gitlab.com/gitlab-org/gitlab/-/issues/437947) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage CI/CD variables | Create, read, update, and delete CI/CD variables. | [`admin_cicd_variables`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/143369) | Group,<br> Project | GitLab [16.10](https://gitlab.com/gitlab-org/gitlab/-/issues/437947) |
## Security policy management
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Allows linking security policy projects. | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Link to a security policy project | Allows linking security policy projects. | [`manage_security_policy_link`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148371) | Group,<br> Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/440226) |
## Source code management
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_merge_request`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128302) | Allows approval of merge requests. | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/412708) | | |
| [`admin_protected_branch`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162208) | Create, read, update, and delete protected branches for a project. | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/448823) | | |
| [`admin_push_rules`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147872) | Configure push rules for repositories at the group or project level. | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/421786) | | |
| [`read_code`](https://gitlab.com/gitlab-org/gitlab/-/issues/376180) | Allows read-only access to the source code in the user interface. Does not allow users to edit or download repository archives, clone or pull repositories, view source code in an IDE, or view merge requests for private projects. You can download individual files because read-only access inherently grants the ability to make a local copy of the file. | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/20277) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Approve merge request | Allows approval of merge requests. | [`admin_merge_request`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128302) | Project | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/412708) |
| | Create, read, update, and delete protected branches for a project. | [`admin_protected_branch`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162208) | Project | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/448823) |
| Manage push rules | Configure push rules for repositories at the group or project level. | [`admin_push_rules`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147872) | Group,<br> Project | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/421786) |
| View repository code | Allows read-only access to the source code in the user interface. Does not allow users to edit or download repository archives, clone or pull repositories, view source code in an IDE, or view merge requests for private projects. You can download individual files because read-only access inherently grants the ability to make a local copy of the file. | [`read_code`](https://gitlab.com/gitlab-org/gitlab/-/issues/376180) | Group,<br> Project | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/20277) |
## System access
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`manage_group_access_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115) | Create, read, update, and delete group access tokens. When creating a token, users with this custom permission must select a role for that token that has the same or fewer permissions as the default role used as the base for the custom role. | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/428353) | | |
| [`manage_project_access_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132342) | Create, read, update, and delete project access tokens. When creating a token, users with this custom permission must select a role for that token that has the same or fewer permissions as the default role used as the base for the custom role. | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage group access tokens | Create, read, update, and delete group access tokens. When creating a token, users with this custom permission must select a role for that token that has the same or fewer permissions as the default role used as the base for the custom role. | [`manage_group_access_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115) | Group | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/428353) |
| Manage project access tokens | Create, read, update, and delete project access tokens. When creating a token, users with this custom permission must select a role for that token that has the same or fewer permissions as the default role used as the base for the custom role. | [`manage_project_access_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132342) | Project | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) |
## Team planning
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`read_crm_contact`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154017) | Read CRM contact. | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/443268) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| View CRM contact | Read CRM contact. | [`read_crm_contact`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154017) | Group | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/443268) |
## Vulnerability management
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_vulnerability`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121534) | Edit the vulnerability object, including the status and linking an issue. Includes the `read_vulnerability` permission actions. | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/412536) | | |
| [`read_dependency`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126247) | Allows read-only access to the dependencies and licenses. | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/415255) | | |
| [`read_vulnerability`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120704) | Read vulnerability reports and security dashboards. | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/399119) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage vulnerabilities | Edit the vulnerability object, including the status and linking an issue. Includes the `read_vulnerability` permission actions. | [`admin_vulnerability`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121534) | Group,<br> Project | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/412536) |
| View dependency list | Allows read-only access to the dependencies and licenses. | [`read_dependency`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126247) | Group,<br> Project | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/415255) |
| View vulnerability reports and dashboards | Read vulnerability reports and security dashboards. | [`read_vulnerability`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120704) | Group,<br> Project | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/399119) |
## Webhooks
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| [`admin_web_hook`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151551) | Manage webhooks | GitLab [17.0](https://gitlab.com/gitlab-org/quality/triage-ops/-/issues/1373) | | |
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
| Manage web hooks | Manage webhooks | [`admin_web_hook`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151551) | Group,<br> Project | GitLab [17.0](https://gitlab.com/gitlab-org/quality/triage-ops/-/issues/1373) |

View File

@ -75,8 +75,12 @@ module Gitlab
end
end
# https://www.rubydoc.info/stdlib/core/IO:read says:
# When this method is called at end of file, it returns nil or "",
# depending on length: read, read(nil), and read(0) return "",
# read(positive_integer) returns nil.
def read(length = nil, outbuf = nil)
out = []
out = length&.positive? ? nil : []
length ||= size - tell
@ -87,15 +91,16 @@ module Gitlab
chunk_bytes = [BUFFER_SIZE - chunk_offset, length].min
data_slice = data.byteslice(0, chunk_bytes)
out ||= []
out << data_slice
@tell += data_slice.bytesize
length -= data_slice.bytesize
end
out = out.join
out = out&.join
# If outbuf is passed, we put the output into the buffer. This supports IO.copy_stream functionality
if outbuf
if outbuf && out
outbuf.replace(out)
end

View File

@ -32,7 +32,7 @@ module Gitlab
end
def enabled?
Gitlab.config.topology_service_enabled? && Gitlab.config.has_configured_cell?
Gitlab.config.topology_service_enabled?
end
end
end

View File

@ -39520,6 +39520,9 @@ msgstr ""
msgid "Organization|Home organization"
msgstr ""
msgid "Organization|If you proceed with this change you will lose your owner permissions for this organization, including access to this page."
msgstr ""
msgid "Organization|Internal - The organization can be accessed by any signed in user except external users."
msgstr ""

View File

@ -11,8 +11,6 @@ RSpec.describe 'Project Network Graph', :js, feature_category: :groups_and_proje
before do
sign_in(user)
stub_feature_flags(use_list_commits_rpc_network_graph: false)
project.repository.create_branch(ref_with_hash, 'master')
# Stub Graph max_size to speed up test (10 commits vs. 650)

View File

@ -241,6 +241,38 @@ RSpec.describe PersonalAccessTokensFinder, :enable_admin_mode, feature_category:
end
end
describe 'by expires before' do
where(:by_expires_before, :expected_tokens) do
2.days.ago | []
29.days.from_now | [:expired, :expired_impersonation]
30.days.from_now | ref(:tokens_keys)
end
with_them do
let(:params) { { expires_before: by_expires_before } }
it 'returns tokens by expires before' do
is_expected.to match_array(tokens.values_at(*expected_tokens))
end
end
end
describe 'by expires after' do
where(:by_expires_after, :expected_tokens) do
2.days.ago | ref(:tokens_keys)
30.days.from_now | [:active, :active_other, :revoked, :active_impersonation, :revoked_impersonation, :bot]
31.days.from_now | []
end
with_them do
let(:params) { { expires_after: by_expires_after } }
it 'returns tokens by expires after' do
is_expected.to match_array(tokens.values_at(*expected_tokens))
end
end
end
describe 'by last used date' do
before do
PersonalAccessToken.update_all(last_used_at: Time.now)

View File

@ -1,11 +1,11 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlCollapsibleListbox, GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlCollapsibleListbox, GlDrawer } from '@gitlab/ui';
import organizationUserUpdateResponseWithErrors from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql_with_errors.json';
import organizationUserUpdateResponse from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql.json';
import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import { pageInfoMultiplePages } from 'jest/organizations/mock_data';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
@ -31,16 +31,24 @@ describe('UserDetailsDrawer', () => {
const successfulResponseHandler = jest.fn().mockResolvedValue(organizationUserUpdateResponse);
const mockToastShow = jest.fn();
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlDrawer = () => wrapper.findComponent(GlDrawer);
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findUserAvatar = () => wrapper.findComponent(UserAvatar);
const findFooter = () => wrapper.findByTestId('user-details-drawer-footer');
const findSaveButton = () => wrapper.findByRole('button', { name: 'Save' });
const findCancelButton = () => wrapper.findByRole('button', { name: 'Cancel' });
const selectRole = (value) => findGlCollapsibleListbox().vm.$emit('select', value);
const createComponent = ({ props = {}, handler = successfulResponseHandler } = {}) => {
const createComponent = ({
mountFn = shallowMountExtended,
props = {},
handler = successfulResponseHandler,
} = {}) => {
mockApollo = createMockApollo([[organizationUserUpdateMutation, handler]]);
wrapper = shallowMount(UserDetailsDrawer, {
wrapper = mountFn(UserDetailsDrawer, {
propsData: {
user: mockUser,
pageInfo: pageInfoMultiplePages,
@ -94,6 +102,12 @@ describe('UserDetailsDrawer', () => {
});
});
it('does not render footer and action buttons', () => {
expect(findFooter().exists()).toBe(false);
expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
});
it('renders role listbox label', () => {
expect(findGlDrawer().text()).toContain('Organization role');
});
@ -141,89 +155,157 @@ describe('UserDetailsDrawer', () => {
});
describe('when selecting new role', () => {
const unselectedRole =
mockUser.accessLevel.stringValue === ACCESS_LEVEL_OWNER_STRING
? ACCESS_LEVEL_DEFAULT_STRING
: ACCESS_LEVEL_OWNER_STRING;
beforeEach(() => {
createComponent();
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
createComponent({ mountFn: mountExtended });
selectRole(unselectedRole);
});
it('calls GraphQL mutation with correct variables', () => {
expect(successfulResponseHandler).toHaveBeenCalledWith({
input: {
id: mockUser.gid,
accessLevel: ACCESS_LEVEL_DEFAULT_STRING,
},
it('renders footer and action buttons', () => {
expect(findFooter().exists()).toBe(true);
expect(findSaveButton().exists()).toBe(true);
expect(findCancelButton().exists()).toBe(true);
});
it('does not render remove self as owner warning', () => {
expect(findGlAlert().exists()).toBe(false);
});
describe('when active user is the current user', () => {
beforeEach(() => {
window.gon.current_user_id = mockUser.id;
});
describe('when removing self as owner', () => {
beforeEach(() => {
createComponent({
props: {
user: {
...mockUser,
accessLevel: { ...mockUser.accessLevel, stringValue: ACCESS_LEVEL_OWNER_STRING },
},
},
});
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
});
it('renders remove self as owner warning', () => {
expect(findGlAlert().text()).toBe(
'If you proceed with this change you will lose your owner permissions for this organization, including access to this page.',
);
});
});
});
it('sets listbox to loading', () => {
expect(findGlCollapsibleListbox().props('loading')).toBe(true);
});
it('emits loading start event', () => {
expect(wrapper.emitted('loading')[0]).toEqual([true]);
});
describe('when role update is successful', () => {
describe('when save button is clicked', () => {
beforeEach(async () => {
await waitForPromises();
await findSaveButton().trigger('click');
await nextTick();
});
it('shows toast when GraphQL mutation is successful', () => {
expect(mockToastShow).toHaveBeenCalledWith('Organization role was updated successfully.');
it('calls GraphQL mutation with correct variables', () => {
expect(successfulResponseHandler).toHaveBeenCalledWith({
input: {
id: mockUser.gid,
accessLevel: unselectedRole,
},
});
});
it('emits role-change event', () => {
expect(wrapper.emitted('role-change')).toHaveLength(1);
it('sets listbox to loading', () => {
expect(findGlCollapsibleListbox().props('loading')).toBe(true);
});
it('emits loading end event', () => {
expect(wrapper.emitted('loading')[1]).toEqual([false]);
it('emits loading start event', () => {
expect(wrapper.emitted('loading')[0]).toEqual([true]);
});
});
describe('when role update has a validation error', () => {
beforeEach(async () => {
const errorResponseHandler = jest
.fn()
.mockResolvedValue(organizationUserUpdateResponseWithErrors);
createComponent({
handler: errorResponseHandler,
props: { user: { ...mockUser, accessLevel: ACCESS_LEVEL_OWNER_STRING } },
describe('when role update is successful', () => {
beforeEach(async () => {
await waitForPromises();
});
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
await waitForPromises();
it('shows toast when GraphQL mutation is successful', () => {
expect(mockToastShow).toHaveBeenCalledWith(
'Organization role was updated successfully.',
);
});
it('emits role-change event', () => {
expect(wrapper.emitted('role-change')).toHaveLength(1);
});
it('emits loading end event', () => {
expect(wrapper.emitted('loading')[1]).toEqual([false]);
});
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'You cannot change the access of the last owner from the organization',
describe('when role update has a validation error', () => {
beforeEach(async () => {
const errorResponseHandler = jest
.fn()
.mockResolvedValue(organizationUserUpdateResponseWithErrors);
createComponent({
mountFn: mountExtended,
handler: errorResponseHandler,
});
selectRole(unselectedRole);
await nextTick();
await findSaveButton().trigger('click');
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'You cannot change the access of the last owner from the organization',
});
});
});
describe('when role update has a network error', () => {
const error = new Error();
beforeEach(async () => {
const errorResponseHandler = jest.fn().mockRejectedValue(error);
createComponent({
mountFn: mountExtended,
handler: errorResponseHandler,
});
selectRole(unselectedRole);
await nextTick();
await findSaveButton().trigger('click');
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred updating the organization role. Please try again.',
error,
captureError: true,
});
});
});
});
describe('when role update has a network error', () => {
const error = new Error();
describe('when cancel button is clicked', () => {
it('resets listbox to the initial user role', async () => {
await findCancelButton().trigger('click');
beforeEach(async () => {
const errorResponseHandler = jest.fn().mockRejectedValue(error);
createComponent({
handler: errorResponseHandler,
props: { user: { ...mockUser, accessLevel: ACCESS_LEVEL_OWNER_STRING } },
});
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred updating the organization role. Please try again.',
error,
captureError: true,
});
expect(findGlCollapsibleListbox().props('selected')).toBe(
mockUser.accessLevel.stringValue,
);
});
});
});

View File

@ -18,7 +18,8 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
sort: nil,
ids: nil,
with_issues_enabled: nil,
with_merge_requests_enabled: nil
with_merge_requests_enabled: nil,
with_namespace_domain_pages: nil
}
end
@ -121,6 +122,23 @@ RSpec.describe Resolvers::NamespaceProjectsResolver, feature_category: :groups_a
it { is_expected.to contain_exactly(*group_namespaced_projects) }
end
end
context 'with_namespace_domain_pages' do
before do
group_namespaced_projects[0...-1].each do |project|
project.project_setting.update!(pages_unique_domain_enabled: false)
end
group_namespaced_projects.last.project_setting.update!(
pages_unique_domain_enabled: true,
pages_unique_domain: 'foo123.example.com'
)
end
let(:args) { default_args.merge(with_namespace_domain_pages: true) }
let(:expected_projects) { group_namespaced_projects[0...-1] }
it { is_expected.to contain_exactly(*expected_projects) }
end
end
context 'with an user namespace' do

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'validate database config', feature_category: :cell do
include StubENV
let(:rails_configuration) { Rails::Application::Configuration.new(Rails.root) }
subject(:validate_config) do
load Rails.root.join('config/initializers/validate_cell_config.rb')
end
before do
allow(Rails.application).to receive(:config).and_return(rails_configuration)
end
shared_examples 'with SKIP_CELL_CONFIG_VALIDATION=true' do
before do
stub_env('SKIP_CELL_CONFIG_VALIDATION', 'true')
end
it 'does not raise exception' do
expect { validate_config }.not_to raise_error
end
end
context 'when topology service is correctly configured' do
before do
stub_config(cell: { id: 1 }, topology_service: { enabled: true })
end
it 'does not raise exception' do
expect { validate_config }.not_to raise_error
end
end
context 'when topology service is not configured' do
before do
stub_config(cell: { id: nil }, topology_service: { enabled: false })
end
it 'does not raise exception' do
expect { validate_config }.not_to raise_error
end
end
context 'when configuration is wrong' do
context 'when only cell.id is configured' do
before do
stub_config(cell: { id: 1 }, topology_service: { enabled: false })
end
it 'does not raise exception' do
expect { validate_config }.to raise_error("Topology Service is not configured, but Cell ID is set")
end
it_behaves_like 'with SKIP_CELL_CONFIG_VALIDATION=true'
end
context 'when only topology service is enabled' do
before do
stub_config(cell: { id: nil }, topology_service: { enabled: true })
end
it 'does not raise exception' do
expect { validate_config }.to raise_error("Topology Service is enabled, but Cell ID is not set")
end
it_behaves_like 'with SKIP_CELL_CONFIG_VALIDATION=true'
end
end
end

View File

@ -148,6 +148,26 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata, feature_category: :job_ar
it 'raises error' do
expect { find_entries }.to raise_error(described_class::InvalidStreamError, /not in gzip format/)
end
context 'when metadata is an HttpIO stream' do
let(:tmpfile) { Tempfile.new('test-metadata') }
let(:url) { "file://#{tmpfile.path}" }
let(:metadata_file_stream) { Gitlab::HttpIO.new(url, 0) }
before do
# Normally file:// URLs are not allowed, but bypass this for the sake of testing
# so we don't have to run a Web server.
allow(::Gitlab::UrlSanitizer).to receive(:valid?).with(url).and_return(true)
end
after do
tmpfile.unlink
end
it 'raises error' do
expect { find_entries }.to raise_error(described_class::InvalidStreamError, /not in gzip format/)
end
end
end
context 'with generated metadata' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::HttpIO do
RSpec.describe Gitlab::HttpIO, feature_category: :shared do
include HttpIOHelpers
let(:http_io) { described_class.new(url, size) }
@ -122,7 +122,24 @@ RSpec.describe Gitlab::HttpIO do
describe '#read' do
subject { http_io.read(length) }
shared_examples 'reads the body' do
let(:expected_outbuf) { expected_body || "" }
it 'reads a trace' do
is_expected.to eq(expected_body)
end
it 'reads with outbuf' do
buf = +""
expect(http_io.read(length, buf)).to eq(expected_body)
expect(buf).to eq(expected_outbuf)
end
end
context 'when there are no network issue' do
let(:expected_body) { file_body }
before do
stub_remote_url_206(url, file_path)
end
@ -135,9 +152,7 @@ RSpec.describe Gitlab::HttpIO do
set_smaller_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to eq(file_body)
end
it_behaves_like 'reads the body'
end
context 'when BUFFER_SIZE is larger than file size' do
@ -145,23 +160,20 @@ RSpec.describe Gitlab::HttpIO do
set_larger_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to eq(file_body)
end
it_behaves_like 'reads the body'
end
end
context 'when read only first 100 bytes' do
let(:length) { 100 }
let(:expected_body) { file_body[0, length] }
context 'when BUFFER_SIZE is smaller than file size' do
before do
set_smaller_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to eq(file_body[0, length])
end
it_behaves_like 'reads the body'
end
context 'when BUFFER_SIZE is larger than file size' do
@ -169,23 +181,20 @@ RSpec.describe Gitlab::HttpIO do
set_larger_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to eq(file_body[0, length])
end
it_behaves_like 'reads the body'
end
end
context 'when tries to read oversize' do
let(:length) { size + 1000 }
let(:expected_body) { file_body }
context 'when BUFFER_SIZE is smaller than file size' do
before do
set_smaller_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to eq(file_body)
end
it_behaves_like 'reads the body'
end
context 'when BUFFER_SIZE is larger than file size' do
@ -193,23 +202,20 @@ RSpec.describe Gitlab::HttpIO do
set_larger_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to eq(file_body)
end
it_behaves_like 'reads the body'
end
end
context 'when tries to read 0 bytes' do
let(:length) { 0 }
let(:expected_body) { "" }
context 'when BUFFER_SIZE is smaller than file size' do
before do
set_smaller_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to be_empty
end
it_behaves_like 'reads the body'
end
context 'when BUFFER_SIZE is larger than file size' do
@ -217,14 +223,30 @@ RSpec.describe Gitlab::HttpIO do
set_larger_buffer_size_than(size)
end
it 'reads a trace' do
is_expected.to be_empty
end
it_behaves_like 'reads the body'
end
end
end
context 'when there is anetwork issue' do
context 'when current pos is at end of the file' do
before do
http_io.seek(size, IO::SEEK_SET)
end
it 'returns nil when attempting to read a byte' do
expect(http_io.read(1)).to be_nil
end
it 'returns "" when attempting to read 0 bytes' do
expect(http_io.read(0)).to eq("")
end
it 'returns "" when attempting to read' do
expect(http_io.read).to eq("")
end
end
context 'when there is a network issue' do
let(:length) { nil }
before do

View File

@ -12,13 +12,6 @@ RSpec.describe Gitlab::TopologyServiceClient::BaseService, feature_category: :ce
expect { base_service }.to raise_error(NotImplementedError)
end
it 'raises an error when no cell is configured' do
allow(Gitlab.config.topology_service).to receive(:enabled).and_return(true)
expect(Gitlab.config.cell).to receive(:name).once.and_return(nil)
expect { base_service }.to raise_error(NotImplementedError)
end
end
end
end

View File

@ -22,13 +22,6 @@ RSpec.describe Gitlab::TopologyServiceClient::CellService, feature_category: :ce
expect { cell_service }.to raise_error(NotImplementedError)
end
it 'raises an error when no cell is configured' do
allow(Gitlab.config.topology_service).to receive(:enabled).and_return(true)
expect(Gitlab.config.cell).to receive(:name).once.and_return(nil)
expect { cell_service }.to raise_error(NotImplementedError)
end
end
context 'when topology service is enabled' do

View File

@ -45,7 +45,7 @@ RSpec.describe CapWorkspacesMaxTerminationToOneYear, feature_category: :workspac
desired_state: 'Terminated',
actual_state: 'Terminated',
editor: 'vs-code',
devfile_ref: 'devfile-ref',
project_ref: 'devfile-ref',
devfile_path: 'devfile-path',
devfile: 'devfile',
processed_devfile: 'processed_dev_file',
@ -68,7 +68,7 @@ RSpec.describe CapWorkspacesMaxTerminationToOneYear, feature_category: :workspac
desired_state: 'Running',
actual_state: 'Running',
editor: 'vs-code',
devfile_ref: 'devfile-ref',
project_ref: 'devfile-ref',
devfile_path: 'devfile-path',
devfile: 'devfile',
processed_devfile: 'processed_dev_file',

View File

@ -3,49 +3,5 @@
require 'spec_helper'
RSpec.describe Integrations::Assembla, feature_category: :integrations do
include StubRequests
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { described_class.new }
end
describe 'Validations' do
context 'when active' do
before do
subject.active = true
end
it { is_expected.to validate_presence_of :token }
end
context 'when inactive' do
it { is_expected.not_to validate_presence_of :token }
end
end
describe "#execute" do
let_it_be(:user) { build(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:assembla_integration) { described_class.new }
let(:sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
let(:api_url) { 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret' }
before do
allow(assembla_integration).to receive_messages(
project_id: project.id,
project: project,
token: 'verySecret',
subdomain: 'project_name'
)
stub_full_request(api_url, method: :post)
end
it "calls Assembla API" do
assembla_integration.execute(sample_data)
expect(WebMock).to have_requested(:post, stubbed_hostname(api_url)).with(
body: /#{sample_data[:before]}.*#{sample_data[:after]}.*#{project.path}/
).once
end
end
it_behaves_like Integrations::Base::Assembla
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Integrations::Instance::Assembla, feature_category: :integrations do
it_behaves_like Integrations::Base::Assembla
end

View File

@ -52,46 +52,26 @@ RSpec.describe Network::Graph, feature_category: :source_code_management do
describe '#commits' do
let(:graph) { described_class.new(project, 'refs/heads/master', project.repository.commit, nil) }
context 'when use_list_commits_rpc_network_graph FF is enabled' do
let(:opts) do
{
revisions: %w[--tags --branches],
pagination_params: { limit: 650 },
reverse: false,
order: :date,
ref: 'refs/heads/master',
skip: 0
}
end
before do
stub_feature_flags(use_list_commits_rpc_network_graph: true)
end
it 'only fetches the commits once using `list_all`', :request_store do
expect(Gitlab::Git::Commit).to receive(:list_all)
.with(project.repository.raw_repository, opts)
.once
.and_call_original
graph
end
it_behaves_like 'a collection of commits'
let(:opts) do
{
revisions: %w[--tags --branches],
pagination_params: { limit: 650 },
reverse: false,
order: :date,
ref: 'refs/heads/master',
skip: 0
}
end
context 'when use_list_commits_rpc_network_graph FF is disabled' do
before do
stub_feature_flags(use_list_commits_rpc_network_graph: false)
end
it 'only fetches the commits once using `list_all`', :request_store do
expect(Gitlab::Git::Commit).to receive(:list_all)
.with(project.repository.raw_repository, opts)
.once
.and_call_original
it 'only fetches the commits once using `find_all`', :request_store do
expect(Gitlab::Git::Commit).to receive(:find_all).once.and_call_original
graph
end
it_behaves_like 'a collection of commits'
graph
end
it_behaves_like 'a collection of commits'
end
end

View File

@ -120,6 +120,25 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
end
end
describe 'expires scopes', :time_freeze do
let!(:expires_last_month_token) { create(:personal_access_token, expires_at: 1.month.ago) }
let!(:expires_next_month_token) { create(:personal_access_token, expires_at: 1.month.from_now) }
let!(:expires_two_months_token) { create(:personal_access_token, expires_at: 2.months.from_now) }
describe '.expires_before' do
it 'finds tokens that expire before or on date' do
expect(described_class.expires_before(1.month.ago)).to contain_exactly(expires_last_month_token)
end
end
describe '.expires_after' do
it 'finds tokens that expires after or on date' do
expect(described_class.expires_after(1.month.from_now.beginning_of_hour))
.to contain_exactly(expires_next_month_token, expires_two_months_token)
end
end
end
describe '.last_used_before' do
context 'last_used_*' do
let_it_be(:date) { DateTime.new(2022, 01, 01) }

View File

@ -3116,6 +3116,28 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
end
end
describe '#pages_unique_domain_enabled?' do
let(:project) { create(:project) }
subject { project.pages_unique_domain_enabled? }
context 'if unique domain is enabled' do
before do
project.project_setting.update!(pages_unique_domain_enabled: true, pages_unique_domain: 'foo123.example.com')
end
it { is_expected.to be(true) }
end
context 'if unique domain is disabled' do
before do
project.project_setting.update!(pages_unique_domain_enabled: false)
end
it { is_expected.to be(false) }
end
end
describe '#default_branch_protected?' do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, namespace: namespace) }
@ -9840,4 +9862,32 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
expect(project.uploads_sharding_key).to eq(namespace_id: namespace.id)
end
end
describe '#pages_domain_present?' do
let_it_be(:project) { create(:project) }
before do
allow(project).to receive(:pages_url).and_return('https://example.com')
end
context 'when the domain matches pages_url' do
it 'returns true' do
expect(project.pages_domain_present?('https://example.com')).to be(true)
end
end
context 'when the domain exists in pages_domains' do
let!(:pages_domain) { create(:pages_domain, project: project, domain: 'custom.com') }
it 'returns true' do
expect(project.pages_domain_present?('https://custom.com')).to be(true)
end
end
context 'when the domain does not match pages_url or pages_domains' do
it 'returns false' do
expect(project.pages_domain_present?('https://unknown.com')).to be(false)
end
end
end
end

View File

@ -110,7 +110,8 @@ RSpec.describe API::Pages, feature_category: :pages do
context 'and updates pages primary domain' do
let(:domain) { 'my.domain.com' }
let(:params) { { pages_primary_domain: domain } }
let(:pages_primary_domain_url) { 'https://my.domain.com' }
let(:params) { { pages_primary_domain: pages_primary_domain_url } }
before do
create(:pages_domain, project: project, domain: domain)
@ -120,7 +121,7 @@ RSpec.describe API::Pages, feature_category: :pages do
patch api(path, admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['pages_primary_domain']).to eq(domain)
expect(json_response['pages_primary_domain']).to eq(pages_primary_domain_url)
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
RSpec.shared_examples Integrations::Base::Assembla do
include StubRequests
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { described_class.new }
end
describe 'Validations' do
context 'when active' do
before do
subject.active = true
end
it { is_expected.to validate_presence_of :token }
end
context 'when inactive' do
it { is_expected.not_to validate_presence_of :token }
end
end
describe "#execute" do
let_it_be(:user) { build(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:assembla_integration) { described_class.new }
let(:sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
let(:api_url) { 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret' }
it "calls Assembla API" do
allow(assembla_integration).to receive_messages(
project_id: project.id,
project: project,
token: 'verySecret',
subdomain: 'project_name'
)
stub_full_request(api_url, method: :post).with(body: { payload: sample_data })
assembla_integration.execute(sample_data)
expect(WebMock).to have_requested(:post, stubbed_hostname(api_url)).with(
body: /#{sample_data[:before]}.*#{sample_data[:after]}.*#{project.path}/
).once
end
end
end

View File

@ -156,7 +156,6 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor
before do
allow(Settings).to receive(:topology_service_enabled?).and_return(topology_service_enabled)
allow(Settings).to receive(:has_configured_cell?).and_return(configured_cell)
allow(Settings).to receive(:skip_sequence_alteration?).and_return(skip_sequence_alteration)
end

View File

@ -18,6 +18,13 @@
<% return unless ability[:feature_flag] %>
<% "`#{ability[:feature_flag]}`" %>
<% end %>
<% def scope(ability) %>
<% scopes = [] %>
<% scopes << 'Instance' if ability[:admin_ability]%>
<% scopes << 'Group' if ability[:group_ability]%>
<% scopes << 'Project' if ability[:project_ability]%>
<% return scopes.join(',<br> ') %>
<% end %>
---
stage: Software Supply Chain Security
group: Authorization
@ -46,9 +53,9 @@ Any dependencies are noted in the `Description` column for each permission.
## <%= "#{humanize(category)}" %>
| Name | Description | Introduced | Feature flag | Enabled |
|:-----|:------------|:-----------|:-------------|:--------|
| Permission | Description | API Attribute | Scope | Introduced |
|:-----------|:------------|:--------------|:------|:-----------|
<% abilities.each do |name, ability| %>
| <%= "[`#{name}`](#{ability[:introduced_by_mr]})" %> | <%= ability[:description] %> | GitLab <%= "[#{ability[:milestone]}](#{ability[:introduced_by_issue]})" %> | <%= feature_flag(ability) %> | <%= enabled_link(ability) %> |
| <%= ability[:title] %> | <%= ability[:description] %> | <%= "[`#{name}`](#{ability[:introduced_by_mr]})" %> | <%= scope(ability) %> | GitLab <%= "[#{ability[:milestone]}](#{ability[:introduced_by_issue]})" %> |
<% end %>
<% end %>