Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b4337101a9
commit
3456ec38df
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 %>
|
||||
|
|
@ -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
|
||||
|
|
@ -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 %>
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
90dda8138e958b31a29a180fe5023417418da6862d8621e7b89d80e5d8abd3da
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}**).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 don’t 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 don’t 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 | | | ✓ | ✓ | ✓ | |
|
||||
|
|
|
|||
|
|
@ -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 don’t 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 don’t 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 don’t 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.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 GitLab’s 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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
18
spec/fixtures/scripts/product_group_renamer/event_definition_from_another_group.yml
vendored
Normal file
18
spec/fixtures/scripts/product_group_renamer/event_definition_from_another_group.yml
vendored
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"a_better_group_name",
|
||||
"another_group_name"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue