Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9a8093da81
commit
3e3f936ff7
|
|
@ -33,7 +33,7 @@ export default {
|
|||
emptyField: __('Never'),
|
||||
expired: __('Expired'),
|
||||
modalMessage: __(
|
||||
'Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone.',
|
||||
'Are you sure you want to revoke the %{accessTokenType} "%{tokenName}"? This action cannot be undone.',
|
||||
),
|
||||
revokeButton: __('Revoke'),
|
||||
tokenValidity: __('Token valid until revoked'),
|
||||
|
|
@ -72,11 +72,6 @@ export default {
|
|||
|
||||
return FIELDS.filter(({ key }) => !ignoredFields.includes(key));
|
||||
},
|
||||
modalMessage() {
|
||||
return sprintf(this.$options.i18n.modalMessage, {
|
||||
accessTokenType: this.accessTokenType,
|
||||
});
|
||||
},
|
||||
showPagination() {
|
||||
return this.activeAccessTokens.length > PAGE_SIZE;
|
||||
},
|
||||
|
|
@ -87,6 +82,12 @@ export default {
|
|||
this.activeAccessTokens = convertObjectPropsToCamelCase(activeAccessTokens, { deep: true });
|
||||
this.currentPage = INITIAL_PAGE;
|
||||
},
|
||||
modalMessage(tokenName) {
|
||||
return sprintf(this.$options.i18n.modalMessage, {
|
||||
accessTokenType: this.accessTokenType,
|
||||
tokenName,
|
||||
});
|
||||
},
|
||||
sortingChanged(aRow, bRow, key) {
|
||||
if (['createdAt', 'lastUsedAt', 'expiresAt'].includes(key)) {
|
||||
// Transform `null` value to the latest possible date
|
||||
|
|
@ -149,13 +150,13 @@ export default {
|
|||
}}</span>
|
||||
</template>
|
||||
|
||||
<template #cell(action)="{ item: { revokePath } }">
|
||||
<template #cell(action)="{ item: { name, revokePath } }">
|
||||
<gl-button
|
||||
v-if="revokePath"
|
||||
category="tertiary"
|
||||
:title="$options.i18n.revokeButton"
|
||||
:aria-label="$options.i18n.revokeButton"
|
||||
:data-confirm="modalMessage"
|
||||
:data-confirm="modalMessage(name)"
|
||||
data-confirm-btn-variant="danger"
|
||||
data-qa-selector="revoke_button"
|
||||
data-method="put"
|
||||
|
|
|
|||
|
|
@ -845,6 +845,9 @@
|
|||
"if": {
|
||||
"$ref": "#/definitions/if"
|
||||
},
|
||||
"changes": {
|
||||
"$ref": "#/definitions/changes"
|
||||
},
|
||||
"exists": {
|
||||
"$ref": "#/definitions/exists"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Organizations
|
||||
class ApplicationController < ::ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
before_action :check_feature_flag!
|
||||
before_action :organization
|
||||
|
||||
layout 'organization'
|
||||
|
|
@ -16,11 +16,16 @@ module Organizations
|
|||
end
|
||||
strong_memoize_attr :organization
|
||||
|
||||
def authorize_action!(action)
|
||||
return if Feature.enabled?(:ui_for_organizations, current_user) &&
|
||||
can?(current_user, action, organization)
|
||||
def check_feature_flag!
|
||||
access_denied! unless Feature.enabled?(:ui_for_organizations, current_user)
|
||||
end
|
||||
|
||||
access_denied!
|
||||
def authorize_create_organization!
|
||||
access_denied! unless can?(current_user, :create_organization)
|
||||
end
|
||||
|
||||
def authorize_read_organization!
|
||||
access_denied! unless can?(current_user, :read_organization, organization)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,10 +4,20 @@ module Organizations
|
|||
class OrganizationsController < ApplicationController
|
||||
feature_category :cell
|
||||
|
||||
before_action { authorize_action!(:read_organization) }
|
||||
skip_before_action :authenticate_user!, except: [:index, :new]
|
||||
|
||||
def show; end
|
||||
def index; end
|
||||
|
||||
def groups_and_projects; end
|
||||
def new
|
||||
authorize_create_organization!
|
||||
end
|
||||
|
||||
def show
|
||||
authorize_read_organization!
|
||||
end
|
||||
|
||||
def groups_and_projects
|
||||
authorize_read_organization!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
|
|||
|
||||
flash[:notice] = _('All merge conflicts were resolved. The merge request can now be merged.')
|
||||
|
||||
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
|
||||
render json: { redirect_to: project_merge_request_path(@project, @merge_request, resolved_conflicts: true) }
|
||||
rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e
|
||||
render status: :bad_request, json: { message: e.message }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2048,7 +2048,7 @@ class User < MainClusterwide::ApplicationRecord
|
|||
end
|
||||
|
||||
def user_detail
|
||||
super.presence || (persisted? ? create_user_detail! : build_user_detail)
|
||||
super.presence || build_user_detail
|
||||
end
|
||||
|
||||
def pending_todo_for(target)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,12 @@ class BasePolicy < DeclarativePolicy::Base
|
|||
with_options scope: :user, score: 0
|
||||
condition(:can_create_group) { @user&.can_create_group }
|
||||
|
||||
# TODO: update to check application setting
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/423302
|
||||
desc 'User can create an organization'
|
||||
with_options scope: :user, score: 0
|
||||
condition(:can_create_organization) { true }
|
||||
|
||||
desc "The application is restricted from public visibility"
|
||||
condition(:restricted_public_level, scope: :global) do
|
||||
Gitlab::CurrentSettings.current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class GlobalPolicy < BasePolicy
|
|||
prevent :receive_notifications
|
||||
prevent :use_quick_actions
|
||||
prevent :create_group
|
||||
prevent :create_organization
|
||||
prevent :execute_graphql_mutation
|
||||
end
|
||||
|
||||
|
|
@ -93,6 +94,10 @@ class GlobalPolicy < BasePolicy
|
|||
enable :create_group
|
||||
end
|
||||
|
||||
rule { can_create_organization }.policy do
|
||||
enable :create_organization
|
||||
end
|
||||
|
||||
rule { can?(:create_group) }.policy do
|
||||
enable :create_group_with_default_branch_protection
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ module Users
|
|||
yield(@user) if block
|
||||
|
||||
user_exists = @user.persisted?
|
||||
@user.user_detail # prevent assignment
|
||||
|
||||
discard_read_only_attributes
|
||||
assign_attributes
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
= f.label :bulk_import_max_download_file_size, s_('BulkImport|Direct transfer maximum download file size (MB)'), class: 'label-light'
|
||||
= f.number_field :bulk_import_max_download_file_size, class: 'form-control gl-form-input', title: s_('BulkImport|Maximum download file size when importing from source GitLab instances by direct transfer.'), data: { toggle: 'tooltip', container: 'body' }
|
||||
.form-group
|
||||
= f.label :max_decompressed_archive_size, s_('Import|Maximum decompressed size (MiB)'), class: 'label-light'
|
||||
= f.label :max_decompressed_archive_size, s_('Import|Maximum decompressed file size for archives from imports (MiB)'), class: 'label-light'
|
||||
= f.number_field :max_decompressed_archive_size, class: 'form-control gl-form-input', title: s_('Import|Maximum size of decompressed archive.'), data: { toggle: 'tooltip', container: 'body' }
|
||||
%span.form-text.text-muted= _('Set to 0 for no size limit.')
|
||||
.form-group
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
= link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url, data: { track_action: "click_link", track_label: "switch_to_canary", track_property: "navigation_top" }
|
||||
|
||||
%li.divider
|
||||
.js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_url} }
|
||||
.js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_path} }
|
||||
|
||||
- if current_user_menu?(:sign_out)
|
||||
%li.divider
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
- page_title @organization.name
|
||||
- header_title @organization.name, organization_path(@organization)
|
||||
- nav "organization"
|
||||
- page_title @organization.name if @organization
|
||||
- header_title @organization.name, organization_path(@organization) if @organization
|
||||
- nav(%w[index new].include?(params[:action]) ? "your_work" : "organization")
|
||||
- @left_sidebar = true
|
||||
|
||||
= render template: "layouts/application"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
- page_title s_('Organization|Organizations')
|
||||
- header_title _("Your work"), root_path
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
- page_title s_('Organization|New organization')
|
||||
- header_title _("Your work"), root_path
|
||||
- add_to_breadcrumbs s_('Organization|Organizations'), organizations_path
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ci_support_include_rules_changes
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129866
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421608
|
||||
milestone: '16.4'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
resources :organizations, only: [:show], param: :organization_path, controller: 'organizations/organizations' do
|
||||
resources(
|
||||
:organizations,
|
||||
only: [:show, :index, :new],
|
||||
param: :organization_path,
|
||||
controller: 'organizations/organizations'
|
||||
) do
|
||||
member do
|
||||
get :groups_and_projects
|
||||
end
|
||||
|
|
|
|||
|
|
@ -152,6 +152,9 @@ Every audit event is associated with an event type. The association with the eve
|
|||
| [`issue_closed_by_project_bot`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121485) | Triggered when an issue is closed using a project access token | **{check-circle}** Yes | **{check-circle}** Yes | `team_planning` | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/323299) |
|
||||
| [`issue_created_by_project_bot`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121485) | Triggered when an issue is created using a project access token | **{check-circle}** Yes | **{check-circle}** Yes | `team_planning` | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/323299) |
|
||||
| [`issue_reopened_by_project_bot`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121485) | Triggered when an issue is reopened using a project access token | **{check-circle}** Yes | **{check-circle}** Yes | `team_planning` | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/323299) |
|
||||
| [`login_failed_with_otp_authentication`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129595) | Triggered when the login fails due to an incorrect OTP | **{check-circle}** Yes | **{check-circle}** Yes | `system_access` | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/377758) |
|
||||
| [`login_failed_with_standard_authentication`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129595) | Triggered when login to GitLab fails with standard authentication like password. | **{check-circle}** Yes | **{check-circle}** Yes | `system_access` | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/377758) |
|
||||
| [`login_failed_with_webauthn_authentication`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129595) | Triggered when login fails via WebAuthn device | **{check-circle}** Yes | **{check-circle}** Yes | `system_access` | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/377758) |
|
||||
| [`member_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109711) | Event triggered when a membership is created | **{check-circle}** Yes | **{check-circle}** Yes | `compliance_management` | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374112) |
|
||||
| [`member_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109711) | Event triggered when a membership is destroyed | **{check-circle}** Yes | **{check-circle}** Yes | `compliance_management` | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374112) |
|
||||
| [`member_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109711) | Event triggered when a membership is updated | **{check-circle}** Yes | **{check-circle}** Yes | `compliance_management` | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/374112) |
|
||||
|
|
|
|||
|
|
@ -10,43 +10,45 @@ type: howto
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20912) in GitLab 12.6.
|
||||
> - [Bot-created access tokens not displayed in personal access token list](https://gitlab.com/gitlab-org/gitlab/-/issues/351759) in GitLab 14.9.
|
||||
|
||||
GitLab administrators are responsible for the overall security of their instance. To assist, GitLab
|
||||
provides a Credentials inventory to keep track of all the credentials that can be used to access
|
||||
their self-managed instance.
|
||||
As a GitLab administrator, you are responsible for the overall security of your instance.
|
||||
To assist, GitLab provides an inventory of all the credentials that can be used to access
|
||||
your self-managed instance.
|
||||
|
||||
Use Credentials inventory to see for your GitLab instance all:
|
||||
In the credentials inventory, you can view all:
|
||||
|
||||
- Personal access tokens (PAT).
|
||||
- Personal access tokens (PATs).
|
||||
- Project access tokens (introduced in GitLab 14.8).
|
||||
- Group access tokens ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102959) in GitLab 15.6).
|
||||
- SSH keys.
|
||||
- GPG keys.
|
||||
|
||||
You can also [revoke](#revoke-a-users-personal-access-token) and [delete](#delete-a-users-ssh-key) and see:
|
||||
You can also [revoke](#revoke-a-users-personal-access-token), [delete](#delete-a-users-ssh-key), and view:
|
||||
|
||||
- Who they belong to.
|
||||
- Their access scope.
|
||||
- Their usage pattern.
|
||||
- When they expire. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214809) in GitLab 13.2.
|
||||
- When they were revoked. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214809) in GitLab 13.2.
|
||||
|
||||
To access the Credentials inventory:
|
||||
|
||||
1. On the left sidebar, select **Search or go to**.
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Credentials**.
|
||||
- [In GitLab 13.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/214809), when they:
|
||||
- Expire.
|
||||
- Were revoked.
|
||||
|
||||
## Revoke a user's personal access token
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214811) in GitLab 13.4.
|
||||
|
||||
If you see a **Revoke** button, you can revoke that user's PAT. Whether you see a **Revoke** button depends on the token state, and if an expiration date has been set. For more information, see the following table:
|
||||
You can revoke a user's personal access token.
|
||||
|
||||
| Token state | Show Revoke button? | Comments |
|
||||
|-------------|---------------------|----------------------------------------------------------------------------|
|
||||
| Active | Yes | Allows administrators to revoke the PAT, such as for a compromised account |
|
||||
| Expired | No | Not applicable; token is already expired |
|
||||
| Revoked | No | Not applicable; token is already revoked |
|
||||
1. On the left sidebar, select **Search or go to**.
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Credentials**.
|
||||
1. By the personal access token, select **Revoke**.
|
||||
|
||||
If a **Revoke** button is not available, the token may be expired or revoked, or an expiration date set.
|
||||
|
||||
| Token state | Revoke button displayed? | Comments |
|
||||
|-------------|--------------------------|----------------------------------------------------------------------------|
|
||||
| Active | Yes | Allows administrators to revoke the PAT, such as for a compromised account |
|
||||
| Expired | No | Not applicable; token is already expired |
|
||||
| Revoked | No | Not applicable; token is already revoked |
|
||||
|
||||
When a PAT is revoked from the credentials inventory, the instance notifies the user by email.
|
||||
|
||||
|
|
@ -56,10 +58,13 @@ When a PAT is revoked from the credentials inventory, the instance notifies the
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/243833) in GitLab 14.8.
|
||||
|
||||
The **Revoke** button next to a project access token can be selected to revoke that particular project access token. This both:
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Credentials**.
|
||||
1. Select the **Project Access Tokens** tab.
|
||||
1. By the project access token, select **Revoke**.
|
||||
|
||||
- Revokes the token project access token.
|
||||
- Enqueues a background worker to delete the project bot user.
|
||||
The project access token is revoked and a background worker is queued to delete the project bot user.
|
||||
|
||||

|
||||
|
||||
|
|
@ -67,8 +72,13 @@ The **Revoke** button next to a project access token can be selected to revoke t
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225248) in GitLab 13.5.
|
||||
|
||||
You can **Delete** a user's SSH key by navigating to the credentials inventory's SSH Keys tab.
|
||||
The instance then notifies the user.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Credentials**.
|
||||
1. Select the **SSH Keys** tab.
|
||||
1. By the SSH key, select **Delete**.
|
||||
|
||||
The instance notifies the user.
|
||||
|
||||

|
||||
|
||||
|
|
@ -77,11 +87,11 @@ The instance then notifies the user.
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/282429) in GitLab 13.10.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292961) in GitLab 13.12.
|
||||
|
||||
You can view all existing GPG in your GitLab instance by navigating to the
|
||||
You can view all existing GPG in your GitLab instance by going to the
|
||||
credentials inventory GPG Keys tab, as well as the following properties:
|
||||
|
||||
- Who the GPG key belongs to.
|
||||
- The ID of the GPG key.
|
||||
- Whether the GPG key is [verified or unverified](../user/project/repository/gpg_signed_commits/index.md)
|
||||
- Whether the GPG key is [verified or unverified](../user/project/repository/gpg_signed_commits/index.md).
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -1086,7 +1086,7 @@ Reverting the PostgreSQL upgrade with `gitlab-ctl revert-pg-upgrade` has the sam
|
|||
`gitlab-ctl pg-upgrade`. You should follow the same procedure by first stopping the replicas,
|
||||
then reverting the leader, and finally reverting the replicas.
|
||||
|
||||
### Near zero downtime upgrade of PostgreSQL in a Patroni cluster (Experimental)
|
||||
### Near zero downtime upgrade of PostgreSQL in a Patroni cluster **(EXPERIMENT)**
|
||||
|
||||
Patroni enables you to run a major PostgreSQL upgrade without shutting down the cluster. However, this
|
||||
requires additional resources to host the new Patroni nodes with the upgraded PostgreSQL. In practice, with this
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ status in the output of the `sudo gitlab-rake db:migrate:status` command.
|
|||
sudo gitlab-ctl restart sidekiq
|
||||
```
|
||||
|
||||
## Rebuild database indexes (Experiment)
|
||||
## Rebuild database indexes **(EXPERIMENT)**
|
||||
|
||||
WARNING:
|
||||
This feature is experimental, and isn't enabled by default. Use caution when
|
||||
|
|
|
|||
|
|
@ -137,7 +137,8 @@ To modify the maximum download file size for imports by direct transfer:
|
|||
|
||||
## Maximum decompressed file size for imported archives
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128218) in GitLab 16.3.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128218) in GitLab 16.3.
|
||||
> - **Maximum decompressed file size for archives from imports** field [renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130081) from **Maximum decompressed size** in GitLab 16.4.
|
||||
|
||||
When you import a project using [file exports](../../user/project/settings/import_export.md) or [direct transfer](../../user/group/import/index.md#migrate-groups-by-direct-transfer-recommended), you can specify the maximum decompressed file size for imported archives. The default value is 25 GB.
|
||||
|
||||
|
|
@ -153,7 +154,7 @@ To modify the maximum decompressed file size for imports in GitLab:
|
|||
1. Select **Admin Area**.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Account and limit**.
|
||||
1. Set another value for **Maximum decompressed size (MiB)**.
|
||||
1. Set another value for **Maximum decompressed file size for archives from imports (MiB)**.
|
||||
|
||||
## Timeout for decompressing archived files
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Geo
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# GitLab Silent Mode (Experiment) **(FREE SELF)**
|
||||
# GitLab Silent Mode **(FREE SELF EXPERIMENT)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/9826) in GitLab 15.11. This feature is an [Experiment](../../policy/experiment-beta-support.md#experiment).
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Generate code completions (Experiment)
|
||||
## Generate code completions **(EXPERIMENT)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/415581) in GitLab 16.2 [with a flag](../administration/feature_flags.md) named `code_suggestions_completion_api`. Disabled by default. This feature is an Experiment.
|
||||
> - Requirement to generate a JWT before calling this endpoint was [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127863) in GitLab 16.3.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: reference
|
||||
---
|
||||
|
||||
# CI/CD Components (Experimental)
|
||||
# CI/CD Components **(EXPERIMENT)**
|
||||
|
||||
> - Introduced as an [experimental feature](../../policy/experiment-beta-support.md) in GitLab 16.0, [with a flag](../../administration/feature_flags.md) named `ci_namespace_catalog_experimental`. Disabled by default.
|
||||
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/groups/gitlab-org/-/epics/9897) in GitLab 16.2.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: reference
|
||||
---
|
||||
|
||||
# Mobile DevOps (Experiment)
|
||||
# Mobile DevOps **(EXPERIMENT)**
|
||||
|
||||
Use GitLab Mobile DevOps to quickly build, sign, and release native and cross-platform mobile apps
|
||||
for Android and iOS using GitLab CI/CD. Mobile DevOps is an experimental feature developed by
|
||||
|
|
|
|||
|
|
@ -501,6 +501,44 @@ In this example, GitLab checks for the existence of `test-file.yml` in `my-group
|
|||
not the current project. Follow [issue 386040](https://gitlab.com/gitlab-org/gitlab/-/issues/386040)
|
||||
for information about work to improve this behavior.
|
||||
|
||||
### `include` with `rules:changes`
|
||||
|
||||
> Support for `rules:changes` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/342209) in GitLab 16.4 [with a flag](../../administration/feature_flags.md) named `ci_support_include_rules_changes`. Disabled by default.
|
||||
|
||||
Use [`rules:changes`](index.md#ruleschanges) to conditionally include other configuration files
|
||||
based on changed files. For example:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- local: builds1.yml
|
||||
rules:
|
||||
- changes:
|
||||
- Dockerfile
|
||||
- local: builds2.yml
|
||||
rules:
|
||||
- changes:
|
||||
paths:
|
||||
- Dockerfile
|
||||
compare_to: 'refs/heads/branch1'
|
||||
when: always
|
||||
- local: builds3.yml
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
changes:
|
||||
paths:
|
||||
- Dockerfile
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script: exit 0
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- `builds1.yml` is included when `Dockerfile` has changed.
|
||||
- `builds2.yml` is included when `Dockerfile` has changed relative to `refs/heads/branch1`.
|
||||
- `builds3.yml` is included when `Dockerfile` has changed and the pipeline source is a merge request event.
|
||||
|
||||
## Use `include:local` with wildcard file paths
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25921) in GitLab 13.11.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Sentry
|
||||
|
||||
As part of the [Frontend Observability Working Group](https://google.com) we're looking to provide documentation on how to use Sentry effectively.
|
||||
If left unchecked, Sentry can get noisy and become unreliable.
|
||||
This page aims to help guide us toward more sensible Sentry usage.
|
||||
|
||||
## Which errors we should report to Sentry explicitly and which should be only shown to users (e.g. as alerts)
|
||||
|
||||
If we send all errors to Sentry, it gets very noisy, very quickly.
|
||||
We want to filter out the errors that we either don't care about, or have no control over.
|
||||
For example, if a user fills out a form incorrectly, this is not something we want to send to Sentry.
|
||||
If that form fails because it's hitting a dead endpoint, this is an error we want Sentry to know about.
|
||||
|
||||
## How to catch errors correctly so Sentry can display them reliably
|
||||
|
||||
TBD
|
||||
|
||||
## How to catch special cases you want to track (like we did with the pipeline graph)
|
||||
|
||||
TBD
|
||||
|
||||
## How to navigate Sentry and find errors
|
||||
|
||||
TBD
|
||||
|
||||
## How to debug Sentry errors effectively
|
||||
|
||||
TBD
|
||||
|
|
@ -83,7 +83,7 @@ of an incident.
|
|||
|
||||

|
||||
|
||||
### When labels change (Experiment)
|
||||
### When labels change **(EXPERIMENT)**
|
||||
|
||||
> [Introduced]([issue-link](https://gitlab.com/gitlab-org/gitlab/-/issues/365489)) in GitLab 15.3 [with a flag](../../administration/feature_flags.md) named `incident_timeline_events_from_labels`. Disabled by default.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ The following features are in Experiment:
|
|||
|
||||
The rest of the features described on this page are also in the Experiment phase.
|
||||
|
||||
### Explain Selected Code in the Web UI **(ULTIMATE SAAS)**
|
||||
### Explain Selected Code in the Web UI **(ULTIMATE SAAS EXPERIMENT)**
|
||||
|
||||
> Introduced in GitLab 15.11 as an [Experiment](../policy/experiment-beta-support.md#experiment) on GitLab.com.
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ code in a merge request:
|
|||
|
||||
We cannot guarantee that the large language model produces results that are correct. Use the explanation with caution.
|
||||
|
||||
### GitLab Duo Chat **(ULTIMATE SAAS)**
|
||||
### GitLab Duo Chat **(ULTIMATE SAAS EXPERIMENT)**
|
||||
|
||||
> Introduced in GitLab 16.0 as an [Experiment](../policy/experiment-beta-support.md#experiment).
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ Or, you can add a comment in the [feedback issue](https://gitlab.com/gitlab-org/
|
|||
NOTE:
|
||||
Only the last 50 messages are retained in the chat history. The chat history expires 3 days after last use.
|
||||
|
||||
### Summarize issue discussions **(ULTIMATE SAAS)**
|
||||
### Summarize issue discussions **(ULTIMATE SAAS EXPERIMENT)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10344) in GitLab 16.0 as an [Experiment](../policy/experiment-beta-support.md#experiment).
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ Provide feedback on this experimental feature in [issue 407779](https://gitlab.c
|
|||
**Data usage**: When you use this feature, the text of public comments on the issue are sent to the large
|
||||
language model referenced above.
|
||||
|
||||
### Show deployment frequency forecast **(ULTIMATE SAAS)**
|
||||
### Show deployment frequency forecast **(ULTIMATE SAAS EXPERIMENT)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10228) in GitLab 16.2 as an [Experiment](../policy/experiment-beta-support.md#experiment).
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ For example, if you select a 30-day range, a forecast for the following 15 days
|
|||
|
||||
Provide feedback on this experimental feature in [issue 416833](https://gitlab.com/gitlab-org/gitlab/-/issues/416833).
|
||||
|
||||
### Generate issue descriptions **(ULTIMATE SAAS)**
|
||||
### Generate issue descriptions **(ULTIMATE SAAS EXPERIMENT)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10762) in GitLab 16.3 as an [Experiment](../policy/experiment-beta-support.md#experiment).
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Product Analytics
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Analytics dashboards (Experiment) **(ULTIMATE ALL)**
|
||||
# Analytics dashboards **(ULTIMATE ALL EXPERIMENT)**
|
||||
|
||||
> Introduced in GitLab 15.9 as an [Experiment](../../policy/experiment-beta-support.md#experiment) feature [with a flag](../../administration/feature_flags.md) named `combined_analytics_dashboards`. Disabled by default.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - Released in GitLab 15.11 as an Open [Beta](../../policy/experiment-beta-support.md#beta) feature [with a flag](../../administration/feature_flags.md) named `group_analytics_dashboards_page`. Enabled by default.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/392734) in GitLab 16.0. Feature flag `group_analytics_dashboards_page` removed.
|
||||
|
||||
You can leave feedback on dashboard bugs or functionality in [issue 419488](https://gitlab.com/gitlab-org/gitlab/-/issues/419488).
|
||||
To help us improve the Value Streams Dashboard, please share feedback about your experience in this [survey](https://gitlab.fra1.qualtrics.com/jfe/form/SV_50guMGNU2HhLeT4).
|
||||
For more information, see also the [Value Stream Management category direction page](https://about.gitlab.com/direction/plan/value_stream_management/).
|
||||
|
||||
The Value Streams Dashboard is a customizable dashboard you can use to identify trends, patterns, and opportunities for digital transformation improvements.
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6346) in GitLab 14.8.
|
||||
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/368828) the starboard directive in GitLab 15.4. The starboard directive is scheduled for removal in GitLab 16.0.
|
||||
|
||||
To view cluster vulnerabilities, you can view the [vulnerability report](../../application_security/vulnerabilities/index.md).
|
||||
You can also configure your agent so the vulnerabilities are displayed with other agent information in GitLab.
|
||||
|
||||
## Enable operational container scanning
|
||||
|
||||
You can use operational container scanning to scan container images in your cluster for security vulnerabilities. You
|
||||
|
|
@ -140,3 +137,7 @@ This information can also be found under [operational vulnerabilities](../../../
|
|||
|
||||
NOTE:
|
||||
You must have at least the Developer role.
|
||||
|
||||
## Scanning private images
|
||||
|
||||
To scan private images, the scanner relies on the image pull secrets (direct references and from the service account) to pull the image.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Product Analytics
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Product analytics (Experiment) **(ULTIMATE ALL)**
|
||||
# Product analytics **(ULTIMATE ALL EXPERIMENT)**
|
||||
|
||||
> - Introduced in GitLab 15.4 as an [Experiment](../../policy/experiment-beta-support.md#experiment) feature [with a flag](../../administration/feature_flags.md) named `cube_api_proxy`. Disabled by default.
|
||||
> - `cube_api_proxy` revised to only reference the [Product Analytics API](../../api/product_analytics.md) in GitLab 15.6.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Tenant Scale
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Achievements (Experiment) **(FREE ALL)**
|
||||
# Achievements **(FREE ALL EXPERIMENT)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113156) in GitLab 15.10 [with a flag](../../administration/feature_flags.md) named `achievements`. Disabled by default.
|
||||
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ module Gitlab
|
|||
def build_context(project:, pipeline:, sha:, user:, parent_pipeline:, pipeline_config:)
|
||||
Config::External::Context.new(
|
||||
project: project,
|
||||
pipeline: pipeline,
|
||||
sha: sha || find_sha(project),
|
||||
user: user,
|
||||
parent_pipeline: parent_pipeline,
|
||||
|
|
|
|||
|
|
@ -7,13 +7,17 @@ module Gitlab
|
|||
class Include
|
||||
class Rules::Rule < ::Gitlab::Config::Entry::Node
|
||||
include ::Gitlab::Config::Entry::Validatable
|
||||
include ::Gitlab::Config::Entry::Configurable
|
||||
include ::Gitlab::Config::Entry::Attributable
|
||||
|
||||
ALLOWED_KEYS = %i[if exists when].freeze
|
||||
ALLOWED_KEYS = %i[if exists when changes].freeze
|
||||
ALLOWED_WHEN = %w[never always].freeze
|
||||
|
||||
attributes :if, :exists, :when
|
||||
|
||||
entry :changes, Entry::Rules::Rule::Changes,
|
||||
description: 'File change condition rule.'
|
||||
|
||||
validations do
|
||||
validates :config, presence: true
|
||||
validates :config, type: { with: Hash }
|
||||
|
|
@ -27,7 +31,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def value
|
||||
config.compact
|
||||
config.merge(
|
||||
changes: (changes_value if changes_defined?)
|
||||
).compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,17 +14,18 @@ module Gitlab
|
|||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_reader :project, :sha, :user, :parent_pipeline, :variables, :pipeline_config
|
||||
attr_reader :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes
|
||||
attr_reader :pipeline, :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes
|
||||
|
||||
attr_accessor :total_file_size_in_bytes
|
||||
|
||||
delegate :instrument, to: :logger
|
||||
|
||||
def initialize(
|
||||
project: nil, sha: nil, user: nil, parent_pipeline: nil, variables: nil,
|
||||
project: nil, pipeline: nil, sha: nil, user: nil, parent_pipeline: nil, variables: nil,
|
||||
pipeline_config: nil, logger: nil
|
||||
)
|
||||
@project = project
|
||||
@pipeline = pipeline
|
||||
@sha = sha
|
||||
@user = user
|
||||
@parent_pipeline = parent_pipeline
|
||||
|
|
@ -60,6 +61,7 @@ module Gitlab
|
|||
|
||||
def mutate(attrs = {})
|
||||
self.class.new(**attrs) do |ctx|
|
||||
ctx.pipeline = pipeline
|
||||
ctx.expandset = expandset
|
||||
ctx.execution_deadline = execution_deadline
|
||||
ctx.logger = logger
|
||||
|
|
@ -106,7 +108,7 @@ module Gitlab
|
|||
|
||||
protected
|
||||
|
||||
attr_writer :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes
|
||||
attr_writer :pipeline, :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes
|
||||
|
||||
private
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,18 @@ module Gitlab
|
|||
else
|
||||
Result.new('never')
|
||||
end
|
||||
rescue Build::Rules::Rule::Clause::ParseError => e
|
||||
raise InvalidIncludeRulesError, "include:#{e.message}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def match_rule(context)
|
||||
@rule_list.find { |rule| rule.matches?(nil, context) }
|
||||
if Feature.enabled?(:ci_support_include_rules_changes, context.project)
|
||||
@rule_list.find { |rule| rule.matches?(context.pipeline, context) }
|
||||
else
|
||||
@rule_list.find { |rule| rule.matches?(nil, context) }
|
||||
end
|
||||
end
|
||||
|
||||
Result = Struct.new(:when) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module YourWork
|
||||
module Menus
|
||||
class OrganizationsMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
organizations_path
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Organizations')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'organization'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
Feature.enabled?(:ui_for_organizations) && !!context.current_user
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: 'organizations/organizations', actions: %w[index new] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -31,6 +31,7 @@ module Sidebars
|
|||
def add_menus
|
||||
add_menu(Sidebars::YourWork::Menus::ProjectsMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::GroupsMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::OrganizationsMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::IssuesMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::MergeRequestsMenu.new(context))
|
||||
add_menu(Sidebars::YourWork::Menus::TodosMenu.new(context))
|
||||
|
|
|
|||
|
|
@ -6327,7 +6327,7 @@ msgstr ""
|
|||
msgid "Are you sure you want to retry this migration?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone."
|
||||
msgid "Are you sure you want to revoke the %{accessTokenType} \"%{tokenName}\"? This action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to revoke this SSH key?"
|
||||
|
|
@ -24235,7 +24235,7 @@ msgstr ""
|
|||
msgid "Import|GitHub import details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import|Maximum decompressed size (MiB)"
|
||||
msgid "Import|Maximum decompressed file size for archives from imports (MiB)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import|Maximum import remote file size (MB)"
|
||||
|
|
@ -32987,6 +32987,9 @@ msgstr ""
|
|||
msgid "Organization|Frequently visited projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|New organization"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Org ID"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32996,6 +32999,9 @@ msgstr ""
|
|||
msgid "Organization|Organization overview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Organizations"
|
||||
msgstr ""
|
||||
|
||||
msgid "Organization|Public - The organization can be accessed without any authentication."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ async function createJsConfig() {
|
|||
paths[alias] = target;
|
||||
});
|
||||
|
||||
// JS/TS config. See more: https://www.typescriptlang.org/tsconfig
|
||||
const jsConfig = {
|
||||
// As we're introducing jsconfig to the project, as a precaution we add both:
|
||||
// 'include' and 'exclude' options. This might be simplified in the future.
|
||||
|
|
@ -52,13 +53,22 @@ async function createJsConfig() {
|
|||
'ee/app/assets/javascripts',
|
||||
'spec/frontend',
|
||||
'ee/spec/frontend',
|
||||
'tmp/tests/frontend/fixtures/',
|
||||
'tmp/tests/frontend/fixtures-ee/',
|
||||
'tmp/tests/frontend/fixtures',
|
||||
'tmp/tests/frontend/fixtures-ee',
|
||||
],
|
||||
|
||||
// Explicitly enable automatic type acquisition
|
||||
// See more: https://www.typescriptlang.org/tsconfig#type-acquisition
|
||||
typeAcquisition: {
|
||||
enable: true,
|
||||
},
|
||||
|
||||
compilerOptions: {
|
||||
baseUrl: '.', // Define the project root
|
||||
checkJs: false, // Disable type checking on JavaScript files
|
||||
disableSizeLimit: true, // Disable memory size limit for the language server
|
||||
skipLibCheck: true, // Skip type checking all .d.ts files
|
||||
resolveJsonModule: true, // Enable importing .json files
|
||||
paths, // Aliases
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -111,10 +111,6 @@ FactoryBot.define do
|
|||
last_sign_in_ip { '127.0.0.1' }
|
||||
end
|
||||
|
||||
trait :with_user_detail do
|
||||
after :build, &:user_detail
|
||||
end
|
||||
|
||||
trait :with_credit_card_validation do
|
||||
after :create do |user|
|
||||
create :credit_card_validation, user: user
|
||||
|
|
|
|||
|
|
@ -157,9 +157,9 @@ describe('~/access_tokens/components/access_token_table_app', () => {
|
|||
href: '/-/profile/personal_access_tokens/1/revoke',
|
||||
'data-confirm': sprintf(
|
||||
__(
|
||||
'Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone.',
|
||||
'Are you sure you want to revoke the %{accessTokenType} "%{tokenName}"? This action cannot be undone.',
|
||||
),
|
||||
{ accessTokenType },
|
||||
{ accessTokenType, tokenName: 'a' },
|
||||
),
|
||||
});
|
||||
expect(button.props('category')).toBe('tertiary');
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ include:
|
|||
rules:
|
||||
- exists:
|
||||
- file.md
|
||||
- local: builds.yml
|
||||
rules:
|
||||
- if: $INCLUDE_BUILDS == "true"
|
||||
changes:
|
||||
- 'test.yml'
|
||||
- local: builds.yml
|
||||
rules:
|
||||
- changes:
|
||||
paths:
|
||||
- 'test.yml'
|
||||
compare_to: 'master'
|
||||
|
||||
# valid trigger:include
|
||||
trigger:include accepts project and file properties:
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ RSpec.describe ApplicationHelper do
|
|||
describe '#linkedin_url?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:user) { build_stubbed(:user, user_detail: build_stubbed(:user_detail)) }
|
||||
let(:user) { build_stubbed(:user) }
|
||||
|
||||
subject { helper.linkedin_url(user) }
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ RSpec.describe ApplicationHelper do
|
|||
describe '#twitter_url?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:user) { build_stubbed(:user, user_detail: build_stubbed(:user_detail)) }
|
||||
let(:user) { build_stubbed(:user) }
|
||||
|
||||
subject { helper.twitter_url(user) }
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ RSpec.describe SessionsHelper, feature_category: :system_access do
|
|||
describe '#unconfirmed_verification_email?', :freeze_time do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:user) { build_stubbed(:user, user_detail: build_stubbed(:user_detail)) }
|
||||
let(:user) { build_stubbed(:user) }
|
||||
let(:token_valid_for) { ::Users::EmailVerification::ValidateTokenService::TOKEN_VALID_FOR_MINUTES }
|
||||
|
||||
subject { helper.unconfirmed_verification_email?(user) }
|
||||
|
|
@ -101,7 +101,7 @@ RSpec.describe SessionsHelper, feature_category: :system_access do
|
|||
end
|
||||
|
||||
describe '#verification_data' do
|
||||
let(:user) { build_stubbed(:user, user_detail: build_stubbed(:user_detail)) }
|
||||
let(:user) { build_stubbed(:user) }
|
||||
|
||||
it 'returns the expected data' do
|
||||
expect(helper.verification_data(user)).to eq({
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule, feature_category
|
|||
entry.compose!
|
||||
end
|
||||
|
||||
shared_examples 'a valid config' do
|
||||
shared_examples 'a valid config' do |expected_value = nil|
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
it 'returns the expected value' do
|
||||
expect(entry.value).to eq(config.compact)
|
||||
expect(entry.value).to eq(expected_value || config.compact)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -89,19 +89,37 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule, feature_category
|
|||
|
||||
it_behaves_like 'a valid config'
|
||||
|
||||
context 'when array' do
|
||||
context 'when exists: clause is an array' do
|
||||
let(:config) { { exists: ['./this.md', './that.md'] } }
|
||||
|
||||
it_behaves_like 'a valid config'
|
||||
end
|
||||
|
||||
context 'when null' do
|
||||
context 'when exists: clause is null' do
|
||||
let(:config) { { exists: nil } }
|
||||
|
||||
it_behaves_like 'a valid config'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when specifying a changes: clause' do
|
||||
let(:config) { { changes: %w[Dockerfile lib/* paths/**/*.rb] } }
|
||||
|
||||
it_behaves_like 'a valid config', { changes: { paths: %w[Dockerfile lib/* paths/**/*.rb] } }
|
||||
|
||||
context 'with paths:' do
|
||||
let(:config) { { changes: { paths: %w[Dockerfile lib/* paths/**/*.rb] } } }
|
||||
|
||||
it_behaves_like 'a valid config'
|
||||
end
|
||||
|
||||
context 'with paths: and compare_to:' do
|
||||
let(:config) { { changes: { paths: ['Dockerfile'], compare_to: 'branch1' } } }
|
||||
|
||||
it_behaves_like 'a valid config'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when specifying an unknown keyword' do
|
||||
let(:config) { { invalid: :something } }
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules, feature_category: :pip
|
|||
entry.compose!
|
||||
end
|
||||
|
||||
it_behaves_like 'an invalid config', /contains unknown keys: changes/
|
||||
it_behaves_like 'a valid config'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -80,7 +80,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules, feature_category: :pip
|
|||
let(:config) do
|
||||
[
|
||||
{ if: '$THIS == "that"' },
|
||||
{ if: '$SKIP', when: 'never' }
|
||||
{ if: '$SKIP', when: 'never' },
|
||||
{ changes: ['Dockerfile'] }
|
||||
]
|
||||
end
|
||||
|
||||
|
|
@ -96,7 +97,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules, feature_category: :pip
|
|||
is_expected.to eq(
|
||||
[
|
||||
{ if: '$THIS == "that"' },
|
||||
{ if: '$SKIP', when: 'never' }
|
||||
{ if: '$SKIP', when: 'never' },
|
||||
{ changes: { paths: ['Dockerfile'] } }
|
||||
]
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipeline_composition do
|
||||
let(:project) { build(:project) }
|
||||
let(:pipeline) { double('Pipeline') }
|
||||
let(:user) { double('User') }
|
||||
let(:sha) { '12345' }
|
||||
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'a', 'value' => 'b' }]) }
|
||||
|
|
@ -11,6 +12,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
|
|||
let(:attributes) do
|
||||
{
|
||||
project: project,
|
||||
pipeline: pipeline,
|
||||
user: user,
|
||||
sha: sha,
|
||||
variables: variables,
|
||||
|
|
@ -32,7 +34,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
|
|||
end
|
||||
|
||||
context 'without values' do
|
||||
let(:attributes) { { project: nil, user: nil, sha: nil } }
|
||||
let(:attributes) { { project: nil, pipeline: nil, user: nil, sha: nil } }
|
||||
|
||||
it { is_expected.to have_attributes(**attributes) }
|
||||
it { expect(subject.expandset).to eq([]) }
|
||||
|
|
@ -148,6 +150,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
|
|||
let(:attributes) do
|
||||
{
|
||||
project: project,
|
||||
pipeline: pipeline,
|
||||
user: user,
|
||||
sha: sha,
|
||||
logger: double('logger')
|
||||
|
|
@ -165,6 +168,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
|
|||
it { expect(mutated).not_to eq(subject) }
|
||||
it { expect(mutated).to be_a(described_class) }
|
||||
it { expect(mutated).to have_attributes(new_attributes) }
|
||||
it { expect(mutated.pipeline).to eq(subject.pipeline) }
|
||||
it { expect(mutated.expandset).to eq(subject.expandset) }
|
||||
it { expect(mutated.execution_deadline).to eq(mutated.execution_deadline) }
|
||||
it { expect(mutated.logger).to eq(mutated.logger) }
|
||||
|
|
|
|||
|
|
@ -557,11 +557,11 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
|
|||
context 'when rules defined' do
|
||||
context 'when a rule is invalid' do
|
||||
let(:values) do
|
||||
{ include: [{ local: 'builds.yml', rules: [{ changes: ['$MY_VAR'] }] }] }
|
||||
{ include: [{ local: 'builds.yml', rules: [{ allow_failure: ['$MY_VAR'] }] }] }
|
||||
end
|
||||
|
||||
it 'raises IncludeError' do
|
||||
expect { subject }.to raise_error(described_class::IncludeError, /contains unknown keys: changes/)
|
||||
expect { subject }.to raise_error(described_class::IncludeError, /contains unknown keys: allow_failure/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,122 +4,197 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_composition do
|
||||
let(:context) { double(variables_hash: {}) }
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] }
|
||||
let(:rule_hashes) {}
|
||||
let(:pipeline) { instance_double(Ci::Pipeline) }
|
||||
let_it_be(:project) { create(:project, :custom_repo, files: { 'file.txt' => 'file' }) }
|
||||
|
||||
subject(:rules) { described_class.new(rule_hashes) }
|
||||
|
||||
before do
|
||||
allow(context).to receive(:project).and_return(project)
|
||||
allow(context).to receive(:pipeline).and_return(pipeline)
|
||||
end
|
||||
|
||||
describe '#evaluate' do
|
||||
subject(:result) { rules.evaluate(context).pass? }
|
||||
|
||||
context 'when there is no rule' do
|
||||
let(:rule_hashes) {}
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
shared_examples 'when there is a rule with if' do |rule_matched_result = true, rule_not_matched_result = false|
|
||||
shared_examples 'with when: specified' do
|
||||
context 'with when: never' do
|
||||
before do
|
||||
rule_hashes.first[:when] = 'never'
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'with when: always' do
|
||||
before do
|
||||
rule_hashes.first[:when] = 'always'
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'with when: <invalid string>' do
|
||||
before do
|
||||
rule_hashes.first[:when] = 'on_success'
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with when: null' do
|
||||
before do
|
||||
rule_hashes.first[:when] = nil
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a rule with if:' do
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] }
|
||||
|
||||
context 'when the rule matches' do
|
||||
let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) }
|
||||
|
||||
it { is_expected.to eq(rule_matched_result) }
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
it_behaves_like 'with when: specified'
|
||||
end
|
||||
|
||||
context 'when the rule does not match' do
|
||||
let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) }
|
||||
|
||||
it { is_expected.to eq(rule_not_matched_result) }
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'when there is a rule with exists' do |file_exists_result = true, file_not_exists_result = false|
|
||||
let(:project) { create(:project, :repository) }
|
||||
context 'when there is a rule with exists:' do
|
||||
let(:rule_hashes) { [{ exists: 'file.txt' }] }
|
||||
|
||||
context 'when the file exists' do
|
||||
let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) }
|
||||
let(:context) { double(top_level_worktree_paths: ['file.txt']) }
|
||||
|
||||
before do
|
||||
project.repository.create_file(project.first_owner, 'Dockerfile', "commit", message: 'test', branch_name: "master")
|
||||
end
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
it { is_expected.to eq(file_exists_result) }
|
||||
it_behaves_like 'with when: specified'
|
||||
end
|
||||
|
||||
context 'when the file does not exist' do
|
||||
let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) }
|
||||
let(:context) { double(top_level_worktree_paths: ['README.md']) }
|
||||
|
||||
it { is_expected.to eq(file_not_exists_result) }
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'when there is a rule with if'
|
||||
context 'when there is a rule with changes:' do
|
||||
let(:rule_hashes) { [{ changes: ['file.txt'] }] }
|
||||
|
||||
context 'when there is a rule with exists' do
|
||||
let(:rule_hashes) { [{ exists: 'Dockerfile' }] }
|
||||
shared_examples 'when the pipeline has modified paths' do
|
||||
let(:modified_paths) { ['file.txt'] }
|
||||
|
||||
it_behaves_like 'when there is a rule with exists'
|
||||
end
|
||||
before do
|
||||
allow(pipeline).to receive(:modified_paths).and_return(modified_paths)
|
||||
end
|
||||
|
||||
context 'when there is a rule with if and when' do
|
||||
context 'with when: never' do
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'never' }] }
|
||||
context 'when the file has changed' do
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
it_behaves_like 'when there is a rule with if', false, false
|
||||
end
|
||||
it_behaves_like 'with when: specified'
|
||||
end
|
||||
|
||||
context 'with when: always' do
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'always' }] }
|
||||
context 'when the file has not changed' do
|
||||
let(:modified_paths) { ['README.md'] }
|
||||
|
||||
it_behaves_like 'when there is a rule with if'
|
||||
end
|
||||
it { is_expected.to eq(false) }
|
||||
|
||||
context 'with when: <invalid string>' do
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
|
||||
context 'when FF `ci_support_include_rules_changes` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_support_include_rules_changes: false)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with when: null' do
|
||||
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: nil }] }
|
||||
it_behaves_like 'when the pipeline has modified paths'
|
||||
|
||||
it_behaves_like 'when there is a rule with if'
|
||||
end
|
||||
end
|
||||
context 'with paths: specified' do
|
||||
let(:rule_hashes) { [{ changes: { paths: ['file.txt'] } }] }
|
||||
|
||||
context 'when there is a rule with exists and when' do
|
||||
context 'with when: never' do
|
||||
let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'never' }] }
|
||||
|
||||
it_behaves_like 'when there is a rule with exists', false, false
|
||||
it_behaves_like 'when the pipeline has modified paths'
|
||||
end
|
||||
|
||||
context 'with when: always' do
|
||||
let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'always' }] }
|
||||
context 'with paths: and compare_to: specified' do
|
||||
before_all do
|
||||
project.repository.add_branch(project.owner, 'branch1', 'master')
|
||||
|
||||
it_behaves_like 'when there is a rule with exists'
|
||||
end
|
||||
project.repository.update_file(
|
||||
project.owner, 'file.txt', 'file updated', message: 'Update file.txt', branch_name: 'branch1'
|
||||
)
|
||||
|
||||
context 'with when: <invalid string>' do
|
||||
let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] }
|
||||
project.repository.add_branch(project.owner, 'branch2', 'branch1')
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
|
||||
let_it_be(:pipeline) do
|
||||
build(:ci_pipeline, project: project, ref: 'branch2', sha: project.commit('branch2').sha)
|
||||
end
|
||||
|
||||
context 'when the file has changed compared to the given ref' do
|
||||
let(:rule_hashes) { [{ changes: { paths: ['file.txt'], compare_to: 'master' } }] }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
it_behaves_like 'with when: specified'
|
||||
end
|
||||
|
||||
context 'when the file has not changed compared to the given ref' do
|
||||
let(:rule_hashes) { [{ changes: { paths: ['file.txt'], compare_to: 'branch1' } }] }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
|
||||
context 'when FF `ci_support_include_rules_changes` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_support_include_rules_changes: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when compare_to: is invalid' do
|
||||
let(:rule_hashes) { [{ changes: { paths: ['file.txt'], compare_to: 'invalid' } }] }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /compare_to is not a valid ref/)
|
||||
end
|
||||
|
||||
context 'when FF `ci_support_include_rules_changes` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_support_include_rules_changes: false)
|
||||
end
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect { result }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with when: null' do
|
||||
let(:rule_hashes) { [{ exists: 'Dockerfile', when: nil }] }
|
||||
|
||||
it_behaves_like 'when there is a rule with exists'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a rule with changes' do
|
||||
let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
|
||||
context 'when there is a rule with an invalid key' do
|
||||
let(:rule_hashes) { [{ invalid: ['$MY_VAR'] }] }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /contains unknown keys: changes/)
|
||||
expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /contains unknown keys: invalid/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::YourWork::Menus::OrganizationsMenu, feature_category: :navigation do
|
||||
let(:user) { build_stubbed(:user) }
|
||||
let(:context) { Sidebars::Context.new(current_user: user, container: nil) }
|
||||
|
||||
subject { described_class.new(context) }
|
||||
|
||||
describe '#render?' do
|
||||
context 'when `ui_for_organizations` feature flag is enabled' do
|
||||
context 'when `current_user` is available' do
|
||||
it 'returns true' do
|
||||
expect(subject.render?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `current_user` is not available' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.render?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `ui_for_organizations` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ui_for_organizations: false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.render?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -218,53 +218,28 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
describe '#user_detail' do
|
||||
context 'when user is not persisted' do
|
||||
let(:user) { build(:user) }
|
||||
|
||||
it 'builds `user_detail`' do
|
||||
expect(user.user_detail).to be_instance_of(UserDetail)
|
||||
expect(user.user_detail).not_to be_persisted
|
||||
end
|
||||
it 'does not persist `user_detail` by default' do
|
||||
expect(create(:user).user_detail).not_to be_persisted
|
||||
end
|
||||
|
||||
context 'when user is persisted' do
|
||||
let(:user) { create(:user) }
|
||||
shared_examples 'delegated field' do |field|
|
||||
it 'creates `user_detail` when the field is given' do
|
||||
user = create(:user, field => 'my field')
|
||||
|
||||
it 'creates `user_detail`' do
|
||||
expect(UserDetail.exists?(user_id: user.id)).to eq(false)
|
||||
|
||||
user.user_detail
|
||||
|
||||
expect(UserDetail.exists?(user_id: user.id)).to eq(true)
|
||||
|
||||
expect(user.user_detail).to be_instance_of(UserDetail)
|
||||
expect(user.user_detail).to be_persisted
|
||||
expect(user.user_detail[field]).to eq('my field')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'delegated field' do |field, value = 'field value'|
|
||||
context "when #{field} field" do
|
||||
it 'creates `user_detail` when the field is given', :aggregate_failures do
|
||||
user = create(:user, field => value)
|
||||
it 'delegates to `user_detail`' do
|
||||
user = create(:user, field => 'my field')
|
||||
|
||||
expect(user.user_detail).to be_persisted
|
||||
expect(user.user_detail[field]).to eq(value)
|
||||
end
|
||||
expect(user.public_send(field)).to eq(user.user_detail[field])
|
||||
end
|
||||
|
||||
it 'delegates the field to `user_detail`' do
|
||||
user = create(:user, field => value)
|
||||
it 'creates `user_detail` when first updated' do
|
||||
user = create(:user)
|
||||
|
||||
expect(user.public_send(field)).to eq(user.user_detail[field])
|
||||
end
|
||||
|
||||
it 'creates `user_detail` when the field is first updated', :aggregate_failures do
|
||||
user = create(:user)
|
||||
|
||||
user.update!(field => value)
|
||||
|
||||
expect(user.user_detail).to be_persisted
|
||||
expect(user.user_detail[field]).to eq(value)
|
||||
end
|
||||
expect { user.update!(field => 'my field') }.to change { user.user_detail.persisted? }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -274,27 +249,36 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
it_behaves_like 'delegated field', :skype
|
||||
it_behaves_like 'delegated field', :location
|
||||
it_behaves_like 'delegated field', :organization
|
||||
it_behaves_like 'delegated field', :website_url, 'https://example.com'
|
||||
it_behaves_like 'delegated field', :pronouns, 'they/them'
|
||||
it_behaves_like 'delegated field', :pronunciation, 'uhg-zaam-pl'
|
||||
|
||||
context 'when race condition' do
|
||||
it 'handles it properly' do
|
||||
user = create(:user)
|
||||
stale_user = described_class.find(user.id)
|
||||
it 'creates `user_detail` when `website_url` is given' do
|
||||
user = create(:user, website_url: 'https://example.com')
|
||||
|
||||
user.user_detail
|
||||
stale_user.user_detail
|
||||
expect(user.user_detail).to be_persisted
|
||||
expect(user.user_detail.website_url).to eq('https://example.com')
|
||||
end
|
||||
|
||||
user.update!(bio: 'hello')
|
||||
it 'delegates `website_url` to `user_detail`' do
|
||||
user = create(:user, website_url: 'http://example.com')
|
||||
|
||||
expect { stale_user.update!(pronunciation: 'my-pronunciation') }.not_to raise_error
|
||||
expect(user.website_url).to eq(user.user_detail.website_url)
|
||||
end
|
||||
|
||||
user.reload
|
||||
it 'creates `user_detail` when `website_url` is first updated' do
|
||||
user = create(:user)
|
||||
|
||||
expect(user.bio).to eq('hello')
|
||||
expect(user.pronunciation).to eq('my-pronunciation')
|
||||
end
|
||||
expect { user.update!(website_url: 'https://example.com') }.to change { user.user_detail.persisted? }.from(false).to(true)
|
||||
end
|
||||
|
||||
it 'delegates `pronouns` to `user_detail`' do
|
||||
user = create(:user, pronouns: 'they/them')
|
||||
|
||||
expect(user.pronouns).to eq(user.user_detail.pronouns)
|
||||
end
|
||||
|
||||
it 'delegates `pronunciation` to `user_detail`' do
|
||||
user = create(:user, name: 'Example', pronunciation: 'uhg-zaam-pl')
|
||||
|
||||
expect(user.pronunciation).to eq(user.user_detail.pronunciation)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -465,7 +449,7 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
|
||||
describe 'validations' do
|
||||
describe 'password' do
|
||||
let!(:user) { build_stubbed(:user, user_detail: build_stubbed(:user_detail)) }
|
||||
let!(:user) { build_stubbed(:user) }
|
||||
|
||||
before do
|
||||
allow(Devise).to receive(:password_length).and_return(8..128)
|
||||
|
|
@ -637,7 +621,7 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
context 'when username is changed' do
|
||||
let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:user_namespace), user_detail: build_stubbed(:user_detail)) }
|
||||
let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:user_namespace)) }
|
||||
|
||||
it 'validates move_dir is allowed for the namespace' do
|
||||
expect(user.namespace).to receive(:any_project_has_container_registry_tags?).and_return(true)
|
||||
|
|
|
|||
|
|
@ -695,4 +695,18 @@ RSpec.describe GlobalPolicy, feature_category: :shared do
|
|||
it { is_expected.to be_disallowed(:create_instance_runner) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create_organization' do
|
||||
context 'with regular user' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it { is_expected.to be_allowed(:create_organization) }
|
||||
end
|
||||
|
||||
context 'with anonymous' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it { is_expected.to be_disallowed(:create_organizatinon) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
|
|||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :repository, :public, group: group) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user, :with_user_detail).tap { |reporter| project.add_reporter(reporter) } }
|
||||
let_it_be(:reporter) { create(:user).tap { |reporter| project.add_reporter(reporter) } }
|
||||
let_it_be(:label1) { create(:label, project: project) }
|
||||
let_it_be(:label2) { create(:label, project: project) }
|
||||
let_it_be(:milestone1) { create(:milestone, project: project) }
|
||||
|
|
@ -410,7 +410,7 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
|
|||
# TODO: Fix N+1 queries executed for the linked work item widgets
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/420605
|
||||
expect { post_graphql(query, current_user: current_user) }
|
||||
.not_to exceed_all_query_limit(control).with_threshold(13)
|
||||
.not_to exceed_all_query_limit(control).with_threshold(11)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do
|
|||
get api(members_url, maintainer)
|
||||
end
|
||||
|
||||
project.add_developer(create(:user, :with_user_detail))
|
||||
project.add_developer(create(:user))
|
||||
|
||||
expect do
|
||||
get api(members_url, maintainer)
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
|
|||
end
|
||||
|
||||
describe "User with no identities" do
|
||||
let(:user) { create(:user, :with_user_detail) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context "when the project doesn't exist" do
|
||||
context "when namespace doesn't exist" do
|
||||
|
|
|
|||
|
|
@ -13,25 +13,30 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'action disabled by `ui_for_organizations` feature flag' do
|
||||
before do
|
||||
stub_feature_flags(ui_for_organizations: false)
|
||||
end
|
||||
|
||||
it 'renders 404' do
|
||||
shared_examples 'redirects to sign in page' do
|
||||
it 'redirects to sign in page' do
|
||||
gitlab_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'basic organization controller action' do
|
||||
context 'when the user is not logged in' do
|
||||
it_behaves_like 'successful response'
|
||||
it_behaves_like 'action disabled by `ui_for_organizations` feature flag'
|
||||
end
|
||||
shared_examples 'action disabled by `ui_for_organizations` feature flag' do
|
||||
context 'when `ui_for_organizations` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ui_for_organizations: false)
|
||||
end
|
||||
|
||||
context 'when the user is logged in' do
|
||||
it 'renders 404' do
|
||||
gitlab_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'when the user is signed in' do
|
||||
context 'when the user is signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
@ -63,15 +68,52 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'controller action that requires authentication' do
|
||||
context 'when the user is not signed in' do
|
||||
it_behaves_like 'redirects to sign in page'
|
||||
|
||||
context 'when `ui_for_organizations` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ui_for_organizations: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'redirects to sign in page'
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'when the user is signed in'
|
||||
end
|
||||
|
||||
shared_examples 'controller action that does not require authentication' do
|
||||
context 'when the user is not logged in' do
|
||||
it_behaves_like 'successful response'
|
||||
it_behaves_like 'action disabled by `ui_for_organizations` feature flag'
|
||||
end
|
||||
|
||||
it_behaves_like 'when the user is signed in'
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
subject(:gitlab_request) { get organization_path(organization) }
|
||||
|
||||
it_behaves_like 'basic organization controller action'
|
||||
it_behaves_like 'controller action that does not require authentication'
|
||||
end
|
||||
|
||||
describe 'GET #groups_and_projects' do
|
||||
subject(:gitlab_request) { get groups_and_projects_organization_path(organization) }
|
||||
|
||||
it_behaves_like 'basic organization controller action'
|
||||
it_behaves_like 'controller action that does not require authentication'
|
||||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
subject(:gitlab_request) { get new_organization_path }
|
||||
|
||||
it_behaves_like 'controller action that requires authentication'
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
subject(:gitlab_request) { get organizations_path }
|
||||
|
||||
it_behaves_like 'controller action that requires authentication'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe SearchController, type: :request, feature_category: :global_search do
|
||||
let_it_be(:user) { create(:user, :with_user_detail) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) }
|
||||
let_it_be(:projects) { create_list(:project, 5, :public, :repository, :wiki_repo) }
|
||||
|
|
@ -44,7 +44,7 @@ RSpec.describe SearchController, type: :request, feature_category: :global_searc
|
|||
let(:params) { { search: 'foo', scope: 'issues' } }
|
||||
# some N+1 queries still exist
|
||||
# each issue runs an extra query for group namespaces
|
||||
let(:threshold) { 3 }
|
||||
let(:threshold) { 1 }
|
||||
|
||||
it_behaves_like 'an efficient database result'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@ RSpec.describe Organizations::OrganizationsController, :routing, feature_categor
|
|||
.to route_to('organizations/organizations#show', organization_path: organization.path)
|
||||
end
|
||||
|
||||
it 'routes to #new' do
|
||||
expect(get("/-/organizations/new"))
|
||||
.to route_to('organizations/organizations#new')
|
||||
end
|
||||
|
||||
it 'routes to #index' do
|
||||
expect(get("/-/organizations"))
|
||||
.to route_to('organizations/organizations#index')
|
||||
end
|
||||
|
||||
it 'routes to #groups_and_projects' do
|
||||
expect(get("/-/organizations/#{organization.path}/groups_and_projects"))
|
||||
.to route_to('organizations/organizations#groups_and_projects', organization_path: organization.path)
|
||||
|
|
|
|||
|
|
@ -227,6 +227,10 @@ RSpec.shared_context 'dashboard navbar structure' do
|
|||
nav_item: _("Groups"),
|
||||
nav_sub_items: []
|
||||
},
|
||||
{
|
||||
nav_item: _('Organizations'),
|
||||
nav_sub_items: []
|
||||
},
|
||||
{
|
||||
nav_item: _("Issues"),
|
||||
nav_sub_items: []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'layouts/organization', feature_category: :cell do
|
||||
let_it_be(:organization) { build_stubbed(:organization) }
|
||||
let_it_be(:current_user) { build_stubbed(:user, :admin) }
|
||||
|
||||
before do
|
||||
allow(view).to receive(:current_user).and_return(current_user)
|
||||
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(current_user))
|
||||
allow(view).to receive(:users_path).and_return('/root')
|
||||
end
|
||||
|
||||
subject do
|
||||
render
|
||||
|
||||
rendered
|
||||
end
|
||||
|
||||
describe 'navigation' do
|
||||
context 'when action is #index' do
|
||||
before do
|
||||
allow(view).to receive(:params).and_return({ action: 'index' })
|
||||
end
|
||||
|
||||
it 'renders your_work navigation' do
|
||||
subject
|
||||
|
||||
expect(view.instance_variable_get(:@nav)).to eq('your_work')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when action is #new' do
|
||||
before do
|
||||
allow(view).to receive(:params).and_return({ action: 'new' })
|
||||
end
|
||||
|
||||
it 'renders your_work navigation' do
|
||||
subject
|
||||
|
||||
expect(view.instance_variable_get(:@nav)).to eq('your_work')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when action is #show' do
|
||||
before do
|
||||
allow(view).to receive(:params).and_return({ action: 'show' })
|
||||
view.instance_variable_set(:@organization, organization)
|
||||
end
|
||||
|
||||
it 'renders organization navigation' do
|
||||
subject
|
||||
|
||||
expect(view.instance_variable_get(:@nav)).to eq('organization')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue