Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
458209640c
commit
cf58163b56
52 changed files with 828 additions and 69 deletions
|
@ -1,15 +1,17 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { GlButton, GlIcon, GlModal, GlModalDirective } from '@gitlab/ui';
|
||||
import RequestWarning from './request_warning.vue';
|
||||
|
||||
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RequestWarning,
|
||||
GlModal: DeprecatedModal2,
|
||||
GlButton,
|
||||
GlModal,
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
'gl-modal': GlModalDirective,
|
||||
},
|
||||
props: {
|
||||
currentRequest: {
|
||||
type: Object,
|
||||
|
@ -35,7 +37,15 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openedBacktraces: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
modalId() {
|
||||
return `modal-peek-${this.metric}-details`;
|
||||
},
|
||||
metricDetails() {
|
||||
return this.currentRequest.details[this.metric];
|
||||
},
|
||||
|
@ -58,29 +68,35 @@ export default {
|
|||
return '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleBacktrace(toggledIndex) {
|
||||
const toggledOpenedIndex = this.openedBacktraces.indexOf(toggledIndex);
|
||||
|
||||
if (toggledOpenedIndex === -1) {
|
||||
this.openedBacktraces = [...this.openedBacktraces, toggledIndex];
|
||||
} else {
|
||||
this.openedBacktraces = this.openedBacktraces.filter(
|
||||
openedIndex => openedIndex !== toggledIndex,
|
||||
);
|
||||
}
|
||||
},
|
||||
itemHasOpenedBacktrace(toggledIndex) {
|
||||
return this.openedBacktraces.find(openedIndex => openedIndex === toggledIndex) >= 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="currentRequest.details && metricDetails"
|
||||
:id="`peek-view-${metric}`"
|
||||
class="view"
|
||||
class="gl-display-flex gl-align-items-center view"
|
||||
data-qa-selector="detailed_metric_content"
|
||||
>
|
||||
<button
|
||||
:data-target="`#modal-peek-${metric}-details`"
|
||||
class="btn-blank btn-link bold"
|
||||
type="button"
|
||||
data-toggle="modal"
|
||||
>
|
||||
<gl-button v-gl-modal="modalId" class="gl-mr-2" type="button" variant="link">
|
||||
{{ metricDetailsLabel }}
|
||||
</button>
|
||||
<gl-modal
|
||||
:id="`modal-peek-${metric}-details`"
|
||||
:header-title-text="header"
|
||||
modal-size="xl"
|
||||
class="performance-bar-modal"
|
||||
>
|
||||
</gl-button>
|
||||
<gl-modal :modal-id="modalId" :title="header" size="lg" modal-class="gl-mt-7" scrollable>
|
||||
<table class="table">
|
||||
<template v-if="detailsList.length">
|
||||
<tr v-for="(item, index) in detailsList" :key="index">
|
||||
|
@ -90,7 +106,7 @@ export default {
|
|||
}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="js-toggle-container">
|
||||
<div>
|
||||
<div
|
||||
v-for="(key, keyIndex) in keys"
|
||||
:key="key"
|
||||
|
@ -98,16 +114,18 @@ export default {
|
|||
:class="{ 'mb-3 bold': keyIndex == 0 }"
|
||||
>
|
||||
{{ item[key] }}
|
||||
<button
|
||||
<gl-button
|
||||
v-if="keyIndex == 0 && item.backtrace"
|
||||
class="text-expander js-toggle-button"
|
||||
class="gl-ml-3"
|
||||
data-testid="backtrace-expand-btn"
|
||||
type="button"
|
||||
:aria-label="__('Toggle backtrace')"
|
||||
@click="toggleBacktrace(index)"
|
||||
>
|
||||
<gl-icon :size="12" name="ellipsis_h" />
|
||||
</button>
|
||||
</gl-button>
|
||||
</div>
|
||||
<pre v-if="item.backtrace" class="backtrace-row js-toggle-content mt-2">{{
|
||||
<pre v-if="itemHasOpenedBacktrace(index)" class="backtrace-row mt-2">{{
|
||||
item.backtrace
|
||||
}}</pre>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: #{$zindex-modal-backdrop + 1};
|
||||
z-index: #{$zindex-modal-backdrop - 1};
|
||||
|
||||
height: $performance-bar-height;
|
||||
background: $black;
|
||||
|
|
|
@ -28,6 +28,11 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
super do |new_user|
|
||||
persist_accepted_terms_if_required(new_user)
|
||||
set_role_required(new_user)
|
||||
|
||||
if pending_approval?
|
||||
NotificationService.new.new_instance_access_request(new_user)
|
||||
end
|
||||
|
||||
yield new_user if block_given?
|
||||
end
|
||||
|
||||
|
@ -131,6 +136,12 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
render action: 'new'
|
||||
end
|
||||
|
||||
def pending_approval?
|
||||
return false unless Gitlab::CurrentSettings.require_admin_approval_after_user_signup
|
||||
|
||||
resource.persisted? && resource.blocked_pending_approval?
|
||||
end
|
||||
|
||||
def sign_up_params
|
||||
params.require(:user).permit(:username, :email, :name, :first_name, :last_name, :password)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module PackageEventable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def track_event(event, scope)
|
||||
::Packages::CreateEventService.new(nil, current_user, event_name: event, scope: scope).execute
|
||||
::Gitlab::Tracking.event(event.to_s, scope.to_s)
|
||||
end
|
||||
end
|
||||
end
|
45
app/graphql/mutations/container_repositories/destroy.rb
Normal file
45
app/graphql/mutations/container_repositories/destroy.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module ContainerRepositories
|
||||
class Destroy < Mutations::BaseMutation
|
||||
include ::Mutations::PackageEventable
|
||||
|
||||
graphql_name 'DestroyContainerRepository'
|
||||
|
||||
authorize :destroy_container_image
|
||||
|
||||
argument :id,
|
||||
::Types::GlobalIDType[::ContainerRepository],
|
||||
required: true,
|
||||
description: 'ID of the container repository.'
|
||||
|
||||
field :container_repository,
|
||||
Types::ContainerRepositoryType,
|
||||
null: false,
|
||||
description: 'The container repository policy after scheduling the deletion.'
|
||||
|
||||
def resolve(id:)
|
||||
container_repository = authorized_find!(id: id)
|
||||
|
||||
container_repository.delete_scheduled!
|
||||
DeleteContainerRepositoryWorker.perform_async(current_user.id, container_repository.id)
|
||||
track_event(:delete_repository, :container)
|
||||
|
||||
{
|
||||
container_repository: container_repository,
|
||||
errors: []
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_object(id:)
|
||||
# TODO: remove this line when the compatibility layer is removed
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
|
||||
id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Resolvers
|
||||
class ContainerRepositoriesResolver < BaseResolver
|
||||
include ::Mutations::PackageEventable
|
||||
|
||||
type Types::ContainerRepositoryType, null: true
|
||||
|
||||
argument :name, GraphQL::STRING_TYPE,
|
||||
|
@ -13,12 +15,5 @@ module Resolvers
|
|||
.execute
|
||||
.tap { track_event(:list_repositories, :container) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_event(event, scope)
|
||||
::Packages::CreateEventService.new(nil, current_user, event_name: event, scope: scope).execute
|
||||
::Gitlab::Tracking.event(event.to_s, scope.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,6 +82,7 @@ module Types
|
|||
mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
|
||||
mount_mutation Mutations::DesignManagement::Move
|
||||
mount_mutation Mutations::ContainerExpirationPolicies::Update
|
||||
mount_mutation Mutations::ContainerRepositories::Destroy
|
||||
mount_mutation Mutations::Ci::PipelineCancel
|
||||
mount_mutation Mutations::Ci::PipelineDestroy
|
||||
mount_mutation Mutations::Ci::PipelineRetry
|
||||
|
|
|
@ -214,6 +214,24 @@ module EmailsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def instance_access_request_text(user, format: nil)
|
||||
gitlab_host = Gitlab.config.gitlab.host
|
||||
|
||||
_('%{username} has asked for a GitLab account on your instance %{host}:') % { username: sanitize_name(user.name), host: gitlab_host }
|
||||
end
|
||||
|
||||
def instance_access_request_link(user, format: nil)
|
||||
url = admin_user_url(user)
|
||||
|
||||
case format
|
||||
when :html
|
||||
user_page = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: url }
|
||||
_("Click %{link_start}here%{link_end} to view the request.").html_safe % { link_start: user_page, link_end: '</a>'.html_safe }
|
||||
else
|
||||
_('Click %{link_to} to view the request.') % { link_to: url }
|
||||
end
|
||||
end
|
||||
|
||||
def contact_your_administrator_text
|
||||
_('Please contact your administrator with any questions.')
|
||||
end
|
||||
|
|
|
@ -9,6 +9,15 @@ module Emails
|
|||
mail(to: @user.notification_email, subject: subject("Account was created for you"))
|
||||
end
|
||||
|
||||
def instance_access_request_email(user, recipient)
|
||||
@user = user
|
||||
@recipient = recipient
|
||||
|
||||
profile_email_with_layout(
|
||||
to: recipient.notification_email,
|
||||
subject: subject(_("GitLab Account Request")))
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def new_ssh_key_email(key_id)
|
||||
@key = Key.find_by(id: key_id)
|
||||
|
@ -63,13 +72,9 @@ module Emails
|
|||
@target_url = edit_profile_password_url
|
||||
|
||||
Gitlab::I18n.with_locale(@user.preferred_language) do
|
||||
mail(
|
||||
profile_email_with_layout(
|
||||
to: @user.notification_email,
|
||||
subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host })
|
||||
) do |format|
|
||||
format.html { render layout: 'mailer' }
|
||||
format.text { render layout: 'mailer' }
|
||||
end
|
||||
subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host }))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -82,6 +87,15 @@ module Emails
|
|||
mail(to: @user.notification_email, subject: subject(_("Two-factor authentication disabled")))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def profile_email_with_layout(to:, subject:, layout: 'mailer')
|
||||
mail(to: to, subject: subject) do |format|
|
||||
format.html { render layout: layout }
|
||||
format.text { render layout: layout }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -109,6 +109,11 @@ module HasRepository
|
|||
Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :http)
|
||||
end
|
||||
|
||||
# Is overridden in EE::Project for Geo support
|
||||
def lfs_http_url_to_repo(_operation = nil)
|
||||
http_url_to_repo
|
||||
end
|
||||
|
||||
def web_url(only_path: nil)
|
||||
Gitlab::UrlBuilder.build(self, only_path: only_path)
|
||||
end
|
||||
|
|
|
@ -1469,11 +1469,6 @@ class Project < ApplicationRecord
|
|||
services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
# Is overridden in EE
|
||||
def lfs_http_url_to_repo(_)
|
||||
http_url_to_repo
|
||||
end
|
||||
|
||||
def feature_usage
|
||||
super.presence || build_feature_usage
|
||||
end
|
||||
|
|
|
@ -28,6 +28,8 @@ class User < ApplicationRecord
|
|||
|
||||
DEFAULT_NOTIFICATION_LEVEL = :participating
|
||||
|
||||
INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
|
||||
|
||||
add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
|
||||
add_authentication_token_field :feed_token
|
||||
add_authentication_token_field :static_object_token
|
||||
|
@ -341,6 +343,7 @@ class User < ApplicationRecord
|
|||
|
||||
# Scopes
|
||||
scope :admins, -> { where(admin: true) }
|
||||
scope :instance_access_request_approvers_to_be_notified, -> { admins.active.order_recent_sign_in.limit(INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) }
|
||||
scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
|
||||
scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
|
||||
scope :external, -> { where(external: true) }
|
||||
|
|
|
@ -370,6 +370,16 @@ class NotificationService
|
|||
end
|
||||
end
|
||||
|
||||
def new_instance_access_request(user)
|
||||
recipients = User.instance_access_request_approvers_to_be_notified # https://gitlab.com/gitlab-org/gitlab/-/issues/277016 will change this
|
||||
|
||||
return true if recipients.empty?
|
||||
|
||||
recipients.each do |recipient|
|
||||
mailer.instance_access_request_email(user, recipient).deliver_later
|
||||
end
|
||||
end
|
||||
|
||||
# Members
|
||||
def new_access_request(member)
|
||||
return true unless member.notifiable?(:subscription)
|
||||
|
|
10
app/views/notify/instance_access_request_email.html.haml
Normal file
10
app/views/notify/instance_access_request_email.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
|||
#content
|
||||
= email_default_heading(say_hello(@recipient))
|
||||
%p
|
||||
= instance_access_request_text(@user, format: :html)
|
||||
%p
|
||||
= _("Username: %{username}") % { username: @user.username }
|
||||
%p
|
||||
= _("Email: %{email}") % { email: @user.email }
|
||||
%p
|
||||
= instance_access_request_link(@user, format: :html)
|
8
app/views/notify/instance_access_request_email.text.erb
Normal file
8
app/views/notify/instance_access_request_email.text.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
<%= say_hello(@recipient) %>
|
||||
|
||||
<%= instance_access_request_text(@user) %>
|
||||
|
||||
<%= _("Username: %{username}") % { username: @user.username } %>
|
||||
<%= _("Email: %{email}") % { email: @user.email } %>
|
||||
|
||||
<%= instance_access_request_link(@user) %>
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add container repository destroy GraphQL mutation
|
||||
merge_request: 47175
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/229704-aqualls-metrics-modal.yml
Normal file
5
changelogs/unreleased/229704-aqualls-metrics-modal.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update detailed_metric.vue modal to match Pajamas guidelines
|
||||
merge_request: 46183
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Send email notifications to admins about users pending approval
|
||||
merge_request: 46895
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use dedicated signing key for CI_JOB_JWT by default
|
||||
merge_request: 47336
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/lfs-ssh-authentication.yml
Normal file
5
changelogs/unreleased/lfs-ssh-authentication.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix internal lfs_authenticate API for non-project repositories
|
||||
merge_request: 47404
|
||||
author:
|
||||
type: fixed
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258546
|
|||
milestone: '13.6'
|
||||
type: development
|
||||
group: group::release management
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -69,10 +69,10 @@ options = {
|
|||
# :filter is optional
|
||||
# 'cn' looks for all "cn"s under :base
|
||||
# '*' is the search string - here, it's a wildcard
|
||||
filter: Net::Ldap::Filter.eq('cn', '*'),
|
||||
filter: Net::LDAP::Filter.eq('cn', '*'),
|
||||
|
||||
# :attributes is optional
|
||||
# the attributes we want to get returned
|
||||
# the attributes we want to get returnedk
|
||||
attributes: %w(dn cn memberuid member submember uniquemember memberof)
|
||||
}
|
||||
adapter.ldap_search(options)
|
||||
|
|
|
@ -397,6 +397,20 @@ To configure the `s3` storage driver in Omnibus:
|
|||
}
|
||||
```
|
||||
|
||||
To avoid using static credentials, use an
|
||||
[IAM role](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||
and omit `accesskey` and `secretkey`. Make sure that your IAM profile follows
|
||||
[the permissions documented by Docker](https://docs.docker.com/registry/storage-drivers/s3/#s3-permission-scopes).
|
||||
|
||||
```ruby
|
||||
registry['storage'] = {
|
||||
's3' => {
|
||||
'bucket' => 'your-s3-bucket',
|
||||
'region' => 'your-s3-region'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `regionendpoint` is only required when configuring an S3 compatible service such as MinIO. It takes a URL such as `http://127.0.0.1:9000`.
|
||||
- `your-s3-bucket` should be the name of a bucket that exists, and can't include subdirectories.
|
||||
|
||||
|
@ -412,8 +426,8 @@ when you [deployed your Docker registry](https://docs.docker.com/registry/deploy
|
|||
```yaml
|
||||
storage:
|
||||
s3:
|
||||
accesskey: 's3-access-key'
|
||||
secretkey: 's3-secret-key-for-access-key'
|
||||
accesskey: 's3-access-key' # Not needed if IAM role used
|
||||
secretkey: 's3-secret-key-for-access-key' # Not needed if IAM role used
|
||||
bucket: 'your-s3-bucket'
|
||||
region: 'your-s3-region'
|
||||
regionendpoint: 'your-s3-regionendpoint'
|
||||
|
|
|
@ -6093,6 +6093,41 @@ type DestroyBoardPayload {
|
|||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DestroyContainerRepository
|
||||
"""
|
||||
input DestroyContainerRepositoryInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
ID of the container repository.
|
||||
"""
|
||||
id: ContainerRepositoryID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DestroyContainerRepository
|
||||
"""
|
||||
type DestroyContainerRepositoryPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The container repository policy after scheduling the deletion.
|
||||
"""
|
||||
containerRepository: ContainerRepository!
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DestroyNote
|
||||
"""
|
||||
|
@ -13470,6 +13505,7 @@ type Mutation {
|
|||
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
|
||||
destroyBoard(input: DestroyBoardInput!): DestroyBoardPayload
|
||||
destroyBoardList(input: DestroyBoardListInput!): DestroyBoardListPayload
|
||||
destroyContainerRepository(input: DestroyContainerRepositoryInput!): DestroyContainerRepositoryPayload
|
||||
destroyNote(input: DestroyNoteInput!): DestroyNotePayload
|
||||
destroySnippet(input: DestroySnippetInput!): DestroySnippetPayload
|
||||
|
||||
|
|
|
@ -16765,6 +16765,112 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DestroyContainerRepositoryInput",
|
||||
"description": "Autogenerated input type of DestroyContainerRepository",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the container repository.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ContainerRepositoryID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DestroyContainerRepositoryPayload",
|
||||
"description": "Autogenerated return type of DestroyContainerRepository",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "containerRepository",
|
||||
"description": "The container repository policy after scheduling the deletion.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ContainerRepository",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DestroyNoteInput",
|
||||
|
@ -38177,6 +38283,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "destroyContainerRepository",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DestroyContainerRepositoryInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DestroyContainerRepositoryPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "destroyNote",
|
||||
"description": null,
|
||||
|
|
|
@ -1002,6 +1002,16 @@ Autogenerated return type of DestroyBoard.
|
|||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### DestroyContainerRepositoryPayload
|
||||
|
||||
Autogenerated return type of DestroyContainerRepository.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `containerRepository` | ContainerRepository! | The container repository policy after scheduling the deletion. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### DestroyNotePayload
|
||||
|
||||
Autogenerated return type of DestroyNote.
|
||||
|
|
|
@ -56,7 +56,7 @@ The JWT's payload looks like this:
|
|||
}
|
||||
```
|
||||
|
||||
The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID Connect private key. The expire time for the token will be set to job's timeout, if specified, or 5 minutes if it is not. The key used to sign this token may change without any notice. In such case retrying the job will generate new JWT using the current signing key.
|
||||
The JWT is encoded by using RS256 and signed with a dedicated private key. The expire time for the token will be set to job's timeout, if specified, or 5 minutes if it is not. The key used to sign this token may change without any notice. In such case retrying the job will generate new JWT using the current signing key.
|
||||
|
||||
You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.com/-/jwks`) to authenticate with a Vault server that is configured to allow the JWT Authentication method for authentication.
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ with the [GitLab Container Registry](../../user/packages/container_registry/inde
|
|||
|
||||
This way of triggering can only be used when invoked inside `.gitlab-ci.yml`,
|
||||
and it creates a dependent pipeline relation visible on the
|
||||
[pipeline graph](../multi_project_pipelines.md#overview). For example:
|
||||
[pipeline graph](../multi_project_pipelines.md). For example:
|
||||
|
||||
```yaml
|
||||
build_docs:
|
||||
|
|
|
@ -3837,8 +3837,8 @@ For more information, see [Deployments Safety](../environments/deployment_safety
|
|||
These methods are supported:
|
||||
|
||||
- [`tag_name`](#releasetag_name)
|
||||
- [`description`](#releasedescription)
|
||||
- [`name`](#releasename) (optional)
|
||||
- [`description`](#releasedescription) (optional)
|
||||
- [`ref`](#releaseref) (optional)
|
||||
- [`milestones`](#releasemilestones) (optional)
|
||||
- [`released_at`](#releasereleased_at) (optional)
|
||||
|
|
|
@ -899,6 +899,32 @@ it.each([
|
|||
);
|
||||
```
|
||||
|
||||
**Note**: only use template literal block if pretty print is **not** needed for spec output. For example, empty strings, nested objects etc.
|
||||
|
||||
For example, when testing the difference between an empty search string and a non-empty search string, the use of the array block syntax with the pretty print option would be preferred. That way the differences between an empty string e.g. `''` and a non-empty string e.g. `'search string'` would be visible in the spec output. Whereas with a template literal block, the empty string would be shown as a space, which could lead to a confusing developer experience
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
it.each`
|
||||
searchTerm | expected
|
||||
${''} | ${{ issue: { users: { nodes: [] } } }}
|
||||
${'search term'} | ${{ issue: { other: { nested: [] } } }}
|
||||
`('when search term is $searchTerm, it returns $expected', ({ searchTerm, expected }) => {
|
||||
expect(search(searchTerm)).toEqual(expected)
|
||||
});
|
||||
|
||||
// good
|
||||
it.each([
|
||||
['', { issue: { users: { nodes: [] } } }],
|
||||
['search term', { issue: { other: { nested: [] } } }],
|
||||
])('when search term is %p, expect to return %p',
|
||||
(searchTerm, expected) => {
|
||||
expect(search(searchTerm)).toEqual(expected)
|
||||
}
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
```javascript
|
||||
// test suite with tagged template literal block
|
||||
describe.each`
|
||||
|
|
|
@ -166,7 +166,7 @@ a base domain of `example.com`, you'd need a DNS entry like:
|
|||
```
|
||||
|
||||
In this case, the deployed applications are served from `example.com`, and `1.2.3.4`
|
||||
is the IP address of your load balancer; generally NGINX ([see requirements](#requirements)).
|
||||
is the IP address of your load balancer; generally NGINX ([see requirements](requirements.md)).
|
||||
Setting up the DNS record is beyond the scope of this document; check with your
|
||||
DNS provider for information.
|
||||
|
||||
|
@ -183,7 +183,7 @@ See [Auto DevOps requirements for Amazon ECS](requirements.md#auto-devops-requir
|
|||
|
||||
## Enabling/Disabling Auto DevOps
|
||||
|
||||
When first using Auto DevOps, review the [requirements](#requirements) to ensure
|
||||
When first using Auto DevOps, review the [requirements](requirements.md) to ensure
|
||||
all the necessary components to make full use of Auto DevOps are available. First-time
|
||||
users should follow the [quick start guide](quick_start_guide.md).
|
||||
|
||||
|
|
|
@ -65,11 +65,16 @@ To add a broadcast message:
|
|||
|
||||
1. Navigate to the **Admin Area > Messages** page.
|
||||
1. Add the text for the message to the **Message** field. Markdown and emoji are supported.
|
||||
1. If required, click the **Customize colors** link to edit the background color and font color of the message.
|
||||
1. Select one of the suggested background colors, or add the hex code of a different color. The default color is orange.
|
||||
1. If required, add a **Target Path** to only show the broadcast message on URLs matching that path. You can use the wildcard character `*` to match multiple URLs, for example `/users/*/issues`.
|
||||
1. Select a date for the message to start and end.
|
||||
1. Click the **Add broadcast message** button.
|
||||
|
||||
NOTE: **Note:**
|
||||
The **Background color** field expects the value to be a hexadecimal code because
|
||||
the form uses the [color_field](https://api.rubyonrails.org/v6.0.3.4/classes/ActionView/Helpers/FormHelper.html#method-i-color_field)
|
||||
helper method, which generates the proper HTML to render.
|
||||
|
||||
NOTE: **Note:**
|
||||
Once a broadcast message has expired, it is no longer displayed in the UI but is still listed in the
|
||||
list of broadcast messages. User can also dismiss a broadcast message if the option **Dismissable** is set.
|
||||
|
|
|
@ -46,6 +46,22 @@ To enforce confirmation of the email address used for new sign ups:
|
|||
1. Go to **Admin Area > Settings > General** and expand **Sign-up restrictions**.
|
||||
1. Select the **Enable email restrictions for sign ups** checkbox, then select **Save changes**.
|
||||
|
||||
## Soft email confirmation
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2.
|
||||
> - It's [deployed behind a feature flag](../../..//user/feature_flags.md), disabled by default.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-soft-email-confirmation).
|
||||
|
||||
CAUTION: **Warning:**
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
||||
The soft email confirmation improves the signup experience for new users by allowing
|
||||
them to sign in without an immediate confirmation when an email confirmation is required.
|
||||
GitLab shows the user a reminder to confirm their email address, and the user can't
|
||||
create or update pipelines until their email address is confirmed.
|
||||
|
||||
## Minimum password length limit
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20661) in GitLab 12.6
|
||||
|
@ -90,6 +106,25 @@ semicolon, comma, or a new line.
|
|||
|
||||
![Domain Denylist](img/domain_denylist.png)
|
||||
|
||||
### Enable or disable soft email confirmation
|
||||
|
||||
Soft email confirmation is under development but ready for production use.
|
||||
It is deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can opt to disable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:soft_email_confirmation)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:soft_email_confirmation)
|
||||
```
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
|
|
|
@ -299,7 +299,7 @@ To recover a default stage that was previously hidden:
|
|||
|
||||
A default value stream is readily available for each group. You can create additional value streams based on the different areas of work that you would like to measure.
|
||||
|
||||
Once created, a new value stream includes the [seven stages](#overview) that follow
|
||||
Once created, a new value stream includes the [seven stages](#default-stages) that follow
|
||||
[GitLab workflow](../../topics/gitlab_flow.md)
|
||||
best practices. You can customize this flow by adding, hiding or re-ordering stages.
|
||||
|
||||
|
|
|
@ -71,11 +71,11 @@ The following languages and dependency managers are supported:
|
|||
|
||||
Plans are underway for supporting the following languages, dependency managers, and dependency files. For details, see the issue link for each.
|
||||
|
||||
| Package Managers | Languages | Supported files | Scan tools |
|
||||
| ------------------- | --------- | --------------- | ------------ |
|
||||
| Package Managers | Languages | Supported files | Scan tools | Issue |
|
||||
| ------------------- | --------- | --------------- | ---------- | ----- |
|
||||
| [Pipenv](https://pipenv.pypa.io/en/latest/) | Python | `Pipfile.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#11756](https://gitlab.com/gitlab-org/gitlab/-/issues/11756) |
|
||||
| [Poetry](https://python-poetry.org/) | Python | `poetry.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#7006](https://gitlab.com/gitlab-org/gitlab/-/issues/7006) |
|
||||
| [sbt](https://www.scala-sbt.org/) 1.3+ ([Coursier](https://get-coursier.io/))| Scala | `build.sbt` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#249526](https://gitlab.com/gitlab-org/gitlab/-/issues/249526) |
|
||||
| [sbt](https://www.scala-sbt.org/) 1.3+ ([Coursier](https://get-coursier.io/))| Scala | `build.sbt` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#271345](https://gitlab.com/gitlab-org/gitlab/-/issues/271345) |
|
||||
|
||||
## Contribute your scanner
|
||||
|
||||
|
|
|
@ -609,7 +609,7 @@ Instructions are available in the [legacy template project](https://gitlab.com/g
|
|||
|
||||
This is the current default behavior, because the job's status indicates success or failure of the analyzer itself.
|
||||
Analyzer results are displayed in the [job logs](../../ci/pipelines/#expand-and-collapse-job-log-sections),
|
||||
[Merge Request widget](sast/index.md#overview)
|
||||
[Merge Request widget](sast/index.md)
|
||||
or [Security Dashboard](security_dashboard/index.md).
|
||||
There is [an open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/235772) in which changes to this behavior are being discussed.
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ as shown in the following table:
|
|||
| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
|
||||
| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** |
|
||||
| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
|
||||
| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
|
||||
| Presentation of JSON Report in Merge Request | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities) | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Access to Security Dashboard](#security-dashboard) | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Configure SAST in the UI](#configure-sast-in-the-ui) | **{dotted-circle}** | **{check-circle}** |
|
||||
|
|
|
@ -83,7 +83,7 @@ as shown in the following table:
|
|||
| [Configure Secret Detection Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
|
||||
| [Customize Secret Detection Settings](#customizing-settings) | **{check-circle}** | **{check-circle}** |
|
||||
| View [JSON Report](../sast/index.md#reports-json-format) | **{check-circle}** | **{check-circle}** |
|
||||
| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
|
||||
| Presentation of JSON Report in Merge Request | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Interaction with Vulnerabilities](../vulnerabilities/index.md) | **{dotted-circle}** | **{check-circle}** |
|
||||
| [Access to Security Dashboard](../security_dashboard/index.md) | **{dotted-circle}** | **{check-circle}** |
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@ The Dependency Proxy is not available for projects.
|
|||
|
||||
## Use the Dependency Proxy for Docker images
|
||||
|
||||
CAUTION: **Important:**
|
||||
In some specific storage configurations, an issue occurs and container images are not pulled correctly from the cache. The problem occurs when an image is located in object storage. The proxy looks for it locally and fails to find it. View [issue #208080](https://gitlab.com/gitlab-org/gitlab/-/issues/208080) for details.
|
||||
|
||||
You can use GitLab as a source for your Docker images.
|
||||
|
||||
Prerequisites:
|
||||
|
|
|
@ -11,7 +11,7 @@ GitLab Pages offers.
|
|||
|
||||
To familiarize yourself with GitLab Pages first:
|
||||
|
||||
- Read an [introduction to GitLab Pages](index.md#overview).
|
||||
- Read an [introduction to GitLab Pages](index.md).
|
||||
- Learn [how to get started with Pages](index.md#getting-started).
|
||||
- Learn how to enable GitLab Pages
|
||||
across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md).
|
||||
|
|
|
@ -34,10 +34,10 @@ module API
|
|||
{ status: success, message: message }.merge(extra_options).compact
|
||||
end
|
||||
|
||||
def lfs_authentication_url(project)
|
||||
def lfs_authentication_url(container)
|
||||
# This is a separate method so that EE can alter its behaviour more
|
||||
# easily.
|
||||
project.http_url_to_repo
|
||||
container.lfs_http_url_to_repo
|
||||
end
|
||||
|
||||
def check_allowed(params)
|
||||
|
@ -135,6 +135,8 @@ module API
|
|||
end
|
||||
|
||||
post "/lfs_authenticate", feature_category: :source_code_management do
|
||||
not_found! unless container&.lfs_enabled?
|
||||
|
||||
status 200
|
||||
|
||||
unless actor.key_or_user
|
||||
|
@ -145,7 +147,7 @@ module API
|
|||
|
||||
Gitlab::LfsToken
|
||||
.new(actor.key_or_user)
|
||||
.authentication_payload(lfs_authentication_url(project))
|
||||
.authentication_payload(lfs_authentication_url(container))
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -63,7 +63,7 @@ module Gitlab
|
|||
|
||||
def key
|
||||
@key ||= begin
|
||||
key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project)
|
||||
key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project, default_enabled: true)
|
||||
Gitlab::CurrentSettings.ci_jwt_signing_key
|
||||
else
|
||||
Rails.application.secrets.openid_connect_signing_key
|
||||
|
|
|
@ -879,6 +879,9 @@ msgstr ""
|
|||
msgid "%{user_name} profile page"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{username} has asked for a GitLab account on your instance %{host}:"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{username}'s avatar"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5551,6 +5554,12 @@ msgstr ""
|
|||
msgid "Clears weight."
|
||||
msgstr ""
|
||||
|
||||
msgid "Click %{link_start}here%{link_end} to view the request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Click %{link_to} to view the request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Click the %{strong_open}Download%{strong_close} button and wait for downloading to complete."
|
||||
msgstr ""
|
||||
|
||||
|
@ -9993,6 +10002,9 @@ msgstr ""
|
|||
msgid "Email updates (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Email: %{email}"
|
||||
msgstr ""
|
||||
|
||||
msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
|
||||
msgstr ""
|
||||
|
||||
|
@ -12580,6 +12592,9 @@ msgstr ""
|
|||
msgid "GitLab API"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab Account Request"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab Billing Team."
|
||||
msgstr ""
|
||||
|
||||
|
@ -29564,6 +29579,9 @@ msgstr ""
|
|||
msgid "Username or email"
|
||||
msgstr ""
|
||||
|
||||
msgid "Username: %{username}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -53,6 +53,14 @@ RSpec.describe RegistrationsController do
|
|||
.to eq('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.')
|
||||
end
|
||||
|
||||
it 'emails the access request to approvers' do
|
||||
expect_next_instance_of(NotificationService) do |notification|
|
||||
allow(notification).to receive(:new_instance_access_request).with(User.find_by(email: 'new@user.com'))
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'email confirmation' do
|
||||
context 'when `send_user_confirmation_email` is true' do
|
||||
before do
|
||||
|
@ -86,6 +94,12 @@ RSpec.describe RegistrationsController do
|
|||
expect(flash[:notice]).to be_nil
|
||||
end
|
||||
|
||||
it 'does not email the approvers' do
|
||||
expect(NotificationService).not_to receive(:new)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'email confirmation' do
|
||||
context 'when `send_user_confirmation_email` is true' do
|
||||
before do
|
||||
|
|
|
@ -66,7 +66,7 @@ FactoryBot.define do
|
|||
|
||||
trait :with_sign_ins do
|
||||
sign_in_count { 3 }
|
||||
current_sign_in_at { Time.now }
|
||||
current_sign_in_at { FFaker::Time.between(10.days.ago, 1.day.ago) }
|
||||
last_sign_in_at { FFaker::Time.between(10.days.ago, 1.day.ago) }
|
||||
current_sign_in_ip { '127.0.0.1' }
|
||||
last_sign_in_ip { '127.0.0.1' }
|
||||
|
|
36
spec/fixtures/api/schemas/graphql/container_repository.json
vendored
Normal file
36
spec/fixtures/api/schemas/graphql/container_repository.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"expirationPolicyStartedAt": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"status": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"tagsCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"canDelete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import DetailedMetric from '~/performance_bar/components/detailed_metric.vue';
|
||||
|
@ -14,6 +15,11 @@ describe('detailedMetric', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findAllTraceBlocks = () => wrapper.findAll('pre');
|
||||
const findTraceBlockAtIndex = index => findAllTraceBlocks().at(index);
|
||||
const findExpandBacktraceBtns = () => wrapper.findAll('[data-testid="backtrace-expand-btn"]');
|
||||
const findExpandedBacktraceBtnAtIndex = index => findExpandBacktraceBtns().at(index);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
@ -37,7 +43,12 @@ describe('detailedMetric', () => {
|
|||
describe('when the current request has details', () => {
|
||||
const requestDetails = [
|
||||
{ duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] },
|
||||
{ duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] },
|
||||
{
|
||||
duration: '23',
|
||||
feature: 'rebase_in_progress',
|
||||
request: '',
|
||||
backtrace: ['other', 'example'],
|
||||
},
|
||||
];
|
||||
|
||||
describe('with a default metric name', () => {
|
||||
|
@ -82,7 +93,7 @@ describe('detailedMetric', () => {
|
|||
expect(request.text()).toContain(requestDetails[index].request);
|
||||
});
|
||||
|
||||
expect(wrapper.find('.text-expander.js-toggle-button')).not.toBeNull();
|
||||
expect(wrapper.find('.js-toggle-button')).not.toBeNull();
|
||||
|
||||
wrapper.findAll('.performance-bar-modal td:nth-child(2)').wrappers.forEach(request => {
|
||||
expect(request.text()).toContain('world');
|
||||
|
@ -96,6 +107,30 @@ describe('detailedMetric', () => {
|
|||
it('displays request warnings', () => {
|
||||
expect(wrapper.find(RequestWarning).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('can open and close traces', async () => {
|
||||
expect(findAllTraceBlocks()).toHaveLength(0);
|
||||
|
||||
// Each block click on a new trace and assert that the correct
|
||||
// count is open and that the content is what we expect to ensure
|
||||
// we opened or closed the right one
|
||||
const secondExpandButton = findExpandedBacktraceBtnAtIndex(1);
|
||||
|
||||
findExpandedBacktraceBtnAtIndex(0).vm.$emit('click');
|
||||
await nextTick();
|
||||
expect(findAllTraceBlocks()).toHaveLength(1);
|
||||
expect(findTraceBlockAtIndex(0).text()).toContain(requestDetails[0].backtrace[0]);
|
||||
|
||||
secondExpandButton.vm.$emit('click');
|
||||
await nextTick();
|
||||
expect(findAllTraceBlocks()).toHaveLength(2);
|
||||
expect(findTraceBlockAtIndex(1).text()).toContain(requestDetails[1].backtrace[0]);
|
||||
|
||||
secondExpandButton.vm.$emit('click');
|
||||
await nextTick();
|
||||
expect(findAllTraceBlocks()).toHaveLength(1);
|
||||
expect(findTraceBlockAtIndex(0).text()).toContain(requestDetails[0].backtrace[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using a custom metric title', () => {
|
||||
|
@ -140,7 +175,11 @@ describe('detailedMetric', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders only the number of calls', () => {
|
||||
it('renders only the number of calls', async () => {
|
||||
expect(trimText(wrapper.text())).toEqual('456 notification bullet');
|
||||
|
||||
findExpandedBacktraceBtnAtIndex(0).vm.$emit('click');
|
||||
await nextTick();
|
||||
expect(trimText(wrapper.text())).toEqual('456 notification backtrace bullet');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::ContainerRepositories::Destroy do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be_with_reload(:container_repository) { create(:container_repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:project) { container_repository.project }
|
||||
let(:id) { container_repository.to_global_id.to_s }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
|
||||
|
||||
describe '#resolve' do
|
||||
subject do
|
||||
described_class.new(object: nil, context: { current_user: user }, field: nil)
|
||||
.resolve(id: id)
|
||||
end
|
||||
|
||||
shared_examples 'destroying the container repository' do
|
||||
it 'destroys the container repistory' do
|
||||
expect(::Packages::CreateEventService)
|
||||
.to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original
|
||||
expect(DeleteContainerRepositoryWorker)
|
||||
.to receive(:perform_async).with(user.id, container_repository.id)
|
||||
|
||||
expect { subject }.to change { ::Packages::Event.count }.by(1)
|
||||
expect(container_repository.reload.delete_scheduled?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'denying access to container respository' do
|
||||
it 'raises an error' do
|
||||
expect(DeleteContainerRepositoryWorker)
|
||||
.not_to receive(:perform_async).with(user.id, container_repository.id)
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid id' do
|
||||
where(:user_role, :shared_examples_name) do
|
||||
:maintainer | 'destroying the container repository'
|
||||
:developer | 'destroying the container repository'
|
||||
:reporter | 'denying access to container respository'
|
||||
:guest | 'denying access to container respository'
|
||||
:anonymous | 'denying access to container respository'
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
project.send("add_#{user_role}", user) unless user_role == :anonymous
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid id' do
|
||||
let(:id) { 'gid://gitlab/ContainerRepository/5555' }
|
||||
|
||||
it_behaves_like 'denying access to container respository'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1740,6 +1740,16 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.instance_access_request_approvers_to_be_notified' do
|
||||
let_it_be(:admin_list) { create_list(:user, 12, :admin, :with_sign_ins) }
|
||||
|
||||
it 'returns up to the ten most recently active instance admins' do
|
||||
active_admins_in_recent_sign_in_desc_order = User.admins.active.order_recent_sign_in.limit(10)
|
||||
|
||||
expect(User.instance_access_request_approvers_to_be_notified).to eq(active_admins_in_recent_sign_in_desc_order)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.filter_items' do
|
||||
let(:user) { double }
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Destroying a container repository' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be_with_reload(:container_repository) { create(:container_repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:project) { container_repository.project }
|
||||
let(:id) { container_repository.to_global_id.to_s }
|
||||
|
||||
let(:query) do
|
||||
<<~GQL
|
||||
containerRepository {
|
||||
#{all_graphql_fields_for('ContainerRepository')}
|
||||
}
|
||||
errors
|
||||
GQL
|
||||
end
|
||||
|
||||
let(:params) { { id: container_repository.to_global_id.to_s } }
|
||||
let(:mutation) { graphql_mutation(:destroy_container_repository, params, query) }
|
||||
let(:mutation_response) { graphql_mutation_response(:destroyContainerRepository) }
|
||||
let(:container_repository_mutation_response) { mutation_response['containerRepository'] }
|
||||
|
||||
before do
|
||||
stub_container_registry_config(enabled: true)
|
||||
stub_container_registry_tags(tags: %w[a b c])
|
||||
end
|
||||
|
||||
shared_examples 'destroying the container repository' do
|
||||
it 'destroy the container repository' do
|
||||
expect(::Packages::CreateEventService)
|
||||
.to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original
|
||||
expect(DeleteContainerRepositoryWorker)
|
||||
.to receive(:perform_async).with(user.id, container_repository.id)
|
||||
|
||||
expect { subject }.to change { ::Packages::Event.count }.by(1)
|
||||
|
||||
expect(container_repository_mutation_response).to match_schema('graphql/container_repository')
|
||||
expect(container_repository_mutation_response['status']).to eq('DELETE_SCHEDULED')
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
shared_examples 'denying the mutation request' do
|
||||
it 'does not destroy the container repository' do
|
||||
expect(DeleteContainerRepositoryWorker)
|
||||
.not_to receive(:perform_async).with(user.id, container_repository.id)
|
||||
|
||||
expect { subject }.not_to change { ::Packages::Event.count }
|
||||
|
||||
expect(mutation_response).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
describe 'post graphql mutation' do
|
||||
subject { post_graphql_mutation(mutation, current_user: user) }
|
||||
|
||||
context 'with valid id' do
|
||||
where(:user_role, :shared_examples_name) do
|
||||
:maintainer | 'destroying the container repository'
|
||||
:developer | 'destroying the container repository'
|
||||
:reporter | 'denying the mutation request'
|
||||
:guest | 'denying the mutation request'
|
||||
:anonymous | 'denying the mutation request'
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
project.send("add_#{user_role}", user) unless user_role == :anonymous
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid id' do
|
||||
let(:params) { { id: 'gid://gitlab/ContainerRepository/5555' } }
|
||||
|
||||
it_behaves_like 'denying the mutation request'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -253,6 +253,7 @@ RSpec.describe API::Internal::Base do
|
|||
|
||||
describe "POST /internal/lfs_authenticate" do
|
||||
before do
|
||||
stub_lfs_setting(enabled: true)
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
|
@ -293,6 +294,33 @@ RSpec.describe API::Internal::Base do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
it 'returns a 404 when LFS is disabled on the project' do
|
||||
project.update!(lfs_enabled: false)
|
||||
lfs_auth_user(user.id, project)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'other repository types' do
|
||||
it 'returns the correct information for a project wiki' do
|
||||
wiki = create(:project_wiki, project: project)
|
||||
lfs_auth_user(user.id, wiki)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['username']).to eq(user.username)
|
||||
expect(json_response['repository_http_path']).to eq(wiki.http_url_to_repo)
|
||||
expect(json_response['expires_in']).to eq(Gitlab::LfsToken::DEFAULT_EXPIRE_TIME)
|
||||
expect(Gitlab::LfsToken.new(user).token_valid?(json_response['lfs_token'])).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns a 404 when the container does not support LFS' do
|
||||
snippet = create(:project_snippet)
|
||||
lfs_auth_user(user.id, snippet)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'deploy key' do
|
||||
|
|
|
@ -2305,6 +2305,26 @@ RSpec.describe NotificationService, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#new_instance_access_request', :deliver_mails_inline do
|
||||
let_it_be(:user) { create(:user, :blocked_pending_approval) }
|
||||
let_it_be(:admins) { create_list(:admin, 12, :with_sign_ins) }
|
||||
|
||||
subject { notification.new_instance_access_request(user) }
|
||||
|
||||
before do
|
||||
reset_delivered_emails!
|
||||
stub_application_setting(require_admin_approval_after_user_signup: true)
|
||||
end
|
||||
|
||||
it 'sends notification only to a maximum of ten most recently active instance admins' do
|
||||
ten_most_recently_active_instance_admins = User.admins.active.sort_by(&:current_sign_in_at).last(10)
|
||||
|
||||
subject
|
||||
|
||||
should_only_email(*ten_most_recently_active_instance_admins)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GroupMember', :deliver_mails_inline do
|
||||
let(:added_user) { create(:user) }
|
||||
|
||||
|
|
Loading…
Reference in a new issue