Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-08-29 18:10:24 +00:00
parent 9a8093da81
commit 3e3f936ff7
63 changed files with 697 additions and 238 deletions

View File

@ -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"

View File

@ -845,6 +845,9 @@
"if": {
"$ref": "#/definitions/if"
},
"changes": {
"$ref": "#/definitions/changes"
},
"exists": {
"$ref": "#/definitions/exists"
},

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1,2 @@
- page_title s_('Organization|Organizations')
- header_title _("Your work"), root_path

View File

@ -0,0 +1,3 @@
- page_title s_('Organization|New organization')
- header_title _("Your work"), root_path
- add_to_breadcrumbs s_('Organization|Organizations'), organizations_path

View File

@ -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

View File

@ -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

View File

@ -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) |

View File

@ -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.
![Credentials inventory page - Project access tokens](img/credentials_inventory_project_access_tokens_v14_9.png)
@ -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.
![Credentials inventory page - SSH keys](img/credentials_inventory_ssh_keys_v14_9.png)
@ -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).
![Credentials inventory page - GPG keys](img/credentials_inventory_gpg_keys_v14_9.png)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -83,7 +83,7 @@ of an incident.
![Incident timeline event for severity change](img/timeline_event_for_severity_change_v15_6.png)
### 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.

View File

@ -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).

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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 ""

View File

@ -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
},
};

View File

@ -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

View File

@ -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');

View File

@ -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:

View File

@ -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) }

View File

@ -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({

View File

@ -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 } }

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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: []

View File

@ -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