Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-04-15 15:10:58 +00:00
parent b4337101a9
commit 3456ec38df
54 changed files with 872 additions and 115 deletions

View File

@ -35,7 +35,7 @@ export default {
shortcuts: __('Keyboard shortcuts'),
version: __('Your GitLab version'),
whatsnew: __("What's new"),
chat: s__('TanukiBot|GitLab Duo Chat'),
chat: s__('DuoChat|GitLab Duo Chat'),
},
props: {
sidebarData: {

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module Resolvers
module Ml
class FindModelVersionResolver < Resolvers::BaseResolver
extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
type ::Types::Ml::ModelType, null: true
argument :model_version_id, ::Types::GlobalIDType[::Ml::ModelVersion],
required: false,
description: 'Id of the version to be fetched.'
def resolve(model_version_id:)
Gitlab::Graphql::Lazy.with_value(find_object(id: model_version_id)) do |model_version|
model_version if Ability.allowed?(current_user, :read_model_registry, model_version&.project) &&
model_version.model_id == object.id
end
end
def find_object(id:)
GitlabSchema.find_by_gid(id)
end
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Types
module Ml
# rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver
class CandidateMetadataType < ::Types::BaseObject
graphql_name 'MlCandidateMetadata'
description 'Metadata for a candidate in the model registry'
connection_type_class Types::LimitedCountableConnectionType
field :id, ::Types::GlobalIDType[::Ml::CandidateMetadata], null: false, description: 'ID of the metadata.'
field :name, ::GraphQL::Types::String,
null: true,
description: 'Name of the metadata entry.'
field :value, ::GraphQL::Types::String,
null: false,
description: 'Value set for the metadata entry.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Types
module Ml
# rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver
class CandidateMetricType < ::Types::BaseObject
graphql_name 'MlCandidateMetric'
description 'Metric for a candidate in the model registry'
connection_type_class Types::LimitedCountableConnectionType
field :id, ::Types::GlobalIDType[::Ml::CandidateMetric], null: false, description: 'ID of the metric.'
field :name, ::GraphQL::Types::String,
null: true,
description: 'Name of the metric.'
field :step, ::GraphQL::Types::Int,
null: false,
description: 'Step at which the metric was measured.'
field :value, ::GraphQL::Types::Float,
null: false,
description: 'Value set for the metric.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Types
module Ml
# rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver
class CandidateParamType < ::Types::BaseObject
graphql_name 'MlCandidateParam'
description 'Parameter for a candidate in the model registry'
connection_type_class Types::LimitedCountableConnectionType
field :id, ::Types::GlobalIDType[::Ml::CandidateParam], null: false, description: 'ID of the parameter.'
field :name, ::GraphQL::Types::String,
null: true,
description: 'Name of the parameter.'
field :value, ::GraphQL::Types::String,
null: false,
description: 'Value set for the parameter.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -31,6 +31,18 @@ module Types
field :created_at, Types::TimeType, null: false, description: 'Date of creation.'
field :params, ::Types::Ml::CandidateParamType.connection_type,
null: false,
description: 'Parameters for the candidate.'
field :metrics, ::Types::Ml::CandidateMetricType.connection_type,
null: false,
description: 'Metrics for the candidate.'
field :metadata, ::Types::Ml::CandidateMetadataType.connection_type,
null: false,
description: 'Metadata entries for the candidate.'
field :ci_job, ::Types::Ci::JobType,
null: true,
description: 'CI information about the job that created the candidate.'

View File

@ -38,6 +38,10 @@ module Types
field :candidates, ::Types::Ml::CandidateType.connection_type, null: true,
description: 'Version candidates of the model.'
field :version, ::Types::Ml::ModelVersionType, null: true,
description: 'Version of the model.',
resolver: ::Resolvers::Ml::FindModelVersionResolver
end
# rubocop: enable Graphql/AuthorizeTypes
end

View File

@ -14,6 +14,10 @@ module Types
field :created_at, Types::TimeType, null: false, description: 'Date of creation.'
field :version, ::GraphQL::Types::String, null: false, description: 'Name of the version.'
field :package_id, ::Types::GlobalIDType[::Packages::Package],
null: false,
description: 'Package for model version artifacts.'
field :candidate, ::Types::Ml::CandidateType,
null: false,
description: 'Metrics, params and metadata for the model version.'

View File

@ -618,8 +618,7 @@ module Types
resolver: Resolvers::Projects::ForkDetailsResolver,
description: 'Details of the fork project compared to its upstream project.'
field :branch_rules,
Types::Projects::BranchRuleType.connection_type,
field :branch_rules, Types::Projects::BranchRuleType.connection_type,
null: true,
description: "Branch rules configured for the project.",
resolver: Resolvers::Projects::BranchRulesResolver

View File

@ -43,12 +43,12 @@ module Types
field :created_at,
Types::TimeType,
null: false,
null: true,
description: 'Timestamp of when the branch rule was created.'
field :updated_at,
Types::TimeType,
null: false,
null: true,
description: 'Timestamp of when the branch rule was last updated.'
end
end

View File

@ -60,9 +60,10 @@ module Emails
end
# rubocop: enable CodeReuse/ActiveRecord
def resource_access_tokens_about_to_expire_email(recipient, resource, token_names)
# resource owners are sent mail about expiring access tokens which belong to a bot user
def bot_resource_access_token_about_to_expire_email(recipient, resource, token_name)
@user = recipient
@token_names = token_names
@token_name = token_name
@days_to_expire = PersonalAccessToken::DAYS_TO_EXPIRE
@resource = resource
if resource.is_a?(Group)

View File

@ -65,7 +65,7 @@ class NotifyPreview < ActionMailer::Preview
end
def resource_access_token_about_to_expire_email
Notify.resource_access_tokens_about_to_expire_email(user, group, ['token_name'])
Notify.bot_resource_access_token_about_to_expire_email(user, group, 'token_name')
end
def access_token_created_email

View File

@ -8,7 +8,7 @@ module Projects
attr_reader :project, :protected_branch
alias_method :branch_protection, :protected_branch
def_delegators(:protected_branch, :id, :name, :group, :default_branch?, :created_at, :updated_at)
def_delegators(:protected_branch, :id, :name, :group, :default_branch?, :created_at, :updated_at, :persisted?)
def self.find(id)
protected_branch = ProtectedBranch.find(id)

View File

@ -75,15 +75,15 @@ class NotificationService
end
end
def resource_access_tokens_about_to_expire(bot_user, token_names)
def bot_resource_access_token_about_to_expire(bot_user, token_name)
recipients = bot_user.resource_bot_owners.select { |owner| owner.can?(:receive_notifications) }
resource = bot_user.resource_bot_resource
recipients.each do |recipient|
mailer.resource_access_tokens_about_to_expire_email(
mailer.bot_resource_access_token_about_to_expire_email(
recipient,
resource,
token_names
token_name
).deliver_later
end
end

View File

@ -0,0 +1,10 @@
%p
= _('Hi %{username}!') % { username: sanitize_name(@user.name) }
%p
- code_tag_pair = tag_pair(tag.code, :codeOpen, :codeClose)
= safe_format(_('Your %{resource_type} access token %{codeOpen}%{token_name}%{codeClose} for %{codeOpen}%{resource_path}%{codeClose} will expire in %{days_to_expire} or less.'), code_tag_pair, days_to_expire: pluralize(@days_to_expire, _('day')), token_name: @token_name, resource_path: @resource.full_path, resource_type: @resource.class.name)
%p
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
= html_escape(_('You can create a new one or check them in your %{link_start}Access Tokens%{link_end} settings.')) % { link_start: link_start, link_end: '</a>'.html_safe }
%p
= @reason_text

View File

@ -0,0 +1,7 @@
<%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %>
<%= _('Your %{resource_type} access token %{token_name} for %{resource_path} will expire in %{days_to_expire} or less.') % { days_to_expire: pluralize(@days_to_expire, _('day')), token_name: @token_name, resource_path: "#{@resource.class.name.titleize}: #{@resource.full_path}", resource_type: "#{@resource.class.name.titleize}" }%>
<%= _('You can create a new one or check them in your access token settings: %{target_url}') % { target_url: @target_url } %>
<%= @reason_text %>

View File

@ -1,15 +0,0 @@
%p
= _('Hi %{username}!') % { username: sanitize_name(@user.name) }
%p
= _('One or more of your resource access tokens will expire in %{days_to_expire} or less:') % { days_to_expire: pluralize(@days_to_expire, _('day')) }
%p
#{@resource.class.name.titleize}: #{@resource.full_path}
%p
%ul
- @token_names.each do |token|
%li= token
%p
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
= html_escape(_('You can create a new one or check them in your %{link_start}access tokens%{link_end} settings.')) % { link_start: link_start, link_end: '</a>'.html_safe }
%p
= @reason_text

View File

@ -1,13 +0,0 @@
<%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %>
<%= _('One or more of your resource access tokens will expire in %{days_to_expire} or less:') % { days_to_expire: pluralize(@days_to_expire, _('day')) } %>
<%= "#{@resource.class.name.titleize}: #{@resource.full_path}" %>
<% @token_names.each do |token| %>
- <%= token %>
<% end %>
<%= _('You can create a new one or check them in your access token settings: %{target_url}') % { target_url: @target_url } %>
<%= @reason_text %>

View File

@ -20,6 +20,7 @@
- own_projects_illustration_path = 'illustrations/empty-state/empty-projects-md.svg'
- own_projects_current_user_empty_message_header = s_('UserProfile|You haven\'t created any personal projects')
- own_projects_current_user_empty_message_description = s_('UserProfile|Your projects can be available publicly, internally, or privately, at your choice.')
- own_projects_current_user_cant_create_description = s_("UserProfile|You cannot create projects in your personal namespace. Contact your GitLab administrator.")
- own_projects_visitor_empty_message = s_('UserProfile|There are no projects available to be displayed here')
- explore_page_empty_message = s_('UserProfile|Explore public groups to find projects to contribute to')
- new_project_button_label = _('New project')
@ -69,9 +70,10 @@
primary_button_link: explore_projects_button_link,
visitor_empty_message: starred_projects_visitor_empty_message }
- else
- user_has_limits = current_user&.projects_limit.to_i > 0
= render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: own_projects_illustration_path,
current_user_empty_message_header: own_projects_current_user_empty_message_header,
current_user_empty_message_description: own_projects_current_user_empty_message_description,
primary_button_label: new_project_button_label,
primary_button_link: new_project_button_link,
current_user_empty_message_description: user_has_limits ? own_projects_current_user_empty_message_description : own_projects_current_user_cant_create_description,
primary_button_label: user_has_limits ? new_project_button_label : nil,
primary_button_link: user_has_limits ? new_project_button_link : nil,
visitor_empty_message: defined?(explore_page) && explore_page ? explore_page_empty_message : own_projects_visitor_empty_message }

View File

@ -50,7 +50,7 @@ module PersonalAccessTokens
# We're limiting to 100 tokens so we avoid loading too many tokens into memory.
# At the time of writing this would only affect 69 users on GitLab.com
deliver_user_notifications(token_names, user)
deliver_user_notifications(user, token_names)
expiring_user_tokens.update_all(expire_notification_delivered: true)
end
@ -77,7 +77,7 @@ module PersonalAccessTokens
bot_users.each do |user|
with_context(user: user) do
expiring_user_token = user.personal_access_tokens.first
expiring_user_token = user.personal_access_tokens.first # bot user should not have more than 1 token
execute_web_hooks(expiring_user_token, user)
deliver_bot_notifications(expiring_user_token.name, user)
@ -91,8 +91,8 @@ module PersonalAccessTokens
# rubocop: enable CodeReuse/ActiveRecord
end
def deliver_bot_notifications(token_names, user)
notification_service.resource_access_tokens_about_to_expire(user, token_names)
def deliver_bot_notifications(token_name, user)
notification_service.bot_resource_access_token_about_to_expire(user, token_name)
Gitlab::AppLogger.info(
message: "Notifying Bot User resource owners about expiring tokens",
@ -101,7 +101,7 @@ module PersonalAccessTokens
)
end
def deliver_user_notifications(token_names, user)
def deliver_user_notifications(user, token_names)
notification_service.access_token_about_to_expire(user, token_names)
Gitlab::AppLogger.info(

View File

@ -7,4 +7,13 @@ feature_categories:
description: Stores metadata about exported Vulnerabilities CSV files
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27196
milestone: '13.0'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:
project_id: projects
group_id: namespaces

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddIndexForMemberApprovalsMemberNamespaceIdStatus < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '16.11'
INDEX_NAME = 'index_member_approvals_on_member_namespace_id_status'
def up
add_concurrent_index :member_approvals, [:member_namespace_id, :status], where: 'status = 0', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :member_approvals, INDEX_NAME
end
end

View File

@ -0,0 +1 @@
90dda8138e958b31a29a180fe5023417418da6862d8621e7b89d80e5d8abd3da

View File

@ -25902,6 +25902,8 @@ CREATE INDEX index_member_approval_on_requested_by_id ON member_approvals USING
CREATE INDEX index_member_approval_on_reviewed_by_id ON member_approvals USING btree (reviewed_by_id);
CREATE INDEX index_member_approvals_on_member_namespace_id_status ON member_approvals USING btree (member_namespace_id, status) WHERE (status = 0);
CREATE INDEX index_member_approvals_on_member_role_id ON member_approvals USING btree (member_role_id);
CREATE INDEX index_member_approvals_on_user_id ON member_approvals USING btree (user_id);

View File

@ -470,7 +470,7 @@ To delete only the custom HTTP headers for a streaming destination:
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Monitoring > Audit Events**.
1. On the main area, select the **Streams** tab.
1. To the right of the item, **Edit** (**{pencil}**).
1. To the right of the item, select **Edit** (**{pencil}**).
1. Locate the **Custom HTTP headers** table.
1. Locate the header that you wish to remove.
1. To the right of the header, select **Delete** (**{remove}**).

View File

@ -12843,6 +12843,117 @@ The edge type for [`MlCandidate`](#mlcandidate).
| <a id="mlcandidateedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="mlcandidateedgenode"></a>`node` | [`MlCandidate`](#mlcandidate) | The item at the end of the edge. |
#### `MlCandidateMetadataConnection`
The connection type for [`MlCandidateMetadata`](#mlcandidatemetadata).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetadataconnectionedges"></a>`edges` | [`[MlCandidateMetadataEdge]`](#mlcandidatemetadataedge) | A list of edges. |
| <a id="mlcandidatemetadataconnectionnodes"></a>`nodes` | [`[MlCandidateMetadata]`](#mlcandidatemetadata) | A list of nodes. |
| <a id="mlcandidatemetadataconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
##### Fields with arguments
###### `MlCandidateMetadataConnection.count`
Limited count of collection. Returns limit + 1 for counts greater than the limit.
Returns [`Int!`](#int).
####### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetadataconnectioncountlimit"></a>`limit` | [`Int`](#int) | Limit value to be applied to the count query. Default is 1000. |
#### `MlCandidateMetadataEdge`
The edge type for [`MlCandidateMetadata`](#mlcandidatemetadata).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetadataedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="mlcandidatemetadataedgenode"></a>`node` | [`MlCandidateMetadata`](#mlcandidatemetadata) | The item at the end of the edge. |
#### `MlCandidateMetricConnection`
The connection type for [`MlCandidateMetric`](#mlcandidatemetric).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetricconnectionedges"></a>`edges` | [`[MlCandidateMetricEdge]`](#mlcandidatemetricedge) | A list of edges. |
| <a id="mlcandidatemetricconnectionnodes"></a>`nodes` | [`[MlCandidateMetric]`](#mlcandidatemetric) | A list of nodes. |
| <a id="mlcandidatemetricconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
##### Fields with arguments
###### `MlCandidateMetricConnection.count`
Limited count of collection. Returns limit + 1 for counts greater than the limit.
Returns [`Int!`](#int).
####### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetricconnectioncountlimit"></a>`limit` | [`Int`](#int) | Limit value to be applied to the count query. Default is 1000. |
#### `MlCandidateMetricEdge`
The edge type for [`MlCandidateMetric`](#mlcandidatemetric).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetricedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="mlcandidatemetricedgenode"></a>`node` | [`MlCandidateMetric`](#mlcandidatemetric) | The item at the end of the edge. |
#### `MlCandidateParamConnection`
The connection type for [`MlCandidateParam`](#mlcandidateparam).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidateparamconnectionedges"></a>`edges` | [`[MlCandidateParamEdge]`](#mlcandidateparamedge) | A list of edges. |
| <a id="mlcandidateparamconnectionnodes"></a>`nodes` | [`[MlCandidateParam]`](#mlcandidateparam) | A list of nodes. |
| <a id="mlcandidateparamconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
##### Fields with arguments
###### `MlCandidateParamConnection.count`
Limited count of collection. Returns limit + 1 for counts greater than the limit.
Returns [`Int!`](#int).
####### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidateparamconnectioncountlimit"></a>`limit` | [`Int`](#int) | Limit value to be applied to the count query. Default is 1000. |
#### `MlCandidateParamEdge`
The edge type for [`MlCandidateParam`](#mlcandidateparam).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidateparamedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="mlcandidateparamedgenode"></a>`node` | [`MlCandidateParam`](#mlcandidateparam) | The item at the end of the edge. |
#### `MlModelConnection`
The connection type for [`MlModel`](#mlmodel).
@ -16581,14 +16692,14 @@ Branch rules configured for a rule target.
| ---- | ---- | ----------- |
| <a id="branchruleapprovalrules"></a>`approvalRules` | [`ApprovalProjectRuleConnection`](#approvalprojectruleconnection) | Merge request approval rules configured for this branch rule. (see [Connections](#connections)) |
| <a id="branchrulebranchprotection"></a>`branchProtection` | [`BranchProtection`](#branchprotection) | Branch protections configured for this branch rule. |
| <a id="branchrulecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the branch rule was created. |
| <a id="branchrulecreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the branch rule was created. |
| <a id="branchruleexternalstatuschecks"></a>`externalStatusChecks` | [`ExternalStatusCheckConnection`](#externalstatuscheckconnection) | External status checks configured for this branch rule. (see [Connections](#connections)) |
| <a id="branchruleid"></a>`id` | [`ProjectsBranchRuleID`](#projectsbranchruleid) | ID of the branch rule. |
| <a id="branchruleisdefault"></a>`isDefault` | [`Boolean!`](#boolean) | Check if this branch rule protects the project's default branch. |
| <a id="branchruleisprotected"></a>`isProtected` | [`Boolean!`](#boolean) | Check if this branch rule protects access for the branch. |
| <a id="branchrulematchingbranchescount"></a>`matchingBranchesCount` | [`Int!`](#int) | Number of existing branches that match this branch rule. |
| <a id="branchrulename"></a>`name` | [`String!`](#string) | Name of the branch rule target. Includes wildcards. |
| <a id="branchruleupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the branch rule was last updated. |
| <a id="branchruleupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of when the branch rule was last updated. |
### `BurnupChartDailyTotals`
@ -24616,9 +24727,49 @@ Candidate for a model version in the model registry.
| <a id="mlcandidateeid"></a>`eid` | [`String!`](#string) | MLflow uuid for the candidate. |
| <a id="mlcandidateid"></a>`id` | [`MlCandidateID!`](#mlcandidateid) | ID of the candidate. |
| <a id="mlcandidateiid"></a>`iid` | [`Int!`](#int) | IID of the candidate scoped to project. |
| <a id="mlcandidatemetadata"></a>`metadata` | [`MlCandidateMetadataConnection!`](#mlcandidatemetadataconnection) | Metadata entries for the candidate. (see [Connections](#connections)) |
| <a id="mlcandidatemetrics"></a>`metrics` | [`MlCandidateMetricConnection!`](#mlcandidatemetricconnection) | Metrics for the candidate. (see [Connections](#connections)) |
| <a id="mlcandidatename"></a>`name` | [`String`](#string) | Name of the candidate. |
| <a id="mlcandidateparams"></a>`params` | [`MlCandidateParamConnection!`](#mlcandidateparamconnection) | Parameters for the candidate. (see [Connections](#connections)) |
| <a id="mlcandidatestatus"></a>`status` | [`String`](#string) | Candidate status. |
### `MlCandidateMetadata`
Metadata for a candidate in the model registry.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetadataid"></a>`id` | [`MlCandidateMetadataID!`](#mlcandidatemetadataid) | ID of the metadata. |
| <a id="mlcandidatemetadataname"></a>`name` | [`String`](#string) | Name of the metadata entry. |
| <a id="mlcandidatemetadatavalue"></a>`value` | [`String!`](#string) | Value set for the metadata entry. |
### `MlCandidateMetric`
Metric for a candidate in the model registry.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidatemetricid"></a>`id` | [`MlCandidateMetricID!`](#mlcandidatemetricid) | ID of the metric. |
| <a id="mlcandidatemetricname"></a>`name` | [`String`](#string) | Name of the metric. |
| <a id="mlcandidatemetricstep"></a>`step` | [`Int!`](#int) | Step at which the metric was measured. |
| <a id="mlcandidatemetricvalue"></a>`value` | [`Float!`](#float) | Value set for the metric. |
### `MlCandidateParam`
Parameter for a candidate in the model registry.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlcandidateparamid"></a>`id` | [`MlCandidateParamID!`](#mlcandidateparamid) | ID of the parameter. |
| <a id="mlcandidateparamname"></a>`name` | [`String`](#string) | Name of the parameter. |
| <a id="mlcandidateparamvalue"></a>`value` | [`String!`](#string) | Value set for the parameter. |
### `MlModel`
Machine learning model in the model registry.
@ -24639,6 +24790,18 @@ Machine learning model in the model registry.
#### Fields with arguments
##### `MlModel.version`
Version of the model.
Returns [`MlModelVersion`](#mlmodelversion).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mlmodelversionmodelversionid"></a>`modelVersionId` | [`MlModelVersionID`](#mlmodelversionid) | Id of the version to be fetched. |
##### `MlModel.versions`
Versions of the model.
@ -24669,6 +24832,7 @@ Version of a machine learning model.
| <a id="mlmodelversioncandidate"></a>`candidate` | [`MlCandidate!`](#mlcandidate) | Metrics, params and metadata for the model version. |
| <a id="mlmodelversioncreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. |
| <a id="mlmodelversionid"></a>`id` | [`MlModelVersionID!`](#mlmodelversionid) | ID of the model version. |
| <a id="mlmodelversionpackageid"></a>`packageId` | [`PackagesPackageID!`](#packagespackageid) | Package for model version artifacts. |
| <a id="mlmodelversionversion"></a>`version` | [`String!`](#string) | Name of the version. |
### `MonthlyUsage`
@ -25942,7 +26106,6 @@ Check permissions for the current user on a vulnerability finding.
| <a id="projectarchived"></a>`archived` | [`Boolean`](#boolean) | Indicates the archived status of the project. |
| <a id="projectautoclosereferencedissues"></a>`autocloseReferencedIssues` | [`Boolean`](#boolean) | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically. |
| <a id="projectavatarurl"></a>`avatarUrl` | [`String`](#string) | URL to avatar image file of the project. |
| <a id="projectbranchrules"></a>`branchRules` | [`BranchRuleConnection`](#branchruleconnection) | Branch rules configured for the project. (see [Connections](#connections)) |
| <a id="projectciaccessauthorizedagents"></a>`ciAccessAuthorizedAgents` | [`ClusterAgentAuthorizationCiAccessConnection`](#clusteragentauthorizationciaccessconnection) | Authorized cluster agents for the project through ci_access keyword. (see [Connections](#connections)) |
| <a id="projectcicdsettings"></a>`ciCdSettings` | [`ProjectCiCdSetting`](#projectcicdsetting) | CI/CD settings for the project. |
| <a id="projectciconfigpathordefault"></a>`ciConfigPathOrDefault` | [`String!`](#string) | Path of the CI configuration file. |
@ -26261,6 +26424,22 @@ four standard [pagination arguments](#pagination-arguments):
| ---- | ---- | ----------- |
| <a id="projectboardsid"></a>`id` | [`BoardID`](#boardid) | Find a board by its ID. |
##### `Project.branchRules`
Branch rules configured for the project.
Returns [`BranchRuleConnection`](#branchruleconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#pagination-arguments):
`before: String`, `after: String`, `first: Int`, and `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectbranchrulesbuildmissing"></a>`buildMissing` | [`Boolean`](#boolean) | Return unpersisted custom branch rules. |
##### `Project.ciConfigVariables`
CI/CD config variable.
@ -34873,6 +35052,24 @@ A `MlCandidateID` is a global ID. It is encoded as a string.
An example `MlCandidateID` is: `"gid://gitlab/Ml::Candidate/1"`.
### `MlCandidateMetadataID`
A `MlCandidateMetadataID` is a global ID. It is encoded as a string.
An example `MlCandidateMetadataID` is: `"gid://gitlab/Ml::CandidateMetadata/1"`.
### `MlCandidateMetricID`
A `MlCandidateMetricID` is a global ID. It is encoded as a string.
An example `MlCandidateMetricID` is: `"gid://gitlab/Ml::CandidateMetric/1"`.
### `MlCandidateParamID`
A `MlCandidateParamID` is a global ID. It is encoded as a string.
An example `MlCandidateParamID` is: `"gid://gitlab/Ml::CandidateParam/1"`.
### `MlModelID`
A `MlModelID` is a global ID. It is encoded as a string.

View File

@ -319,8 +319,7 @@ For example, users on GitLab Dedicated don't have to have a different and unique
### Can different Cells communicate with each other?
Up until iteration 3, Cells communicate with each other only via a shared database that contains common data.
In iteration 4 we are going to evaluate the option of Cells calling each other via API to provide more isolation and reliability.
Not directly, our goal is to keep them isolated and only communicate using global services.
### How are Cells provisioned?

View File

@ -57,3 +57,25 @@ Currently, the [Metrics Dictionary](https://metrics.gitlab.com/) is built automa
Do not remove the metric's YAML definition altogether. Some self-managed instances might not immediately update to the latest version of GitLab, and
therefore continue to report the removed metric. The Analytics Instrumentation team requires a record of all removed metrics to identify and filter them.
## Group name changes
When the name of a group that owns events or metrics is changed, the `product_group` property should be updated in all metric and event definitions belonging to that group.
The `product_group_renamer` script can update all the definitions so you do not have to do it manually.
For example, if the group 5-min-app was renamed to 2-min-app, you can update the relevant files like this:
```shell
$ ruby scripts/internal_events/product_group_renamer.rb 5-min-app 2-min-app
Updated '5-min-app' to '2-min-app' in 3 files
Updated files:
config/metrics/schema/product_groups.json
config/metrics/counts_28d/20210216184517_p_ci_templates_5_min_production_app_monthly.yml
config/metrics/counts_7d/20210216184515_p_ci_templates_5_min_production_app_weekly.yml
```
After running the script, you must commit all the modified files to Git and create a merge request.
If a group is split into multiple groups, you need to manually update the product_group.

View File

@ -66,10 +66,17 @@ Prerequisites:
#### Configure network and proxy settings
For self-managed instances, you must update your firewalls and HTTP proxy servers
to allow outbound connections to `https://cloud.gitlab.com:443`.
To use an HTTP/S proxy, set the `gitLab_workhorse` and `gitLab_rails`
[web proxy environment variables](https://docs.gitlab.com/omnibus/settings/environment-variables.html).
For all self-managed AI features:
- Your firewalls and HTTP/S proxy servers must allow outbound connections
to `gitlab.com` and `cloud.gitlab.com` on port `443`.
- Both `HTTP2` and the `'upgrade'` header must be allowed, because GitLab Duo
uses both REST and WebSockets.
- To use an HTTP/S proxy, both `gitLab_workhorse` and `gitLab_rails` must have the necessary
[web proxy environment variables](https://docs.gitlab.com/omnibus/settings/environment-variables.html) set.
- Check for restrictions on WebSocket (`wss://`) traffic to `wss://gitlab.com/-/cable` and other `.com` domains.
Network policy restrictions on `wss://` traffic can cause issues with some GitLab Duo Chat
services. Consider policy updates to allow these services.
### Assign seats in bulk

View File

@ -208,8 +208,8 @@ The following table lists project permissions available for each role:
| [Repository](project/repository/index.md):<br>Remove fork relationship | | | | | ✓ | |
| [Repository](project/repository/index.md):<br>Force push to protected branches | | | | | | Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/protected_branches.md). |
| [Repository](project/repository/index.md):<br>Remove protected branches by using the UI or API | | | | ✓ | ✓ | |
| [Requirements Management](project/requirements/index.md):<br>Archive / reopen | | ✓ | ✓ | ✓ | ✓ | |
| [Requirements Management](project/requirements/index.md):<br>Create / edit | | ✓ | ✓ | ✓ | ✓ | |
| [Requirements Management](project/requirements/index.md):<br>Archive / reopen | | ✓ | ✓ | ✓ | ✓ | Authors and assignees can archive and re-open even if they dont have the Reporter role. |
| [Requirements Management](project/requirements/index.md):<br>Create / edit | | ✓ | ✓ | ✓ | ✓ | Authors and assignees can modify the title and description even if they dont have the Reporter role.|
| [Requirements Management](project/requirements/index.md):<br>Import / export | | ✓ | ✓ | ✓ | ✓ | |
| [Security dashboard](application_security/security_dashboard/index.md):<br>Create issue from vulnerability finding | | | ✓ | ✓ | ✓ | |
| [Security dashboard](application_security/security_dashboard/index.md):<br>Create vulnerability from vulnerability finding | | | ✓ | ✓ | ✓ | |

View File

@ -69,13 +69,13 @@ next to the requirement title.
## Edit a requirement
> - The ability to mark a requirement as Satisfied [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218607) in GitLab 13.5.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can edit requirements even if they dont have the Reporter role.
You can edit a requirement from the requirements list page.
Prerequisites:
- You must have at least the Reporter role.
- You must have at least the Reporter role or be the author or assignee of the requirement.
To edit a requirement:
@ -86,12 +86,14 @@ To edit a requirement:
## Archive a requirement
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can archive requirements even if they dont have the Reporter role.
You can archive an open requirement while
you're in the **Open** tab.
Prerequisites:
- You must have at least the Reporter role.
- You must have at least the Reporter role or be the author or assignee of the requirement.
To archive a requirement, select **Archive** (**{archive}**).
@ -99,11 +101,13 @@ As soon as a requirement is archived, it no longer appears in the **Open** tab.
## Reopen a requirement
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can re-open requirements even if they dont have the Reporter role.
You can view the list of archived requirements in the **Archived** tab.
Prerequisites:
- You must have at least the Reporter role.
- You must have at least the Reporter role or be the author or assignee of the requirement.
![archived requirements list](img/requirements_archived_list_view_v13_1.png)

View File

@ -4,7 +4,7 @@
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
default:
image: node:latest
image: node:21.7.3
# This folder is cached between builds
# https://docs.gitlab.com/ee/ci/yaml/index.html#cache

View File

@ -119,7 +119,6 @@ module Sidebars
)
end
# overriden in ee/lib/ee/sidebars/groups/menus/settings_menu.rb
def usage_quotas_menu_enabled?
context.group.usage_quotas_enabled?
end

View File

@ -18948,6 +18948,33 @@ msgstr ""
msgid "Due to inactivity, this project is scheduled to be deleted on %{deletion_date}. %{link_start}Why is this scheduled?%{link_end}"
msgstr ""
msgid "DuoChat|Ask a question about GitLab"
msgstr ""
msgid "DuoChat|For example, %{linkStart}what is a fork%{linkEnd}?"
msgstr ""
msgid "DuoChat|GitLab Duo Chat"
msgstr ""
msgid "DuoChat|Give feedback"
msgstr ""
msgid "DuoChat|How to use GitLab"
msgstr ""
msgid "DuoChat|The issue, epic, or code you're viewing"
msgstr ""
msgid "DuoChat|There was an error communicating with GitLab Duo Chat. Please try again later."
msgstr ""
msgid "DuoChat|Use AI to answer questions about things like:"
msgstr ""
msgid "DuoChat|What is a fork?"
msgstr ""
msgid "DuoProTrialAlert|Dismiss Code Suggestions banner"
msgstr ""
@ -35347,9 +35374,6 @@ msgstr ""
msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less:"
msgstr ""
msgid "One or more of your resource access tokens will expire in %{days_to_expire} or less:"
msgstr ""
msgid "Only %{workspaceType} members with %{permissions} can view or be notified about this %{issuableType}."
msgstr ""
@ -50681,33 +50705,6 @@ msgstr ""
msgid "Take a look at the documentation to discover all of GitLabs capabilities."
msgstr ""
msgid "TanukiBot|Ask a question about GitLab"
msgstr ""
msgid "TanukiBot|For example, %{linkStart}what is a fork%{linkEnd}?"
msgstr ""
msgid "TanukiBot|GitLab Duo Chat"
msgstr ""
msgid "TanukiBot|Give feedback"
msgstr ""
msgid "TanukiBot|How to use GitLab"
msgstr ""
msgid "TanukiBot|The issue, epic, or code you're viewing"
msgstr ""
msgid "TanukiBot|There was an error communicating with GitLab Duo Chat. Please try again later."
msgstr ""
msgid "TanukiBot|Use AI to answer questions about things like:"
msgstr ""
msgid "TanukiBot|What is a fork?"
msgstr ""
msgid "Target"
msgstr ""
@ -55881,6 +55878,9 @@ msgstr ""
msgid "UserProfile|You can create a group for several dependent projects"
msgstr ""
msgid "UserProfile|You cannot create projects in your personal namespace. Contact your GitLab administrator."
msgstr ""
msgid "UserProfile|You do not have any followers"
msgstr ""
@ -58791,7 +58791,7 @@ msgstr ""
msgid "You can create a new SSH key by visiting %{link}"
msgstr ""
msgid "You can create a new one or check them in your %{link_start}access tokens%{link_end} settings."
msgid "You can create a new one or check them in your %{link_start}Access Tokens%{link_end} settings."
msgstr ""
msgid "You can create a new one or check them in your %{pat_link_start}personal access tokens%{pat_link_end} settings."
@ -59378,6 +59378,12 @@ msgstr ""
msgid "Your %{plan} plan will be applied to your group."
msgstr ""
msgid "Your %{resource_type} access token %{codeOpen}%{token_name}%{codeClose} for %{codeOpen}%{resource_path}%{codeClose} will expire in %{days_to_expire} or less."
msgstr ""
msgid "Your %{resource_type} access token %{token_name} for %{resource_path} will expire in %{days_to_expire} or less."
msgstr ""
msgid "Your %{spammable_entity_type} has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."
msgstr ""

View File

@ -0,0 +1,81 @@
# frozen_string_literal: true
# !/usr/bin/env ruby
#
# Update group name in all relevant metric and event definition after a group name change.
require 'json'
PRODUCT_GROUPS_SCHEMA_PATH = 'config/metrics/schema/product_groups.json'
ALL_METRIC_AND_EVENT_DEFINITIONS_GLOB = "{ee/,}config/{metrics/*,events}/*.yml"
class ProductGroupRenamer
def initialize(schema_path, definitions_glob)
@schema_path = schema_path
@definitions_glob = definitions_glob
end
def rename_product_group(old_name, new_name)
changed_files = []
# Rename the product group in the schema
current_schema = File.read(@schema_path)
product_group_schema = JSON.parse(current_schema)
product_group_schema["enum"].delete(old_name)
product_group_schema["enum"].push(new_name) unless product_group_schema["enum"].include?(new_name)
product_group_schema["enum"].sort!
new_schema = "#{JSON.pretty_generate(product_group_schema)}\n"
if new_schema != current_schema
File.write(@schema_path, new_schema)
changed_files << @schema_path
end
# Rename product group in all metric and event definitions
Dir.glob(@definitions_glob).each do |file_path|
file_content = File.read(File.expand_path(file_path))
new_content = file_content.gsub(/product_group:\s*['"]?#{old_name}['"]?$/, "product_group: #{new_name}")
if new_content != file_content
File.write(file_path, new_content)
changed_files << file_path
end
end
changed_files
end
end
if $PROGRAM_NAME == __FILE__
if ARGV.length != 2
puts <<~TEXT
Usage:
When a group is renamed, this script replaces the value for "product_group" in all matching event & metric definitions.
Format:
ruby #{$PROGRAM_NAME} OLD_NAME NEW_NAME
Example:
ruby #{$PROGRAM_NAME} pipeline_authoring renamed_pipeline_authoring
TEXT
exit
end
old_name = ARGV[0]
new_name = ARGV[1]
changed_files = ProductGroupRenamer
.new(PRODUCT_GROUPS_SCHEMA_PATH, ALL_METRIC_AND_EVENT_DEFINITIONS_GLOB)
.rename_product_group(old_name, new_name)
puts "Updated '#{old_name}' to '#{new_name}' in #{changed_files.length} files"
puts
if changed_files.any?
puts "Updated files:"
changed_files.each do |file_path|
puts " #{file_path}"
end
end
end

View File

@ -0,0 +1,18 @@
---
description: Engineer uses Internal Event CLI to define a new event
internal_events: true
action: internal_events_cli_used
identifiers:
- project
- namespace
- user
product_group: a_group_name
milestone: '16.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149010
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,18 @@
---
description: Engineer uses Internal Event CLI to define a new event
internal_events: true
action: internal_events_cli_used
identifiers:
- project
- namespace
- user
product_group: another_group
milestone: '17.0'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149010
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,21 @@
---
key_path: counts.count_total_internal_events_cli_used
description: Total count of when an event was defined using the CLI
product_group: a_group_name
performance_indicator_type: []
value_type: number
status: active
milestone: '17.0'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149010
time_frame: all
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: internal_events_cli_used

View File

@ -0,0 +1,7 @@
{
"type": "string",
"enum": [
"a_better_group_name",
"another_group_name"
]
}

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ml::FindModelVersionResolver, feature_category: :mlops do
include GraphqlHelpers
describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:model) { create(:ml_models, project: project) }
let_it_be(:model_version) { create(:ml_model_versions, model: model) }
let_it_be(:another_model_version) { create(:ml_model_versions) }
let_it_be(:owner) { project.owner }
let(:current_user) { owner }
let(:args) { { model_version_id: global_id_of(model_version) } }
let(:read_model_registry) { true }
subject { force(resolve(described_class, obj: model, ctx: { current_user: current_user }, args: args)) }
before do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?)
.with(current_user, :read_model_registry, project)
.and_return(read_model_registry)
end
context 'when user is allowed and model version exists and belongs to model' do
it { is_expected.to eq(model_version) }
context 'when user is nil' do
let(:current_user) { nil }
it { is_expected.to eq(model_version) }
end
end
context 'when user does not have permission' do
let(:read_model_registry) { false }
it { is_expected.to be_nil }
end
context 'when model version exists but does not belong to model' do
let(:args) { { model_version_id: global_id_of(another_model_version) } }
it { is_expected.to be_nil }
end
context 'when model version does not exist' do
let(:args) { { model_version_id: global_id_of(id: non_existing_record_id, model_name: 'Ml::ModelVersion') } }
it { is_expected.to be_nil }
end
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['MlCandidateMetadata'], feature_category: :mlops do
it 'has the expected fields' do
expected_fields = %w[id name value]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['MlCandidateMetric'], feature_category: :mlops do
it 'has the expected fields' do
expected_fields = %w[id name value step]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['MlCandidateParam'], feature_category: :mlops do
it 'has the expected fields' do
expected_fields = %w[id name value]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end

View File

@ -10,6 +10,9 @@ RSpec.describe GitlabSchema.types['MlCandidate'], feature_category: :mlops do
let_it_be(:candidate) do
model_version.candidate.tap do |c|
c.update!(ci_build: create(:ci_build, pipeline: pipeline, user: current_user))
c.metrics = [create(:ml_candidate_metrics, candidate: c)]
c.params = [create(:ml_candidate_params, candidate: c)]
c.metadata = [create(:ml_candidate_metadata, candidate: c)]
end
end
@ -33,6 +36,21 @@ RSpec.describe GitlabSchema.types['MlCandidate'], feature_category: :mlops do
showPath
artifactPath
}
metrics {
nodes {
id
}
}
params {
nodes {
id
}
}
metadata {
nodes {
id
}
}
}
}
}
@ -59,6 +77,27 @@ RSpec.describe GitlabSchema.types['MlCandidate'], feature_category: :mlops do
'_links' => {
'showPath' => "/#{project.full_path}/-/ml/candidates/#{model_version.candidate.iid}",
'artifactPath' => "/#{project.full_path}/-/packages/#{model_version.package_id}"
},
'metrics' => {
'nodes' => [
{
'id' => "gid://gitlab/Ml::CandidateMetric/#{candidate.metrics.first.id}"
}
]
},
'params' => {
'nodes' => [
{
'id' => "gid://gitlab/Ml::CandidateParam/#{candidate.params.first.id}"
}
]
},
'metadata' => {
'nodes' => [
{
'id' => "gid://gitlab/Ml::CandidateMetadata/#{candidate.metadata.first.id}"
}
]
}
})
end

View File

@ -7,10 +7,13 @@ RSpec.describe GitlabSchema.types['MlModel'], feature_category: :mlops do
let_it_be(:project) { model.project }
let_it_be(:candidates) { Array.new(2) { create(:ml_candidates, experiment: model.default_experiment) } }
let_it_be(:model_id) { GitlabSchema.id_from_object(model).to_s }
let_it_be(:model_version_id) { GitlabSchema.id_from_object(model.latest_version).to_s }
let(:query) do
%(
query {
mlModel(id: "gid://gitlab/Ml::Model/#{model.id}") {
mlModel(id: "#{model_id}") {
id
description
name
@ -19,6 +22,9 @@ RSpec.describe GitlabSchema.types['MlModel'], feature_category: :mlops do
latestVersion {
id
}
version(modelVersionId: "#{model_version_id}") {
id
}
_links {
showPath
}
@ -33,7 +39,7 @@ RSpec.describe GitlabSchema.types['MlModel'], feature_category: :mlops do
it 'includes all the fields' do
expected_fields = %w[id name versions candidates version_count _links created_at latest_version description
candidate_count description]
candidate_count description version]
expect(described_class).to include_graphql_fields(*expected_fields)
end
@ -42,11 +48,14 @@ RSpec.describe GitlabSchema.types['MlModel'], feature_category: :mlops do
model_data = data.dig('data', 'mlModel')
expect(model_data).to eq({
'id' => "gid://gitlab/Ml::Model/#{model.id}",
'id' => model_id,
'name' => model.name,
'description' => 'A description',
'latestVersion' => {
'id' => "gid://gitlab/Ml::ModelVersion/#{model.latest_version.id}"
'id' => model_version_id
},
'version' => {
'id' => model_version_id
},
'versionCount' => 1,
'candidateCount' => 2,

View File

@ -15,6 +15,7 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
latestVersion {
id
version
packageId
candidate {
id
}
@ -33,7 +34,7 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
subject(:data) { GitlabSchema.execute(query, context: { current_user: project.owner }).as_json }
it 'includes all fields' do
expected_fields = %w[id version created_at _links candidate]
expected_fields = %w[id version created_at _links candidate package_id]
expect(described_class).to include_graphql_fields(*expected_fields)
end
@ -44,6 +45,7 @@ RSpec.describe GitlabSchema.types['MlModelVersion'], feature_category: :mlops do
expect(version_data).to eq({
'id' => "gid://gitlab/Ml::ModelVersion/#{model_version.id}",
'version' => model_version.version,
'packageId' => "gid://gitlab/Packages::Package/#{model_version.package_id}",
'candidate' => {
'id' => "gid://gitlab/Ml::Candidate/#{model_version.candidate.id}"
},

View File

@ -42,7 +42,8 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do
'sprints.group_id',
'subscription_add_on_purchases.namespace_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/444338
'temp_notes_backup.project_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/443667'
*['todos.project_id', 'todos.group_id']
*['todos.project_id', 'todos.group_id'],
*['vulnerability_exports.project_id', 'vulnerability_exports.group_id']
]
end

View File

@ -193,7 +193,7 @@ RSpec.describe Emails::Profile, feature_category: :user_profile do
resource.add_developer(project_bot)
end
subject { Notify.resource_access_tokens_about_to_expire_email(user, resource, [expiring_token.name]) }
subject { Notify.bot_resource_access_token_about_to_expire_email(user, resource, expiring_token.name) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@ -216,7 +216,7 @@ RSpec.describe Emails::Profile, feature_category: :user_profile do
resource.add_reporter(project_bot)
end
subject { Notify.resource_access_tokens_about_to_expire_email(user, resource, [expiring_token.name]) }
subject { Notify.bot_resource_access_token_about_to_expire_email(user, resource, expiring_token.name) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'

View File

@ -13,7 +13,7 @@ RSpec.describe 'Creation of a machine learning model', feature_category: :mlops
let(:name) { 'some_name' }
let(:description) { 'A description' }
let(:mutation) { graphql_mutation(:ml_model_create, input) }
let(:mutation) { graphql_mutation(:ml_model_create, input, nil, ['version']) }
let(:mutation_response) { graphql_mutation_response(:ml_model_create) }
context 'when user is not allowed write changes' do

View File

@ -1971,7 +1971,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
end
context 'user does not have access to view the private profile' do
it 'returns no projects' do
it 'returns no projects', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/444704' do
get api(path, user)
expect(response).to have_gitlab_http_status(:ok)

View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'spec_helper'
require_relative '../../../scripts/internal_events/product_group_renamer'
RSpec.describe ProductGroupRenamer, feature_category: :service_ping do
let(:renamer) { described_class.new(schema_path, definitions_glob) }
context 'with real definitions', :aggregate_failures do
let(:schema_path) { PRODUCT_GROUPS_SCHEMA_PATH }
let(:definitions_glob) { ALL_METRIC_AND_EVENT_DEFINITIONS_GLOB }
it 'reads all definitions files' do
allow(File).to receive(:read).and_call_original
Gitlab::Tracking::EventDefinition.definitions.each do |event_definition|
expect(File).to receive(:read).with(event_definition.path)
expect(File).not_to receive(:write).with(event_definition.path)
end
Gitlab::Usage::MetricDefinition.definitions.each_value do |metric_definition|
expect(File).to receive(:read).with(metric_definition.path)
expect(File).not_to receive(:write).with(metric_definition.path)
end
renamer.rename_product_group('old_name', 'new_name')
end
end
describe '#rename_product_group', :aggregate_failures do
let(:temp_dir) { Dir.mktmpdir }
let(:schema_path) { File.join(temp_dir, 'product_groups.json') }
let(:event_definition_path) { File.join(temp_dir, 'event_definition.yml') }
let(:metric_definition_path) { File.join(temp_dir, 'metric_definition.yml') }
let(:event_definition_from_another_group_path) do
File.join(temp_dir, 'event_definition_from_another_group.yml')
end
let(:definitions_glob) { [event_definition_path, metric_definition_path, event_definition_from_another_group_path] }
before do
FileUtils.cp_r(File.join('spec/fixtures/scripts/product_group_renamer', '.'), temp_dir)
end
after do
FileUtils.rm_rf(temp_dir)
end
it 'renames product group in the schema and the definitions' do
renamer.rename_product_group('a_group_name', 'a_better_group_name')
schema_content = File.read(schema_path)
expect(schema_content).to include('a_better_group_name')
expect(schema_content).not_to include('a_group_name')
expect(File.read(event_definition_path)).to include('product_group: a_better_group_name')
expect(File.read(metric_definition_path)).to include('product_group: a_better_group_name')
expect(File.read(event_definition_from_another_group_path)).not_to include('product_group: a_better_group_name')
end
end
end

View File

@ -384,7 +384,7 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
let_it_be(:owner2) { create(:user) }
subject(:notification_service) do
notification.resource_access_tokens_about_to_expire(project_bot, [expiring_token.name])
notification.bot_resource_access_token_about_to_expire(project_bot, [expiring_token.name])
end
context 'when the resource is a group' do
@ -402,13 +402,13 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
owner1,
project_bot.resource_bot_resource,
[expiring_token.name],
mail: "resource_access_tokens_about_to_expire_email"
mail: "bot_resource_access_token_about_to_expire_email"
).and(
have_enqueued_email(
owner2,
project_bot.resource_bot_resource,
[expiring_token.name],
mail: "resource_access_tokens_about_to_expire_email"
mail: "bot_resource_access_token_about_to_expire_email"
)
)
)
@ -430,13 +430,13 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
owner1,
project_bot.resource_bot_resource,
[expiring_token.name],
mail: "resource_access_tokens_about_to_expire_email"
mail: "bot_resource_access_token_about_to_expire_email"
).and(
have_enqueued_email(
owner2,
project_bot.resource_bot_resource,
[expiring_token.name],
mail: "resource_access_tokens_about_to_expire_email"
mail: "bot_resource_access_token_about_to_expire_email"
)
)
)

View File

@ -3,10 +3,12 @@
require 'spec_helper'
RSpec.describe 'shared/projects/_list' do
let(:group) { create(:group) }
let_it_be(:user) { build(:user) }
let_it_be(:group) { create(:group) }
before do
allow(view).to receive(:projects).and_return(projects)
allow(view).to receive(:current_user) { user }
end
context 'with projects' do
@ -99,5 +101,39 @@ RSpec.describe 'shared/projects/_list' do
end
end
end
context 'when projects_limit > 0' do
before do
allow(user).to receive(:projects_limit).and_return(1)
controller.params[:controller] = 'users'
controller.params[:username] = user.username
end
it 'renders `New project` button' do
render
expect(rendered).to have_link('New project')
expect(rendered).to have_content(
s_('UserProfile|Your projects can be available publicly, internally, or privately, at your choice.')
)
end
end
context 'when projects_limit is 0' do
before do
allow(user).to receive(:projects_limit).and_return(0)
controller.params[:controller] = 'users'
controller.params[:username] = user.username
end
it 'does not render `New project` button' do
render
expect(rendered).not_to have_link('New project')
expect(rendered).to have_content(
s_("UserProfile|You cannot create projects in your personal namespace. Contact your GitLab administrator.")
)
end
end
end
end

View File

@ -8,7 +8,7 @@ RSpec.describe PersonalAccessTokens::ExpiringWorker, type: :worker, feature_cate
shared_examples 'sends notification about expiry of bot user tokens' do
it 'uses notification service to send the email' do
expect_next_instance_of(NotificationService) do |notification_service|
expect(notification_service).to receive(:resource_access_tokens_about_to_expire)
expect(notification_service).to receive(:bot_resource_access_token_about_to_expire)
.with(project_bot, expiring_token.name)
end