Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1f96548c39
commit
f3506a4dee
38 changed files with 403 additions and 286 deletions
|
@ -1 +1 @@
|
|||
3154e9cb4c1df03c5405e2bd8a429838ebde03f2
|
||||
cb5c003584e180415fe33d1712e0d5aee2a22b0c
|
||||
|
|
9
app/assets/javascripts/commons/bootstrap.js
vendored
9
app/assets/javascripts/commons/bootstrap.js
vendored
|
@ -1,7 +1,14 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
// bootstrap jQuery plugins
|
||||
import 'bootstrap';
|
||||
import 'bootstrap/js/dist/alert';
|
||||
import 'bootstrap/js/dist/button';
|
||||
import 'bootstrap/js/dist/collapse';
|
||||
import 'bootstrap/js/dist/modal';
|
||||
import 'bootstrap/js/dist/dropdown';
|
||||
import 'bootstrap/js/dist/popover';
|
||||
import 'bootstrap/js/dist/tooltip';
|
||||
import 'bootstrap/js/dist/tab';
|
||||
|
||||
// custom jQuery functions
|
||||
$.fn.extend({
|
||||
|
|
|
@ -137,6 +137,7 @@ export default {
|
|||
ref="modal"
|
||||
modal-id="ide-new-entry"
|
||||
data-qa-selector="new_file_modal"
|
||||
data-testid="ide-new-entry"
|
||||
:title="modalTitle"
|
||||
:ok-title="buttonLabel"
|
||||
ok-variant="success"
|
||||
|
|
|
@ -109,7 +109,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="markdownPreview" class="md-previewer">
|
||||
<div ref="markdownPreview" class="md-previewer" data-testid="md-previewer">
|
||||
<gl-skeleton-loading v-if="isLoading" />
|
||||
<div v-else class="md" v-html="previewContent"></div>
|
||||
</div>
|
||||
|
|
|
@ -10,29 +10,31 @@ class Projects::RawController < Projects::ApplicationController
|
|||
|
||||
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:blob) }
|
||||
|
||||
before_action :set_ref_and_path
|
||||
before_action :require_non_empty_project
|
||||
before_action :authorize_download_code!
|
||||
before_action :show_rate_limit, only: [:show], unless: :external_storage_request?
|
||||
before_action :assign_ref_vars
|
||||
before_action :redirect_to_external_storage, only: :show, if: :static_objects_external_storage_enabled?
|
||||
|
||||
feature_category :source_code_management
|
||||
|
||||
def show
|
||||
@blob = @repository.blob_at(@commit.id, @path)
|
||||
@blob = @repository.blob_at(@ref, @path)
|
||||
|
||||
send_blob(@repository, @blob, inline: (params[:inline] != 'false'), allow_caching: @project.public?)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_rate_limit
|
||||
def set_ref_and_path
|
||||
# This bypasses assign_ref_vars to avoid a Gitaly FindCommit lookup.
|
||||
# When rate limiting, we really don't care if a different commit is
|
||||
# being requested.
|
||||
_ref, path = extract_ref(get_id)
|
||||
# We don't need to find the commit to either rate limit or send the
|
||||
# blob.
|
||||
@ref, @path = extract_ref(get_id)
|
||||
end
|
||||
|
||||
if rate_limiter.throttled?(:show_raw_controller, scope: [@project, path], threshold: raw_blob_request_limit)
|
||||
def show_rate_limit
|
||||
if rate_limiter.throttled?(:show_raw_controller, scope: [@project, @path], threshold: raw_blob_request_limit)
|
||||
rate_limiter.log_request(request, :raw_blob_request_limit, current_user)
|
||||
|
||||
render plain: _('You cannot access the raw file. Please wait a minute.'), status: :too_many_requests
|
||||
|
|
|
@ -467,9 +467,7 @@ module ProjectsHelper
|
|||
}
|
||||
end
|
||||
|
||||
def can_view_operations_tab?(current_user, project)
|
||||
return false unless project.feature_available?(:operations, current_user)
|
||||
|
||||
def view_operations_tab_ability
|
||||
[
|
||||
:metrics_dashboard,
|
||||
:read_alert_management_alert,
|
||||
|
@ -479,7 +477,13 @@ module ProjectsHelper
|
|||
:read_cluster,
|
||||
:read_feature_flag,
|
||||
:read_terraform_state
|
||||
].any? do |ability|
|
||||
]
|
||||
end
|
||||
|
||||
def can_view_operations_tab?(current_user, project)
|
||||
return false unless project.feature_available?(:operations, current_user)
|
||||
|
||||
view_operations_tab_ability.any? do |ability|
|
||||
can?(current_user, ability, project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -62,6 +62,14 @@ module Clusters
|
|||
end
|
||||
end
|
||||
|
||||
def uninstall_command
|
||||
helm_command_module::DeleteCommand.new(
|
||||
name: name,
|
||||
rbac: cluster.platform_kubernetes_rbac?,
|
||||
files: files
|
||||
)
|
||||
end
|
||||
|
||||
def prepare_uninstall
|
||||
# Override if your application needs any action before
|
||||
# being uninstalled by Helm
|
||||
|
|
|
@ -3,14 +3,6 @@
|
|||
module Clusters
|
||||
module Concerns
|
||||
module ApplicationData
|
||||
def uninstall_command
|
||||
helm_command_module::DeleteCommand.new(
|
||||
name: name,
|
||||
rbac: cluster.platform_kubernetes_rbac?,
|
||||
files: files
|
||||
)
|
||||
end
|
||||
|
||||
def repository
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -513,6 +513,9 @@ class Repository
|
|||
# Don't attempt to return a special result if there is no blob at all
|
||||
return unless blob
|
||||
|
||||
# Don't attempt to return a special result if this can't be a README
|
||||
return blob unless Gitlab::FileDetector.type_of(blob.name) == :readme
|
||||
|
||||
# Don't attempt to return a special result unless we're looking at HEAD
|
||||
return blob unless head_commit&.sha == sha
|
||||
|
||||
|
|
|
@ -726,6 +726,7 @@ class User < ApplicationRecord
|
|||
u.name = 'GitLab Security Bot'
|
||||
u.website_url = Gitlab::Routing.url_helpers.help_page_url('user/application_security/security_bot/index.md')
|
||||
u.avatar = bot_avatar(image: 'security-bot.png')
|
||||
u.confirmed_at = Time.zone.now
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ class BasePolicy < DeclarativePolicy::Base
|
|||
with_options scope: :user, score: 0
|
||||
condition(:support_bot) { @user&.support_bot? }
|
||||
|
||||
desc "User is security bot"
|
||||
with_options scope: :user, score: 0
|
||||
condition(:security_bot) { @user&.security_bot? }
|
||||
|
||||
desc "User email is unconfirmed or user account is locked"
|
||||
with_options scope: :user, score: 0
|
||||
condition(:inactive) { @user&.confirmation_required_on_sign_in? || @user&.access_locked? }
|
||||
|
|
|
@ -48,7 +48,7 @@ class GlobalPolicy < BasePolicy
|
|||
prevent :use_slash_commands
|
||||
end
|
||||
|
||||
rule { blocked | (internal & ~migration_bot) }.policy do
|
||||
rule { blocked | (internal & ~migration_bot & ~security_bot) }.policy do
|
||||
prevent :access_git
|
||||
end
|
||||
|
||||
|
|
|
@ -75,3 +75,5 @@ class PipelineSerializer < BaseSerializer
|
|||
]
|
||||
end
|
||||
end
|
||||
|
||||
PipelineSerializer.prepend_if_ee('EE::PipelineSerializer')
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
|
||||
%hr
|
||||
= f.hidden_field :header_logo_cache
|
||||
= f.file_field :header_logo, class: ""
|
||||
= f.file_field :header_logo, class: "", accept: 'image/*'
|
||||
.hint
|
||||
Maximum file size is 1MB. Pages are optimized for a 28px tall header logo
|
||||
%hr
|
||||
|
@ -38,7 +38,7 @@
|
|||
= link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
|
||||
%hr
|
||||
= f.hidden_field :favicon_cache
|
||||
= f.file_field :favicon, class: ''
|
||||
= f.file_field :favicon, class: '', accept: 'image/*'
|
||||
.hint
|
||||
Maximum file size is 1MB. Image size must be 32x32px. Allowed image formats are #{favicon_extension_whitelist}.
|
||||
%br
|
||||
|
@ -70,7 +70,7 @@
|
|||
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo"
|
||||
%hr
|
||||
= f.hidden_field :logo_cache
|
||||
= f.file_field :logo, class: ""
|
||||
= f.file_field :logo, class: "", accept: 'image/*'
|
||||
.hint
|
||||
Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.well-confirmation.text-center.append-bottom-20
|
||||
.well-confirmation.text-center.gl-mb-6
|
||||
%h1.gl-mt-0
|
||||
Almost there...
|
||||
%p.lead.append-bottom-20
|
||||
%p.lead.gl-mb-6
|
||||
Please check your email to confirm your account
|
||||
%hr
|
||||
- if Gitlab::CurrentSettings.after_sign_up_text.present?
|
||||
|
@ -9,6 +9,6 @@
|
|||
= markdown_field(Gitlab::CurrentSettings, :after_sign_up_text)
|
||||
%p.text-center
|
||||
No confirmation email received? Please check your spam folder or
|
||||
.append-bottom-20.prepend-top-20.text-center
|
||||
.gl-mb-6.prepend-top-20.text-center
|
||||
%a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
|
||||
Request new confirmation email
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
.form-group
|
||||
= f.label :email, class: 'label-bold'
|
||||
= f.email_field :email, value: @invite_email, class: 'form-control middle', data: { qa_selector: 'new_user_email_field' }, required: true, title: _('Please provide a valid email address.')
|
||||
.form-group.append-bottom-20#password-strength
|
||||
.form-group.gl-mb-5#password-strength
|
||||
= f.label :password, class: 'label-bold'
|
||||
= f.password_field :password, class: 'form-control bottom', data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: s_('SignUp|Minimum length is %{minimum_password_length} characters.') % { minimum_password_length: @minimum_password_length }
|
||||
%p.gl-field-hint.text-secondary= s_('SignUp|Minimum length is %{minimum_password_length} characters.') % { minimum_password_length: @minimum_password_length }
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group.append-bottom-20
|
||||
.form-group.gl-mb-6
|
||||
= f.label :email
|
||||
= f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: 'Please provide a valid email address.'
|
||||
.clearfix
|
||||
|
|
|
@ -262,6 +262,8 @@
|
|||
%span
|
||||
= _('Incidents')
|
||||
|
||||
= render_if_exists 'projects/sidebar/oncall_schedules'
|
||||
|
||||
- if project_nav_tab? :serverless
|
||||
= nav_link(controller: :functions) do
|
||||
= link_to project_serverless_functions_path(@project), title: _('Serverless') do
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add type filtering in appearance page of the admin panel
|
||||
merge_request: 48709
|
||||
author: Paul Ungureanu @ungps
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove unnecessary Gitaly calls from raw endpoint
|
||||
merge_request: 48917
|
||||
author:
|
||||
type: performance
|
|
@ -362,6 +362,18 @@ module.exports = {
|
|||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
Popper: ['popper.js', 'default'],
|
||||
Alert: 'exports-loader?Alert!bootstrap/js/dist/alert',
|
||||
Button: 'exports-loader?Button!bootstrap/js/dist/button',
|
||||
Carousel: 'exports-loader?Carousel!bootstrap/js/dist/carousel',
|
||||
Collapse: 'exports-loader?Collapse!bootstrap/js/dist/collapse',
|
||||
Dropdown: 'exports-loader?Dropdown!bootstrap/js/dist/dropdown',
|
||||
Modal: 'exports-loader?Modal!bootstrap/js/dist/modal',
|
||||
Popover: 'exports-loader?Popover!bootstrap/js/dist/popover',
|
||||
Scrollspy: 'exports-loader?Scrollspy!bootstrap/js/dist/scrollspy',
|
||||
Tab: 'exports-loader?Tab!bootstrap/js/dist/tab',
|
||||
Tooltip: 'exports-loader?Tooltip!bootstrap/js/dist/tooltip',
|
||||
Util: 'exports-loader?Util!bootstrap/js/dist/util',
|
||||
}),
|
||||
|
||||
// if DLLs are enabled, detect whether the DLL exists and create it automatically if necessary
|
||||
|
|
|
@ -268,8 +268,8 @@ query($project_path: ID!) {
|
|||
}
|
||||
```
|
||||
|
||||
To ensure that we get consistent ordering, we will append an ordering on the primary
|
||||
key, in descending order. This is usually `id`, so basically we will add `order(id: :desc)`
|
||||
To ensure that we get consistent ordering, we append an ordering on the primary
|
||||
key, in descending order. This is usually `id`, so we add `order(id: :desc)`
|
||||
to the end of the relation. A primary key _must_ be available on the underlying table.
|
||||
|
||||
#### Shortcut fields
|
||||
|
@ -315,7 +315,7 @@ class MergeRequestPermissionsType < BasePermissionType
|
|||
end
|
||||
```
|
||||
|
||||
- **`permission_field`**: Will act the same as `graphql-ruby`'s
|
||||
- **`permission_field`**: Acts the same as `graphql-ruby`'s
|
||||
`field` method but setting a default description and type and making
|
||||
them non-nullable. These options can still be overridden by adding
|
||||
them as arguments.
|
||||
|
@ -323,7 +323,7 @@ end
|
|||
behaves the same way as `permission_field` and the same
|
||||
arguments can be overridden.
|
||||
- **`abilities`**: Allows exposing several abilities defined in our
|
||||
policies at once. The fields for these will all have be non-nullable
|
||||
policies at once. The fields for these must all be non-nullable
|
||||
booleans with a default description.
|
||||
|
||||
## Feature flags
|
||||
|
@ -331,7 +331,7 @@ end
|
|||
Developers can add [feature flags](../development/feature_flags/index.md) to GraphQL
|
||||
fields in the following ways:
|
||||
|
||||
- Add the `feature_flag` property to a field. This will allow the field to be _hidden_
|
||||
- Add the `feature_flag` property to a field. This allows the field to be _hidden_
|
||||
from the GraphQL schema when the flag is disabled.
|
||||
- Toggle the return value when resolving the field.
|
||||
|
||||
|
@ -339,7 +339,7 @@ You can refer to these guidelines to decide which approach to use:
|
|||
|
||||
- If your field is experimental, and its name or type is subject to
|
||||
change, use the `feature_flag` property.
|
||||
- If your field is stable and its definition will not change, even after the flag is
|
||||
- If your field is stable and its definition doesn't change, even after the flag is
|
||||
removed, toggle the return value of the field instead. Note that
|
||||
[all fields should be nullable](#nullable-fields) anyway.
|
||||
|
||||
|
@ -347,15 +347,15 @@ You can refer to these guidelines to decide which approach to use:
|
|||
|
||||
The `feature_flag` property allows you to toggle the field's
|
||||
[visibility](https://graphql-ruby.org/authorization/visibility.html)
|
||||
within the GraphQL schema. This will remove the field from the schema
|
||||
within the GraphQL schema. This removes the field from the schema
|
||||
when the flag is disabled.
|
||||
|
||||
A description is [appended](https://gitlab.com/gitlab-org/gitlab/-/blob/497b556/app/graphql/types/base_field.rb#L44-53)
|
||||
to the field indicating that it is behind a feature flag.
|
||||
|
||||
CAUTION: **Caution:**
|
||||
If a client queries for the field when the feature flag is disabled, the query will
|
||||
fail. Consider this when toggling the visibility of the feature on or off on
|
||||
If a client queries for the field when the feature flag is disabled, the query
|
||||
fails. Consider this when toggling the visibility of the feature on or off on
|
||||
production.
|
||||
|
||||
The `feature_flag` property does not allow the use of
|
||||
|
@ -385,7 +385,7 @@ When applying a feature flag to toggle the value of a field, the
|
|||
|
||||
- State that the value of the field can be toggled by a feature flag.
|
||||
- Name the feature flag.
|
||||
- State what the field will return when the feature flag is disabled (or
|
||||
- State what the field returns when the feature flag is disabled (or
|
||||
enabled, if more appropriate).
|
||||
|
||||
Example:
|
||||
|
@ -424,8 +424,8 @@ field :token, GraphQL::STRING_TYPE, null: true,
|
|||
```
|
||||
|
||||
The original `description` of the things being deprecated should be maintained,
|
||||
and should _not_ be updated to mention the deprecation. Instead, the `reason` will
|
||||
be appended to the `description`.
|
||||
and should _not_ be updated to mention the deprecation. Instead, the `reason`
|
||||
is appended to the `description`.
|
||||
|
||||
### Deprecation reason style guide
|
||||
|
||||
|
@ -484,13 +484,13 @@ module Types
|
|||
end
|
||||
```
|
||||
|
||||
If the enum will be used for a class property in Ruby that is not an uppercase string,
|
||||
you can provide a `value:` option that will adapt the uppercase value.
|
||||
If the enum is used for a class property in Ruby that is not an uppercase string,
|
||||
you can provide a `value:` option that adapts the uppercase value.
|
||||
|
||||
In the following example:
|
||||
|
||||
- GraphQL inputs of `OPENED` will be converted to `'opened'`.
|
||||
- Ruby values of `'opened'` will be converted to `"OPENED"` in GraphQL responses.
|
||||
- GraphQL inputs of `OPENED` are converted to `'opened'`.
|
||||
- Ruby values of `'opened'` are converted to `"OPENED"` in GraphQL responses.
|
||||
|
||||
```ruby
|
||||
module Types
|
||||
|
@ -536,7 +536,7 @@ When data to be returned by GraphQL is stored as
|
|||
GraphQL types whenever possible. Avoid using the `GraphQL::Types::JSON` type unless
|
||||
the JSON data returned is _truly_ unstructured.
|
||||
|
||||
If the structure of the JSON data varies, but will be one of a set of known possible
|
||||
If the structure of the JSON data varies, but is one of a set of known possible
|
||||
structures, use a
|
||||
[union](https://graphql-ruby.org/type_definitions/unions.html).
|
||||
An example of the use of a union for this purpose is
|
||||
|
@ -605,7 +605,7 @@ descriptions:
|
|||
this field do?". Example: `'Indicates project has a Git repository'`.
|
||||
- Always include the word `"timestamp"` when describing an argument or
|
||||
field of type `Types::TimeType`. This lets the reader know that the
|
||||
format of the property will be `Time`, rather than just `Date`.
|
||||
format of the property is `Time`, rather than just `Date`.
|
||||
- No `.` at end of strings.
|
||||
|
||||
Example:
|
||||
|
@ -618,7 +618,7 @@ field :closed_at, Types::TimeType, description: 'Timestamp of when the issue was
|
|||
|
||||
### `copy_field_description` helper
|
||||
|
||||
Sometimes we want to ensure that two descriptions will always be identical.
|
||||
Sometimes we want to ensure that two descriptions are always identical.
|
||||
For example, to keep a type field description the same as a mutation argument
|
||||
when they both represent the same property.
|
||||
|
||||
|
@ -641,8 +641,8 @@ abilities as in the Rails app.
|
|||
If the:
|
||||
|
||||
- Currently authenticated user fails the authorization, the authorized
|
||||
resource will be returned as `null`.
|
||||
- Resource is part of a collection, the collection will be filtered to
|
||||
resource is returned as `null`.
|
||||
- Resource is part of a collection, the collection is filtered to
|
||||
exclude the objects that the user's authorization checks failed against.
|
||||
|
||||
Also see [authorizing resources in a mutation](#authorizing-resources).
|
||||
|
@ -656,7 +656,7 @@ authorization checks of the loaded records.
|
|||
### Type authorization
|
||||
|
||||
Authorize a type by passing an ability to the `authorize` method. All
|
||||
fields with the same type will be authorized by checking that the
|
||||
fields with the same type is authorized by checking that the
|
||||
currently authenticated user has the required ability.
|
||||
|
||||
For example, the following authorization ensures that the currently
|
||||
|
@ -922,7 +922,7 @@ before calling `resolve`! An example can be seen in our [`GraphQLHelpers`](https
|
|||
|
||||
The full query is known in advance during execution, which means we can make use
|
||||
of [lookahead](https://graphql-ruby.org/queries/lookahead.html) to optimize our
|
||||
queries, and batch load associations we know we will need. Consider adding
|
||||
queries, and batch load associations we know we need. Consider adding
|
||||
lookahead support in your resolvers to avoid `N+1` performance issues.
|
||||
|
||||
To enable support for common lookahead use-cases (pre-loading associations when
|
||||
|
@ -996,7 +996,7 @@ When using resolvers, they can and should serve as the SSoT for field metadata.
|
|||
All field options (apart from the field name) can be declared on the resolver.
|
||||
These include:
|
||||
|
||||
- `type` (this is particularly important, and will soon be mandatory)
|
||||
- `type` (this is particularly important, and is planned to be mandatory)
|
||||
- `extras`
|
||||
- `description`
|
||||
|
||||
|
@ -1164,7 +1164,7 @@ argument :my_arg, GraphQL::STRING_TYPE,
|
|||
description: "A description of the argument"
|
||||
```
|
||||
|
||||
Each GraphQL `argument` defined will be passed to the `#resolve` method
|
||||
Each GraphQL `argument` defined is passed to the `#resolve` method
|
||||
of a mutation as keyword arguments.
|
||||
|
||||
Example:
|
||||
|
@ -1175,7 +1175,7 @@ def resolve(my_arg:)
|
|||
end
|
||||
```
|
||||
|
||||
`graphql-ruby` will automatically wrap up arguments into an
|
||||
`graphql-ruby` wraps up arguments into an
|
||||
[input type](https://graphql.org/learn/schema/#input-types).
|
||||
|
||||
For example, the
|
||||
|
@ -1231,7 +1231,7 @@ single mutation when multiple are performed within a single request.
|
|||
### The `resolve` method
|
||||
|
||||
The `resolve` method receives the mutation's arguments as keyword arguments.
|
||||
From here, we can call the service that will modify the resource.
|
||||
From here, we can call the service that modifies the resource.
|
||||
|
||||
The `resolve` method should then return a hash with the same field
|
||||
names as defined on the mutation including an `errors` array. For example,
|
||||
|
@ -1263,7 +1263,7 @@ should look like this:
|
|||
|
||||
To make the mutation available it must be defined on the mutation
|
||||
type that lives in `graphql/types/mutation_types`. The
|
||||
`mount_mutation` helper method will define a field based on the
|
||||
`mount_mutation` helper method defines a field based on the
|
||||
GraphQL-name of the mutation:
|
||||
|
||||
```ruby
|
||||
|
@ -1278,7 +1278,7 @@ module Types
|
|||
end
|
||||
```
|
||||
|
||||
Will generate a field called `mergeRequestSetWip` that
|
||||
Generates a field called `mergeRequestSetWip` that
|
||||
`Mutations::MergeRequests::SetWip` to be resolved.
|
||||
|
||||
### Authorizing resources
|
||||
|
@ -1301,13 +1301,13 @@ end
|
|||
We can then call `authorize!` in the `resolve` method, passing in the resource we
|
||||
want to validate the abilities for.
|
||||
|
||||
Alternatively, we can add a `find_object` method that will load the
|
||||
Alternatively, we can add a `find_object` method that loads the
|
||||
object on the mutation. This would allow you to use the
|
||||
`authorized_find!` helper method.
|
||||
|
||||
When a user is not allowed to perform the action, or an object is not
|
||||
found, we should raise a
|
||||
`Gitlab::Graphql::Errors::ResourceNotAvailable` error. Which will be
|
||||
`Gitlab::Graphql::Errors::ResourceNotAvailable` error which is
|
||||
correctly rendered to the clients.
|
||||
|
||||
### Errors in mutations
|
||||
|
@ -1418,8 +1418,8 @@ of errors should be treated as internal, and not shown to the user in specific
|
|||
detail.
|
||||
|
||||
We need to inform the user when the mutation fails, but we do not need to
|
||||
tell them why, since they cannot have caused it, and nothing they can do will
|
||||
fix it, although we may offer to retry the mutation.
|
||||
tell them why, since they cannot have caused it, and nothing they can do
|
||||
fixes it, although we may offer to retry the mutation.
|
||||
|
||||
#### Categorizing errors
|
||||
|
||||
|
@ -1483,7 +1483,7 @@ Sometimes a mutation or resolver may accept a number of optional
|
|||
arguments, but we still want to validate that at least one of the optional
|
||||
arguments is provided. In this situation, consider using the `#ready?`
|
||||
method within your mutation or resolver to provide the validation. The
|
||||
`#ready?` method will be called before any work is done within the
|
||||
`#ready?` method is called before any work is done within the
|
||||
`#resolve` method.
|
||||
|
||||
Example:
|
||||
|
@ -1547,11 +1547,11 @@ visit [Testing](graphql_guide/pagination.md#testing) for details.
|
|||
|
||||
To test GraphQL mutation requests, `GraphqlHelpers` provides 2
|
||||
helpers: `graphql_mutation` which takes the name of the mutation, and
|
||||
a hash with the input for the mutation. This will return a struct with
|
||||
a hash with the input for the mutation. This returns a struct with
|
||||
a mutation query, and prepared variables.
|
||||
|
||||
This struct can then be passed to the `post_graphql_mutation` helper,
|
||||
that will post the request with the correct parameters, like a GraphQL
|
||||
that posts the request with the correct parameters, like a GraphQL
|
||||
client would do.
|
||||
|
||||
To access the response of a mutation, the `graphql_mutation_response`
|
||||
|
|
|
@ -53,7 +53,7 @@ helpful to others and, when properly explained, its benefits outweigh the risks.
|
|||
If you think you have found an exception to this rule, contact the
|
||||
Technical Writing team.
|
||||
|
||||
We will add all troubleshooting information to the documentation, no matter how
|
||||
GitLab adds all troubleshooting information to the documentation, no matter how
|
||||
unlikely a user is to encounter a situation. For the [Troubleshooting sections](#troubleshooting),
|
||||
people in GitLab Support can merge additions themselves.
|
||||
|
||||
|
@ -83,7 +83,7 @@ different types. For example, [Divio recommends](https://www.divio.com/blog/docu
|
|||
|
||||
At GitLab, we have so many product changes in our monthly releases that we can't
|
||||
afford to continuously update multiple types of information. If we have multiple
|
||||
types, the information will become outdated. Therefore, we have a
|
||||
types, the information becomes outdated. Therefore, we have a
|
||||
[single template](../structure.md) for documentation.
|
||||
|
||||
We currently do not distinguish specific document types, although we are open to
|
||||
|
@ -93,8 +93,8 @@ improvement efforts, that point hasn't been reached.
|
|||
|
||||
### Link instead of summarize
|
||||
|
||||
There is a temptation to summarize the information on another page. This will
|
||||
cause the information to live in two places. Instead, link to the single source
|
||||
There is a temptation to summarize the information on another page, which
|
||||
causes the information to live in two places. Instead, link to the single source
|
||||
of truth and explain why it is important to consume the information.
|
||||
|
||||
### Organize by topic, not by type
|
||||
|
@ -137,7 +137,7 @@ that among any other documentation changes, you can either:
|
|||
our [documentation template](../structure.md#template-for-new-docs), if present.
|
||||
|
||||
The more we reflexively add useful information to the documentation, the more
|
||||
(and more successfully) the documentation will be used to efficiently accomplish
|
||||
the documentation helps others efficiently accomplish
|
||||
tasks and solve problems.
|
||||
|
||||
If you have questions when considering, authoring, or editing documentation, ask
|
||||
|
@ -160,11 +160,11 @@ Markdown rendering engine. For a complete Kramdown reference, see the
|
|||
[GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/markdown-guide/).
|
||||
|
||||
The [`gitlab-kramdown`](https://gitlab.com/gitlab-org/gitlab_kramdown) Ruby gem
|
||||
will support all [GFM markup](../../../user/markdown.md) in the future, which is
|
||||
all markup supported for display in the GitLab application itself. For now, use
|
||||
regular Markdown markup, following the rules in the linked style guide.
|
||||
will support all [GitLab Flavored Markdown](../../../user/markdown.md) in the future, which is
|
||||
all Markdown supported for display in the GitLab application itself. For now, use
|
||||
regular Markdown, following the rules in the linked style guide.
|
||||
|
||||
Note that Kramdown-specific markup (for example, `{:.class}`) doesn't render
|
||||
Kramdown-specific markup (for example, `{:.class}`) doesn't render
|
||||
properly on GitLab instances under [`/help`](../index.md#gitlab-help).
|
||||
|
||||
### HTML in Markdown
|
||||
|
@ -183,7 +183,7 @@ GitLab ensures that the Markdown used across all documentation is consistent, as
|
|||
well as easy to review and maintain, by [testing documentation changes](../testing.md)
|
||||
with [markdownlint](../testing.md#markdownlint). This lint test fails when any
|
||||
document has an issue with Markdown formatting that may cause the page to render
|
||||
incorrectly within GitLab. It will also fail when a document is using
|
||||
incorrectly within GitLab. It also fails when a document has
|
||||
non-standard Markdown (which may render correctly, but is not the current
|
||||
standard for GitLab documentation).
|
||||
|
||||
|
@ -193,7 +193,7 @@ A rule that could cause confusion is `MD044/proper-names`, as it might not be
|
|||
immediately clear what caused markdownlint to fail, or how to correct the
|
||||
failure. This rule checks a list of known words, listed in the `.markdownlint.json`
|
||||
file in each project, to verify proper use of capitalization and backticks.
|
||||
Words in backticks will be ignored by markdownlint.
|
||||
Words in backticks are ignored by markdownlint.
|
||||
|
||||
In general, product names should follow the exact capitalization of the official
|
||||
names of the products, protocols, and so on. See [`.markdownlint.json`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.markdownlint.json)
|
||||
|
@ -210,7 +210,7 @@ included in backticks. For example:
|
|||
|
||||
- "Change the `needs` keyword in your `.gitlab.yml`..."
|
||||
- `needs` is a parameter, and `.gitlab.yml` is a file, so both need backticks.
|
||||
Additionally, `.gitlab.yml` will fail markdownlint without backticks as it
|
||||
Additionally, `.gitlab.yml` without backticks fails markdownlint because it
|
||||
does not have capital G or L.
|
||||
- "Run `git clone` to clone a Git repository..."
|
||||
- `git clone` is a command, so it must be lowercase, while Git is the product,
|
||||
|
@ -286,14 +286,14 @@ Refer to the following items when working with directories and files:
|
|||
located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
|
||||
1. The `doc/topics/` directory holds topic-related technical content. Create
|
||||
`doc/topics/topic_name/subtopic_name/index.md` when subtopics become necessary.
|
||||
General user- and admin- related documentation, should be placed accordingly.
|
||||
General user and administrator documentation should be placed accordingly.
|
||||
1. The `/university/` directory is *deprecated* and the majority of its documentation
|
||||
has been moved.
|
||||
|
||||
If you're unsure where to place a document or a content addition, this shouldn't
|
||||
stop you from authoring and contributing. Use your best judgment, and then ask
|
||||
the reviewer of your MR to confirm your decision, or ask a technical writer at
|
||||
any stage in the process. The technical writing team will review all
|
||||
any stage in the process. The technical writing team reviews all
|
||||
documentation changes, regardless, and can move content if there is a better
|
||||
place for it.
|
||||
|
||||
|
@ -542,8 +542,8 @@ tenses, words, and phrases:
|
|||
- Avoid use of the future tense:
|
||||
- Instead of "after you execute this command, GitLab will display the
|
||||
result", use "after you execute this command, GitLab displays the result".
|
||||
- Only use the future tense to convey when the action or result will actually
|
||||
occur at a future time.
|
||||
- Only use the future tense to convey when the action or result actually
|
||||
occurs at a future time.
|
||||
- Don't use slashes to clump different words together or as a replacement for
|
||||
the word "or":
|
||||
- Instead of "and/or," consider using "or," or use another sensible
|
||||
|
@ -566,7 +566,7 @@ tenses, words, and phrases:
|
|||
- Avoid using the word *currently* when talking about the product or its
|
||||
features. The documentation describes the product as it is, and not as it
|
||||
will be at some indeterminate point in the future.
|
||||
- Avoid using the word scalability when talking about increasing GitLab
|
||||
- Avoid using the word *scalability* when talking about increasing GitLab
|
||||
performance for additional users. The words scale or scaling are sometimes
|
||||
acceptable, but references to increasing GitLab performance for additional
|
||||
users should direct readers to the GitLab
|
||||
|
@ -673,8 +673,8 @@ Follow these guidelines for punctuation:
|
|||
|
||||
### Placeholder text
|
||||
|
||||
Often in examples, a writer will provide a command or configuration that
|
||||
uses values specific to the reader.
|
||||
You might want to provide a command or configuration that
|
||||
uses specific values.
|
||||
|
||||
In these cases, use [`<` and `>`](https://en.wikipedia.org/wiki/Usage_message#Pattern)
|
||||
to call out where a reader must replace text with their own value.
|
||||
|
|
|
@ -11,7 +11,7 @@ GitLab creates a new Project with an associated Git repository that is a
|
|||
copy of the original project at the time of the fork. If a large project
|
||||
gets forked often, this can lead to a quick increase in Git repository
|
||||
storage disk use. To counteract this problem, we are adding Git object
|
||||
deduplication for forks to GitLab. In this document, we will describe how
|
||||
deduplication for forks to GitLab. In this document, we describe how
|
||||
GitLab implements Git object deduplication.
|
||||
|
||||
## Pool repositories
|
||||
|
@ -27,9 +27,9 @@ If we want repository A to borrow from repository B, we first write a
|
|||
path that resolves to `B.git/objects` in the special file
|
||||
`A.git/objects/info/alternates`. This establishes the alternates link.
|
||||
Next, we must perform a Git repack in A. After the repack, any objects
|
||||
that are duplicated between A and B will get deleted from A. Repository
|
||||
that are duplicated between A and B are deleted from A. Repository
|
||||
A is now no longer self-contained, but it still has its own refs and
|
||||
configuration. Objects in A that are not in B will remain in A. For this
|
||||
configuration. Objects in A that are not in B remain in A. For this
|
||||
to work, it is of course critical that **no objects ever get deleted from
|
||||
B** because A might need them.
|
||||
|
||||
|
@ -49,7 +49,7 @@ repositories** which are hidden from the user. We then use Git
|
|||
alternates to let a collection of project repositories borrow from a
|
||||
single pool repository. We call such a collection of project
|
||||
repositories a pool. Pools form star-shaped networks of repositories
|
||||
that borrow from a single pool, which will resemble (but not be
|
||||
that borrow from a single pool, which resemble (but not be
|
||||
identical to) the fork networks that get formed when users fork
|
||||
projects.
|
||||
|
||||
|
@ -72,9 +72,9 @@ across a collection of GitLab project repositories at the Git level:
|
|||
The effectiveness of Git object deduplication in GitLab depends on the
|
||||
amount of overlap between the pool repository and each of its
|
||||
participants. Each time garbage collection runs on the source project,
|
||||
Git objects from the source project will get migrated to the pool
|
||||
Git objects from the source project are migrated to the pool
|
||||
repository. One by one, as garbage collection runs, other member
|
||||
projects will benefit from the new objects that got added to the pool.
|
||||
projects benefit from the new objects that got added to the pool.
|
||||
|
||||
## SQL model
|
||||
|
||||
|
@ -123,19 +123,19 @@ are as follows:
|
|||
the fork parent and the fork child project become members of the new
|
||||
pool.
|
||||
- Once project A has become the source project of a pool, all future
|
||||
eligible forks of A will become pool members.
|
||||
eligible forks of A become pool members.
|
||||
- If the fork source is itself a fork, the resulting repository will
|
||||
neither join the repository nor will a new pool repository be
|
||||
neither join the repository nor is a new pool repository
|
||||
seeded.
|
||||
|
||||
eg:
|
||||
Such as:
|
||||
|
||||
Suppose fork A is part of a pool repository, any forks created off
|
||||
of fork A *will not* be a part of the pool repository that fork A is
|
||||
of fork A *are not* a part of the pool repository that fork A is
|
||||
a part of.
|
||||
|
||||
Suppose B is a fork of A, and A does not belong to an object pool.
|
||||
Now C gets created as a fork of B. C will not be part of a pool
|
||||
Now C gets created as a fork of B. C is not part of a pool
|
||||
repository.
|
||||
|
||||
> TODO should forks of forks be deduplicated?
|
||||
|
@ -146,11 +146,11 @@ are as follows:
|
|||
- If a normal Project participating in a pool gets moved to another
|
||||
Gitaly storage shard, its "belongs to PoolRepository" relation will
|
||||
be broken. Because of the way moving repositories between shard is
|
||||
implemented, we will automatically get a fresh self-contained copy
|
||||
implemented, we get a fresh self-contained copy
|
||||
of the project's repository on the new storage shard.
|
||||
- If the source project of a pool gets moved to another Gitaly storage
|
||||
shard or is deleted the "source project" relation is not broken.
|
||||
However, as of GitLab 12.0 a pool will not fetch from a source
|
||||
However, as of GitLab 12.0 a pool does not fetch from a source
|
||||
unless the source is on the same Gitaly shard.
|
||||
|
||||
## Consistency between the SQL pool relation and Gitaly
|
||||
|
@ -163,7 +163,7 @@ repository and a pool.
|
|||
### Pool existence
|
||||
|
||||
If GitLab thinks a pool repository exists (i.e. it exists according to
|
||||
SQL), but it does not on the Gitaly server, then it will be created on
|
||||
SQL), but it does not on the Gitaly server, then it is created on
|
||||
the fly by Gitaly.
|
||||
|
||||
### Pool relation existence
|
||||
|
@ -173,27 +173,27 @@ There are three different things that can go wrong here.
|
|||
#### 1. SQL says repo A belongs to pool P but Gitaly says A has no alternate objects
|
||||
|
||||
In this case, we miss out on disk space savings but all RPC's on A
|
||||
itself will function fine. The next time garbage collection runs on A,
|
||||
itself function fine. The next time garbage collection runs on A,
|
||||
the alternates connection gets established in Gitaly. This is done by
|
||||
`Projects::GitDeduplicationService` in GitLab Rails.
|
||||
|
||||
#### 2. SQL says repo A belongs to pool P1 but Gitaly says A has alternate objects in pool P2
|
||||
|
||||
In this case `Projects::GitDeduplicationService` will throw an exception.
|
||||
In this case `Projects::GitDeduplicationService` throws an exception.
|
||||
|
||||
#### 3. SQL says repo A does not belong to any pool but Gitaly says A belongs to P
|
||||
|
||||
In this case `Projects::GitDeduplicationService` will try to
|
||||
In this case `Projects::GitDeduplicationService` tries to
|
||||
"re-duplicate" the repository A using the DisconnectGitAlternates RPC.
|
||||
|
||||
## Git object deduplication and GitLab Geo
|
||||
|
||||
When a pool repository record is created in SQL on a Geo primary, this
|
||||
will eventually trigger an event on the Geo secondary. The Geo secondary
|
||||
will then create the pool repository in Gitaly. This leads to an
|
||||
eventually triggers an event on the Geo secondary. The Geo secondary
|
||||
then creates the pool repository in Gitaly. This leads to an
|
||||
"eventually consistent" situation because as each pool participant gets
|
||||
synchronized, Geo will eventually trigger garbage collection in Gitaly on
|
||||
the secondary, at which stage Git objects will get deduplicated.
|
||||
synchronized, Geo eventually triggers garbage collection in Gitaly on
|
||||
the secondary, at which stage Git objects are deduplicated.
|
||||
|
||||
> TODO How do we handle the edge case where at the time the Geo
|
||||
> secondary tries to create the pool repository, the source project does
|
||||
|
|
|
@ -46,8 +46,8 @@ and running.
|
|||
|
||||
Can the queries used potentially take down any critical services and result in
|
||||
engineers being woken up in the night? Can a malicious user abuse the code to
|
||||
take down a GitLab instance? Will my changes simply make loading a certain page
|
||||
slower? Will execution time grow exponentially given enough load or data in the
|
||||
take down a GitLab instance? Do my changes simply make loading a certain page
|
||||
slower? Does execution time grow exponentially given enough load or data in the
|
||||
database?
|
||||
|
||||
These are all questions one should ask themselves before submitting a merge
|
||||
|
@ -67,14 +67,14 @@ in turn can request a performance specialist to review the changes.
|
|||
|
||||
## Think outside of the box
|
||||
|
||||
Everyone has their own perception how the new feature is going to be used.
|
||||
Everyone has their own perception of how to use the new feature.
|
||||
Always consider how users might be using the feature instead. Usually,
|
||||
users test our features in a very unconventional way,
|
||||
like by brute forcing or abusing edge conditions that we have.
|
||||
|
||||
## Data set
|
||||
|
||||
The data set that will be processed by the merge request should be known
|
||||
The data set the merge request processes should be known
|
||||
and documented. The feature should clearly document what the expected
|
||||
data set is for this feature to process, and what problems it might cause.
|
||||
|
||||
|
@ -86,8 +86,8 @@ from the repository and perform search for the set of files.
|
|||
As an author you should in context of that problem consider
|
||||
the following:
|
||||
|
||||
1. What repositories are going to be supported?
|
||||
1. How long it will take for big repositories like Linux kernel?
|
||||
1. What repositories are planned to be supported?
|
||||
1. How long it do big repositories like Linux kernel take?
|
||||
1. Is there something that we can do differently to not process such a
|
||||
big data set?
|
||||
1. Should we build some fail-safe mechanism to contain
|
||||
|
@ -96,28 +96,28 @@ the following:
|
|||
|
||||
## Query plans and database structure
|
||||
|
||||
The query plan can tell us if we will need additional
|
||||
The query plan can tell us if we need additional
|
||||
indexes, or expensive filtering (such as using sequential scans).
|
||||
|
||||
Each query plan should be run against substantial size of data set.
|
||||
For example, if you look for issues with specific conditions,
|
||||
you should consider validating a query against
|
||||
a small number (a few hundred) and a big number (100_000) of issues.
|
||||
See how the query will behave if the result will be a few
|
||||
See how the query behaves if the result is a few
|
||||
and a few thousand.
|
||||
|
||||
This is needed as we have users using GitLab for very big projects and
|
||||
in a very unconventional way. Even if it seems that it's unlikely
|
||||
that such a big data set will be used, it's still plausible that one
|
||||
of our customers will encounter a problem with the feature.
|
||||
that such a big data set is used, it's still plausible that one
|
||||
of our customers could encounter a problem with the feature.
|
||||
|
||||
Understanding ahead of time how it's going to behave at scale, even if we accept it,
|
||||
is the desired outcome. We should always have a plan or understanding of what it will take
|
||||
Understanding ahead of time how it behaves at scale, even if we accept it,
|
||||
is the desired outcome. We should always have a plan or understanding of what is needed
|
||||
to optimize the feature for higher usage patterns.
|
||||
|
||||
Every database structure should be optimized and sometimes even over-described
|
||||
in preparation for easy extension. The hardest part after some point is
|
||||
data migration. Migrating millions of rows will always be troublesome and
|
||||
data migration. Migrating millions of rows is always troublesome and
|
||||
can have a negative impact on the application.
|
||||
|
||||
To better understand how to get help with the query plan reviews
|
||||
|
@ -130,7 +130,7 @@ queries unless absolutely necessary.
|
|||
|
||||
The total number of queries executed by the code modified or added by a merge request
|
||||
must not increase unless absolutely necessary. When building features it's
|
||||
entirely possible you will need some extra queries, but you should try to keep
|
||||
entirely possible you need some extra queries, but you should try to keep
|
||||
this at a minimum.
|
||||
|
||||
As an example, say you introduce a feature that updates a number of database
|
||||
|
@ -144,7 +144,7 @@ objects_to_update.each do |object|
|
|||
end
|
||||
```
|
||||
|
||||
This will end up running one query for every object to update. This code can
|
||||
This means running one query for every object to update. This code can
|
||||
easily overload a database given enough rows to update or many instances of this
|
||||
code running in parallel. This particular problem is known as the
|
||||
["N+1 query problem"](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations). You can write a test with [QueryRecorder](query_recorder.md) to detect this and prevent regressions.
|
||||
|
@ -162,10 +162,10 @@ query. This in turn makes it much harder for this code to overload a database.
|
|||
|
||||
**Summary:** a merge request **should not** execute duplicated cached queries.
|
||||
|
||||
Rails provides an [SQL Query Cache](cached_queries.md#cached-queries-guidelines),
|
||||
used to cache the results of database queries for the duration of the request.
|
||||
Rails provides an [SQL Query Cache](cached_queries.md#cached-queries-guidelines),
|
||||
used to cache the results of database queries for the duration of the request.
|
||||
|
||||
See [why cached queries are considered bad](cached_queries.md#why-cached-queries-are-considered-bad) and
|
||||
See [why cached queries are considered bad](cached_queries.md#why-cached-queries-are-considered-bad) and
|
||||
[how to detect them](cached_queries.md#how-to-detect-cached-queries).
|
||||
|
||||
The code introduced by a merge request, should not execute multiple duplicated cached queries.
|
||||
|
@ -189,8 +189,8 @@ build.project == pipeline_project
|
|||
# => true
|
||||
```
|
||||
|
||||
When we call `build.project`, it will not hit the database, it will use the cached result, but it will re-instantiate
|
||||
same pipeline project object. It turns out that associated objects do not point to the same in-memory object.
|
||||
When we call `build.project`, it doesn't hit the database, it uses the cached result, but it re-instantiates
|
||||
the same pipeline project object. It turns out that associated objects do not point to the same in-memory object.
|
||||
|
||||
If we try to serialize each build:
|
||||
|
||||
|
@ -200,7 +200,7 @@ pipeline.builds.each do |build|
|
|||
end
|
||||
```
|
||||
|
||||
It will re-instantiate project object for each build, instead of using the same in-memory object.
|
||||
It re-instantiates project object for each build, instead of using the same in-memory object.
|
||||
|
||||
In this particular case the workaround is fairly easy:
|
||||
|
||||
|
@ -212,7 +212,7 @@ end
|
|||
```
|
||||
|
||||
We can assign `pipeline.project` to each `build.project`, since we know it should point to the same project.
|
||||
This will allow us that each build point to the same in-memory project,
|
||||
This allows us that each build point to the same in-memory project,
|
||||
avoiding the cached SQL query and re-instantiation of the project object for each build.
|
||||
|
||||
## Executing Queries in Loops
|
||||
|
@ -323,7 +323,7 @@ Certain UI elements may not always be needed. For example, when hovering over a
|
|||
diff line there's a small icon displayed that can be used to create a new
|
||||
comment. Instead of always rendering these kind of elements they should only be
|
||||
rendered when actually needed. This ensures we don't spend time generating
|
||||
Haml/HTML when it's not going to be used.
|
||||
Haml/HTML when it's not used.
|
||||
|
||||
## Instrumenting New Code
|
||||
|
||||
|
@ -411,8 +411,8 @@ The quota should be optimised to a level that we consider the feature to
|
|||
be performant and usable for the user, but **not limiting**.
|
||||
|
||||
**We want the features to be fully usable for the users.**
|
||||
**However, we want to ensure that the feature will continue to perform well if used at its limit**
|
||||
**and it won't cause availability issues.**
|
||||
**However, we want to ensure that the feature continues to perform well if used at its limit**
|
||||
**and it doesn't cause availability issues.**
|
||||
|
||||
Consider that it's always better to start with some kind of limitation,
|
||||
instead of later introducing a breaking change that would result in some
|
||||
|
@ -433,11 +433,11 @@ The intent of quotas could be different:
|
|||
|
||||
Examples:
|
||||
|
||||
1. Pipeline Schedules: It's very unlikely that user will want to create
|
||||
1. Pipeline Schedules: It's very unlikely that user wants to create
|
||||
more than 50 schedules.
|
||||
In such cases it's rather expected that this is either misuse
|
||||
or abuse of the feature. Lack of the upper limit can result
|
||||
in service degradation as the system will try to process all schedules
|
||||
in service degradation as the system tries to process all schedules
|
||||
assigned the project.
|
||||
|
||||
1. GitLab CI/CD includes: We started with the limit of maximum of 50 nested includes.
|
||||
|
@ -477,7 +477,7 @@ We can consider the following types of storages:
|
|||
for most of our implementations. Even though this allows the above limit to be significantly larger,
|
||||
it does not really mean that you can use more. The shared temporary storage is shared by
|
||||
all nodes. Thus, the job that uses significant amount of that space or performs a lot
|
||||
of operations will create a contention on execution of all other jobs and request
|
||||
of operations creates a contention on execution of all other jobs and request
|
||||
across the whole application, this can easily impact stability of the whole GitLab.
|
||||
Be respectful of that.
|
||||
|
||||
|
|
|
@ -467,7 +467,7 @@ of the `gitlab-org/gitlab-foss` project. These jobs are only created in the foll
|
|||
The `* as-if-foss` jobs are run in addition to the regular EE-context jobs. They have the `FOSS_ONLY='1'` variable
|
||||
set and get their EE-specific folders removed before the tests start running.
|
||||
|
||||
The intent is to ensure that a change won't introduce a failure once the `gitlab-org/gitlab` project will be synced to
|
||||
The intent is to ensure that a change doesn't introduce a failure after the `gitlab-org/gitlab` project is synced to
|
||||
the `gitlab-org/gitlab-foss` project.
|
||||
|
||||
## Performance
|
||||
|
@ -502,7 +502,7 @@ request, be sure to start the `dont-interrupt-me` job before pushing.
|
|||
- `update-assets-compile-production-cache`, defined in [`.gitlab/ci/frontend.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/frontend.gitlab-ci.yml).
|
||||
- `update-assets-compile-test-cache`, defined in [`.gitlab/ci/frontend.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/frontend.gitlab-ci.yml).
|
||||
- `update-yarn-cache`, defined in [`.gitlab/ci/frontend.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/frontend.gitlab-ci.yml).
|
||||
1. These jobs will run in merge requests whose title include `UPDATE CACHE`.
|
||||
1. These jobs run in merge requests whose title include `UPDATE CACHE`.
|
||||
|
||||
### Pre-clone step
|
||||
|
||||
|
@ -546,8 +546,7 @@ on a scheduled pipeline, it does the following:
|
|||
1. Saves the data as a `.tar.gz`.
|
||||
1. Uploads it into the Google Cloud Storage bucket.
|
||||
|
||||
When a CI job runs with this configuration, you'll see something like
|
||||
this:
|
||||
When a CI job runs with this configuration, the output looks something like this:
|
||||
|
||||
```shell
|
||||
$ eval "$CI_PRE_CLONE_SCRIPT"
|
||||
|
@ -568,7 +567,7 @@ GitLab Team Member, find credentials in the
|
|||
[GitLab shared 1Password account](https://about.gitlab.com/handbook/security/#1password-for-teams).
|
||||
|
||||
Note that this bucket should be located in the same continent as the
|
||||
runner, or [network egress charges will apply](https://cloud.google.com/storage/pricing).
|
||||
runner, or [you can incur network egress charges](https://cloud.google.com/storage/pricing).
|
||||
|
||||
## CI configuration internals
|
||||
|
||||
|
@ -662,7 +661,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
|
|||
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success\|manual`), or **not** create a job for forks (by using `when: never`). |
|
||||
| `if-not-ee` | Matches if the project isn't EE (i.e. project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
|
||||
| `if-not-foss` | Matches if the project isn't FOSS (i.e. project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
|
||||
| `if-default-refs` | Matches if the pipeline is for `master`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs won't be created for branches with this default configuration. |
|
||||
| `if-default-refs` | Matches if the pipeline is for `master`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
|
||||
| `if-master-refs` | Matches if the current branch is `master`. | |
|
||||
| `if-master-or-tag` | Matches if the pipeline is for the `master` branch or for a tag. | |
|
||||
| `if-merge-request` | Matches if the pipeline is for a merge request. | |
|
||||
|
|
|
@ -52,11 +52,11 @@ version. The range of supported versions is based on the evaluation of:
|
|||
GitLab supports the following Kubernetes versions, and you can upgrade your
|
||||
Kubernetes version to any supported version at any time:
|
||||
|
||||
- 1.18
|
||||
- 1.17
|
||||
- 1.16
|
||||
- 1.15
|
||||
- 1.14 (deprecated, support ends on December 22, 2020)
|
||||
- 1.13 (deprecated, support ends on November 22, 2020)
|
||||
|
||||
Some GitLab features may support versions outside the range provided here.
|
||||
|
||||
|
|
|
@ -75,7 +75,9 @@ Let's consider the following scenario:
|
|||
A unique job token is generated for each job and provides the user read
|
||||
access all projects that would be normally accessible to the user creating that
|
||||
job. The unique job token does not have any write permissions, but there
|
||||
is a [proposal to add support](https://gitlab.com/gitlab-org/gitlab/-/issues/35067).
|
||||
is a [proposal to add support](https://gitlab.com/groups/gitlab-org/-/epics/3559).
|
||||
|
||||
If you need your CI pipeline to push to the Package Registry, consider using [deploy tokens](deploy_tokens/index.md).
|
||||
|
||||
We try to make sure that this token doesn't leak by:
|
||||
|
||||
|
|
|
@ -190,6 +190,22 @@ Expect the output:
|
|||
_gitlab-pages-verification-code.<YOUR-PAGES-DOMAIN>. 300 IN TXT "gitlab-pages-verification-code=<YOUR-VERIFICATION-CODE>"
|
||||
```
|
||||
|
||||
In some cases it can help to add the verification code with the same domain name as you are trying to register.
|
||||
|
||||
For a root domain:
|
||||
|
||||
| From | DNS Record | To |
|
||||
| ------------------------------------------------- | ---------- | ---------------------- |
|
||||
| `example.com` | TXT | `gitlab-pages-verification-code=00112233445566778899aabbccddeeff` |
|
||||
| `_gitlab-pages-verification-code.example.com` | TXT | `gitlab-pages-verification-code=00112233445566778899aabbccddeeff` |
|
||||
|
||||
For a subdomain:
|
||||
|
||||
| From | DNS Record | To |
|
||||
| ------------------------------------------------- | ---------- | ---------------------- |
|
||||
| `www.example.com` | TXT | `gitlab-pages-verification-code=00112233445566778899aabbccddeeff` |
|
||||
| `_gitlab-pages-verification-code.www.example.com` | TXT | `gitlab-pages-verification-code=00112233445566778899aabbccddeeff` |
|
||||
|
||||
### Adding more domain aliases
|
||||
|
||||
You can add more than one alias (custom domains and subdomains) to the same project.
|
||||
|
|
|
@ -18991,6 +18991,21 @@ msgstr ""
|
|||
msgid "On track"
|
||||
msgstr ""
|
||||
|
||||
msgid "On-call Schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "On-call schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Add a schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Create on-call schedules in GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Route alerts directly to specific members of your team"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ require 'spec_helper'
|
|||
RSpec.describe Projects::RawController do
|
||||
include RepoHelpers
|
||||
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let(:inline) { nil }
|
||||
|
||||
describe 'GET #show' do
|
||||
subject do
|
||||
def get_show
|
||||
get(:show,
|
||||
params: {
|
||||
namespace_id: project.namespace,
|
||||
|
@ -19,6 +19,18 @@ RSpec.describe Projects::RawController do
|
|||
})
|
||||
end
|
||||
|
||||
subject { get_show }
|
||||
|
||||
shared_examples 'single Gitaly request' do
|
||||
it 'makes a single Gitaly request', :request_store, :clean_gitlab_redis_cache do
|
||||
# Warm up to populate repository cache
|
||||
get_show
|
||||
RequestStore.clear!
|
||||
|
||||
expect { get_show }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'regular filename' do
|
||||
let(:filepath) { 'master/README.md' }
|
||||
|
||||
|
@ -33,6 +45,7 @@ RSpec.describe Projects::RawController do
|
|||
|
||||
it_behaves_like 'project cache control headers'
|
||||
it_behaves_like 'content disposition headers'
|
||||
include_examples 'single Gitaly request'
|
||||
end
|
||||
|
||||
context 'image header' do
|
||||
|
@ -48,6 +61,7 @@ RSpec.describe Projects::RawController do
|
|||
|
||||
it_behaves_like 'project cache control headers'
|
||||
it_behaves_like 'content disposition headers'
|
||||
include_examples 'single Gitaly request'
|
||||
end
|
||||
|
||||
context 'with LFS files' do
|
||||
|
@ -56,6 +70,7 @@ RSpec.describe Projects::RawController do
|
|||
|
||||
it_behaves_like 'a controller that can serve LFS files'
|
||||
it_behaves_like 'project cache control headers'
|
||||
include_examples 'single Gitaly request'
|
||||
end
|
||||
|
||||
context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
|
||||
|
|
|
@ -47,6 +47,10 @@ FactoryBot.define do
|
|||
user_type { :migration_bot }
|
||||
end
|
||||
|
||||
trait :security_bot do
|
||||
user_type { :security_bot }
|
||||
end
|
||||
|
||||
trait :external do
|
||||
external { true }
|
||||
end
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'IDE user sees editor info', :js do
|
||||
include WebIdeSpecHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:user) { project.owner }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
ide_visit(project)
|
||||
end
|
||||
|
||||
it 'shows line position' do
|
||||
ide_open_file('README.md')
|
||||
|
||||
within find('.ide-status-bar') do
|
||||
expect(page).to have_content('1:1')
|
||||
end
|
||||
|
||||
ide_set_editor_position(4, 10)
|
||||
|
||||
within find('.ide-status-bar') do
|
||||
expect(page).not_to have_content('1:1')
|
||||
expect(page).to have_content('4:10')
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates after rename' do
|
||||
ide_open_file('README.md')
|
||||
ide_set_editor_position(4, 10)
|
||||
|
||||
within find('.ide-status-bar') do
|
||||
expect(page).to have_content('markdown')
|
||||
expect(page).to have_content('4:10')
|
||||
end
|
||||
|
||||
ide_rename_file('README.md', 'READMEZ.txt')
|
||||
|
||||
within find('.ide-status-bar') do
|
||||
expect(page).to have_content('plaintext')
|
||||
expect(page).to have_content('1:1')
|
||||
end
|
||||
end
|
||||
|
||||
it 'persists position after rename' do
|
||||
ide_open_file('README.md')
|
||||
ide_set_editor_position(4, 10)
|
||||
|
||||
ide_open_file('files/js/application.js')
|
||||
ide_rename_file('README.md', 'READING_RAINBOW.md')
|
||||
|
||||
ide_open_file('READING_RAINBOW.md')
|
||||
|
||||
within find('.ide-status-bar') do
|
||||
expect(page).to have_content('4:10')
|
||||
end
|
||||
end
|
||||
|
||||
it 'persists position' do
|
||||
ide_open_file('README.md')
|
||||
ide_set_editor_position(4, 10)
|
||||
|
||||
ide_close_file('README.md')
|
||||
ide_open_file('README.md')
|
||||
|
||||
within find('.ide-status-bar') do
|
||||
expect(page).to have_content('markdown')
|
||||
expect(page).to have_content('4:10')
|
||||
end
|
||||
end
|
||||
|
||||
it 'persists viewer' do
|
||||
ide_open_file('README.md')
|
||||
click_link('Preview Markdown')
|
||||
|
||||
within find('.md-previewer') do
|
||||
expect(page).to have_content('testme')
|
||||
end
|
||||
|
||||
# Switch away from and back to the file
|
||||
ide_open_file('.gitignore')
|
||||
ide_open_file('README.md')
|
||||
|
||||
# Preview is still enabled
|
||||
within find('.md-previewer') do
|
||||
expect(page).to have_content('testme')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,11 @@
|
|||
import { findAllByText, fireEvent, getByLabelText, screen } from '@testing-library/dom';
|
||||
import {
|
||||
findAllByText,
|
||||
fireEvent,
|
||||
getByLabelText,
|
||||
findByTestId,
|
||||
getByText,
|
||||
screen,
|
||||
} from '@testing-library/dom';
|
||||
|
||||
const isFolderRowOpen = row => row.matches('.folder.is-open');
|
||||
|
||||
|
@ -12,6 +19,11 @@ const clickOnLeftSidebarTab = name => {
|
|||
button.click();
|
||||
};
|
||||
|
||||
export const getStatusBar = () => document.querySelector('.ide-status-bar');
|
||||
|
||||
export const waitForMonacoEditor = () =>
|
||||
new Promise(resolve => window.monaco.editor.onDidCreateEditor(resolve));
|
||||
|
||||
export const findMonacoEditor = () =>
|
||||
screen.findByLabelText(/Editor content;/).then(x => x.closest('.monaco-editor'));
|
||||
|
||||
|
@ -75,11 +87,13 @@ const clickFileRowAction = (row, name) => {
|
|||
dropdownAction.click();
|
||||
};
|
||||
|
||||
const findAndSetFileName = async value => {
|
||||
const nameField = await screen.findByTestId('file-name-field');
|
||||
const fillFileNameModal = async (value, submitText = 'Create file') => {
|
||||
const modal = await screen.findByTestId('ide-new-entry');
|
||||
|
||||
const nameField = await findByTestId(modal, 'file-name-field');
|
||||
fireEvent.input(nameField, { target: { value } });
|
||||
|
||||
const createButton = screen.getByText('Create file');
|
||||
const createButton = getByText(modal, submitText, { selector: 'button' });
|
||||
createButton.click();
|
||||
};
|
||||
|
||||
|
@ -90,6 +104,10 @@ const findAndClickRootAction = async name => {
|
|||
button.click();
|
||||
};
|
||||
|
||||
export const clickPreviewMarkdown = () => {
|
||||
screen.getByText('Preview Markdown').click();
|
||||
};
|
||||
|
||||
export const openFile = async path => {
|
||||
const row = await findAndTraverseToPath(path);
|
||||
|
||||
|
@ -110,7 +128,7 @@ export const createFile = async (path, content) => {
|
|||
await findAndClickRootAction('New file');
|
||||
}
|
||||
|
||||
await findAndSetFileName(path);
|
||||
await fillFileNameModal(path);
|
||||
await findAndSetEditorValue(content);
|
||||
};
|
||||
|
||||
|
@ -123,6 +141,21 @@ export const deleteFile = async path => {
|
|||
clickFileRowAction(row, 'Delete');
|
||||
};
|
||||
|
||||
export const renameFile = async (path, newPath) => {
|
||||
const row = await findAndTraverseToPath(path);
|
||||
clickFileRowAction(row, 'Rename/Move');
|
||||
|
||||
await fillFileNameModal(newPath, 'Rename file');
|
||||
};
|
||||
|
||||
export const closeFile = async path => {
|
||||
const button = await screen.getByLabelText(`Close ${path}`, {
|
||||
selector: '.multi-file-tabs button',
|
||||
});
|
||||
|
||||
button.click();
|
||||
};
|
||||
|
||||
export const commit = async () => {
|
||||
clickOnLeftSidebarTab('Commit');
|
||||
screen.getByTestId('begin-commit-button').click();
|
||||
|
|
|
@ -68,4 +68,93 @@ describe('WebIDE', () => {
|
|||
const tabs = Array.from(document.querySelectorAll('.multi-file-tab'));
|
||||
expect(tabs.map(x => x.textContent.trim())).toEqual(['+test']);
|
||||
});
|
||||
|
||||
describe('editor info', () => {
|
||||
let statusBar;
|
||||
let editor;
|
||||
|
||||
const waitForEditor = async () => {
|
||||
editor = await ideHelper.waitForMonacoEditor();
|
||||
};
|
||||
|
||||
const changeEditorPosition = async (lineNumber, column) => {
|
||||
editor.setPosition({ lineNumber, column });
|
||||
|
||||
await vm.$nextTick();
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
vm = startWebIDE(container);
|
||||
|
||||
await ideHelper.openFile('README.md');
|
||||
editor = await ideHelper.waitForMonacoEditor();
|
||||
|
||||
statusBar = ideHelper.getStatusBar();
|
||||
});
|
||||
|
||||
it('shows line position and type', () => {
|
||||
expect(statusBar).toHaveText('1:1');
|
||||
expect(statusBar).toHaveText('markdown');
|
||||
});
|
||||
|
||||
it('persists viewer', async () => {
|
||||
const markdownPreview = 'test preview_markdown result';
|
||||
mockServer.post('/:namespace/:project/preview_markdown', () => ({
|
||||
body: markdownPreview,
|
||||
}));
|
||||
|
||||
await ideHelper.openFile('README.md');
|
||||
ideHelper.clickPreviewMarkdown();
|
||||
|
||||
const el = await waitForText(markdownPreview);
|
||||
expect(el).toHaveText(markdownPreview);
|
||||
|
||||
// Need to wait for monaco editor to load so it doesn't through errors on dispose
|
||||
await ideHelper.openFile('.gitignore');
|
||||
await ideHelper.waitForMonacoEditor();
|
||||
await ideHelper.openFile('README.md');
|
||||
await ideHelper.waitForMonacoEditor();
|
||||
|
||||
expect(el).toHaveText(markdownPreview);
|
||||
});
|
||||
|
||||
describe('when editor position changes', () => {
|
||||
beforeEach(async () => {
|
||||
await changeEditorPosition(4, 10);
|
||||
});
|
||||
|
||||
it('shows new line position', () => {
|
||||
expect(statusBar).not.toHaveText('1:1');
|
||||
expect(statusBar).toHaveText('4:10');
|
||||
});
|
||||
|
||||
it('updates after rename', async () => {
|
||||
await ideHelper.renameFile('README.md', 'READMEZ.txt');
|
||||
await waitForEditor();
|
||||
|
||||
expect(statusBar).toHaveText('1:1');
|
||||
expect(statusBar).toHaveText('plaintext');
|
||||
});
|
||||
|
||||
it('persists position after opening then rename', async () => {
|
||||
await ideHelper.openFile('files/js/application.js');
|
||||
await waitForEditor();
|
||||
await ideHelper.renameFile('README.md', 'READING_RAINBOW.md');
|
||||
await ideHelper.openFile('READING_RAINBOW.md');
|
||||
await waitForEditor();
|
||||
|
||||
expect(statusBar).toHaveText('4:10');
|
||||
expect(statusBar).toHaveText('markdown');
|
||||
});
|
||||
|
||||
it('persists position after closing', async () => {
|
||||
await ideHelper.closeFile('README.md');
|
||||
await ideHelper.openFile('README.md');
|
||||
await waitForEditor();
|
||||
|
||||
expect(statusBar).toHaveText('4:10');
|
||||
expect(statusBar).toHaveText('markdown');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -493,14 +493,18 @@ RSpec.describe ProjectsHelper do
|
|||
|
||||
subject { helper.send(:can_view_operations_tab?, user, project) }
|
||||
|
||||
[
|
||||
:metrics_dashboard,
|
||||
:read_alert_management_alert,
|
||||
:read_environment,
|
||||
:read_issue,
|
||||
:read_sentry_issue,
|
||||
:read_cluster
|
||||
].each do |ability|
|
||||
where(:ability) do
|
||||
[
|
||||
:metrics_dashboard,
|
||||
:read_alert_management_alert,
|
||||
:read_environment,
|
||||
:read_issue,
|
||||
:read_sentry_issue,
|
||||
:read_cluster
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'includes operations tab' do
|
||||
allow(helper).to receive(:can?).with(user, ability, project).and_return(true)
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ RSpec.describe GlobalPolicy do
|
|||
|
||||
let_it_be(:project_bot) { create(:user, :project_bot) }
|
||||
let_it_be(:migration_bot) { create(:user, :migration_bot) }
|
||||
let_it_be(:security_bot) { create(:user, :security_bot) }
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
@ -205,6 +207,12 @@ RSpec.describe GlobalPolicy do
|
|||
it { is_expected.not_to be_allowed(:access_api) }
|
||||
end
|
||||
|
||||
context 'security bot' do
|
||||
let(:current_user) { security_bot }
|
||||
|
||||
it { is_expected.not_to be_allowed(:access_api) }
|
||||
end
|
||||
|
||||
context 'user blocked pending approval' do
|
||||
before do
|
||||
current_user.block_pending_approval
|
||||
|
@ -335,6 +343,12 @@ RSpec.describe GlobalPolicy do
|
|||
it { is_expected.to be_allowed(:access_git) }
|
||||
end
|
||||
|
||||
context 'security bot' do
|
||||
let(:current_user) { security_bot }
|
||||
|
||||
it { is_expected.to be_allowed(:access_git) }
|
||||
end
|
||||
|
||||
describe 'deactivated user' do
|
||||
before do
|
||||
current_user.deactivate
|
||||
|
@ -495,6 +509,12 @@ RSpec.describe GlobalPolicy do
|
|||
it { is_expected.not_to be_allowed(:log_in) }
|
||||
end
|
||||
|
||||
context 'security bot' do
|
||||
let(:current_user) { security_bot }
|
||||
|
||||
it { is_expected.not_to be_allowed(:log_in) }
|
||||
end
|
||||
|
||||
context 'user blocked pending approval' do
|
||||
before do
|
||||
current_user.block_pending_approval
|
||||
|
|
|
@ -22,8 +22,6 @@ module WebIdeSpecHelpers
|
|||
click_link('Web IDE')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
save_monaco_editor_reference
|
||||
end
|
||||
|
||||
def ide_tree_body
|
||||
|
@ -65,17 +63,6 @@ module WebIdeSpecHelpers
|
|||
ide_set_editor_value(content)
|
||||
end
|
||||
|
||||
def ide_rename_file(path, new_path)
|
||||
container = ide_traverse_to_file(path)
|
||||
|
||||
click_file_action(container, 'Rename/Move')
|
||||
|
||||
within '#ide-new-entry' do
|
||||
find('input').fill_in(with: new_path)
|
||||
click_button('Rename file')
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes a file by traversing to `path`
|
||||
# then clicking the 'Delete' action.
|
||||
#
|
||||
|
@ -103,20 +90,6 @@ module WebIdeSpecHelpers
|
|||
container
|
||||
end
|
||||
|
||||
def ide_close_file(name)
|
||||
within page.find('.multi-file-tabs') do
|
||||
click_button("Close #{name}")
|
||||
end
|
||||
end
|
||||
|
||||
def ide_open_file(path)
|
||||
row = ide_traverse_to_file(path)
|
||||
|
||||
ide_open_file_row(row)
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def ide_open_file_row(row)
|
||||
return if ide_folder_row_open?(row)
|
||||
|
||||
|
@ -130,10 +103,6 @@ module WebIdeSpecHelpers
|
|||
execute_script("monaco.editor.getModel('#{uri}').setValue('#{escape_javascript(value)}')")
|
||||
end
|
||||
|
||||
def ide_set_editor_position(line, col)
|
||||
execute_script("TEST_EDITOR.setPosition(#{{ lineNumber: line, column: col }.to_json})")
|
||||
end
|
||||
|
||||
def ide_editor_value
|
||||
editor = find('.monaco-editor')
|
||||
uri = editor['data-uri']
|
||||
|
@ -180,8 +149,4 @@ module WebIdeSpecHelpers
|
|||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
||||
def save_monaco_editor_reference
|
||||
evaluate_script("monaco.editor.onDidCreateEditor(editor => { window.TEST_EDITOR = editor; })")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue