Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-30 15:09:50 +00:00
parent 6d19e491d1
commit 69d28d313c
43 changed files with 1333 additions and 480 deletions

View File

@ -9,7 +9,9 @@ import {
GlIcon,
GlLoadingIcon,
GlSkeletonLoader,
GlResizeObserverDirective,
} from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { isEmpty } from 'lodash';
import { __, s__ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@ -33,6 +35,9 @@ export default {
GlSkeletonLoader,
ModalCopyButton,
},
directives: {
GlResizeObserver: GlResizeObserverDirective,
},
props: {
modalId: {
type: String,
@ -87,6 +92,7 @@ export default {
selectedArchitecture: null,
showAlert: false,
instructions: {},
platformsButtonGroupVertical: false,
};
},
computed: {
@ -127,6 +133,13 @@ export default {
toggleAlert(state) {
this.showAlert = state;
},
onPlatformsButtonResize() {
if (bp.getBreakpointSize() === 'xs') {
this.platformsButtonGroupVertical = true;
} else {
this.platformsButtonGroupVertical = false;
}
},
},
i18n: {
installARunner: s__('Runners|Install a runner'),
@ -159,17 +172,23 @@ export default {
<h5>
{{ __('Environment') }}
</h5>
<gl-button-group class="gl-mb-3">
<gl-button
v-for="platform in platforms"
:key="platform.name"
:selected="selectedPlatform && selectedPlatform.name === platform.name"
data-testid="platform-button"
@click="selectPlatform(platform)"
<div v-gl-resize-observer="onPlatformsButtonResize">
<gl-button-group
:vertical="platformsButtonGroupVertical"
:class="{ 'gl-w-full': platformsButtonGroupVertical }"
class="gl-mb-3"
data-testid="platform-buttons"
>
{{ platform.humanReadableName }}
</gl-button>
</gl-button-group>
<gl-button
v-for="platform in platforms"
:key="platform.name"
:selected="selectedPlatform && selectedPlatform.name === platform.name"
@click="selectPlatform(platform)"
>
{{ platform.humanReadableName }}
</gl-button>
</gl-button-group>
</div>
</template>
<template v-if="hasArchitecureList">
<template v-if="selectedPlatform">
@ -190,7 +209,7 @@ export default {
{{ architecture.name }}
</gl-dropdown-item>
</gl-dropdown>
<div class="gl-display-flex gl-align-items-center gl-mb-3">
<div class="gl-sm-display-flex gl-align-items-center gl-mb-3">
<h5>{{ $options.i18n.downloadInstallBinary }}</h5>
<gl-button
class="gl-ml-auto"

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
# Arguments:
# current_user: The currently logged in user.
# scope: A Project or Group to scope deploy tokens to (or :all for all tokens).
# params:
# active: Boolean - When true, only return active deployments.
module DeployTokens
class TokensFinder
attr_reader :current_user, :params, :scope
def initialize(current_user, scope, params = {})
@current_user = current_user
@scope = scope
@params = params
end
def execute
by_active(init_collection)
end
private
def init_collection
case scope
when Group, Project
raise Gitlab::Access::AccessDeniedError unless current_user.can?(:read_deploy_token, scope)
scope.deploy_tokens
when :all
raise Gitlab::Access::AccessDeniedError unless current_user.can_read_all_resources?
DeployToken.all
else
raise ArgumentError, "Scope must be a Group, a Project, or the :all symbol."
end
end
def by_active(items)
params[:active] ? items.active : items
end
end
end

View File

@ -404,14 +404,6 @@ module ProjectsHelper
nav_tabs << :pipelines
end
if can_view_operations_tab?(current_user, project)
nav_tabs << :operations
end
if can_view_product_analytics?(current_user, project)
nav_tabs << :product_analytics
end
tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project)
nav_tabs << tab
@ -470,32 +462,6 @@ module ProjectsHelper
}
end
def view_operations_tab_ability
[
:metrics_dashboard,
:read_alert_management_alert,
:read_environment,
:read_issue,
:read_sentry_issue,
:read_cluster,
:read_feature_flag,
:read_terraform_state
]
end
def can_view_operations_tab?(current_user, project)
return false unless project.feature_available?(:operations, current_user)
view_operations_tab_ability.any? do |ability|
can?(current_user, ability, project)
end
end
def can_view_product_analytics?(current_user, project)
Feature.enabled?(:product_analytics, project) &&
can?(current_user, :read_product_analytics, project)
end
def search_tab_ability_map
@search_tab_ability_map ||= tab_ability_map.merge(
blobs: :download_code,
@ -563,14 +529,6 @@ module ProjectsHelper
end
end
def sidebar_operations_link_path(project = @project)
if can?(current_user, :read_environment, project)
metrics_project_environments_path(project)
else
project_feature_flags_path(project)
end
end
def project_last_activity(project)
if project.last_activity_at
time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')

View File

@ -41,7 +41,8 @@ module SidebarsHelper
learn_gitlab_experiment_enabled: learn_gitlab_experiment_enabled?(project),
current_ref: current_ref,
jira_issues_integration: project_jira_issues_integration?,
can_view_pipeline_editor: can_view_pipeline_editor?(project)
can_view_pipeline_editor: can_view_pipeline_editor?(project),
show_cluster_hint: show_gke_cluster_integration_callout?(project)
}
end
end

View File

@ -1,98 +1,3 @@
- if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do
= link_to sidebar_operations_link_path, class: 'shortcuts-operations', data: { qa_selector: 'operations_link' } do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
= _('Operations')
%ul.sidebar-sub-level-items
= nav_link(controller: sidebar_operations_paths, html_options: { class: "fly-out-top-item" } ) do
= link_to sidebar_operations_link_path do
%strong.fly-out-top-item-name
= _('Operations')
%li.divider.fly-out-top-item
- if project_nav_tab? :metrics_dashboards
= nav_link(controller: :metrics_dashboard, action: [:show]) do
= link_to project_metrics_dashboard_path(@project), title: _('Metrics'), class: 'shortcuts-metrics', data: { qa_selector: 'operations_metrics_link' } do
%span
= _('Metrics')
- if project_nav_tab?(:environments) && can?(current_user, :read_pod_logs, @project)
= nav_link(controller: :logs, action: [:index]) do
= link_to project_logs_path(@project), title: _('Logs') do
%span
= _('Logs')
- if project_nav_tab? :environments
= render "layouts/nav/sidebar/tracing_link"
- if project_nav_tab?(:error_tracking)
= nav_link(controller: :error_tracking) do
= link_to project_error_tracking_index_path(@project), title: _('Error Tracking') do
%span
= _('Error Tracking')
- if project_nav_tab?(:alert_management)
= nav_link(controller: :alert_management) do
= link_to project_alert_management_index_path(@project), title: _('Alerts') do
%span
= _('Alerts')
- if project_nav_tab?(:incidents)
= nav_link(controller: :incidents) do
= link_to project_incidents_path(@project), title: _('Incidents'), data: { qa_selector: 'operations_incidents_link' } do
%span
= _('Incidents')
= render_if_exists 'projects/sidebar/oncall_schedules'
- if project_nav_tab? :serverless
= nav_link(controller: :functions) do
= link_to project_serverless_functions_path(@project), title: _('Serverless') do
%span
= _('Serverless')
- if project_nav_tab? :terraform
= nav_link(controller: :terraform) do
= link_to project_terraform_index_path(@project), title: _('Terraform') do
%span
= _('Terraform')
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:cluster_agents, :clusters]) do
= link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
%span
= _('Kubernetes')
- if show_cluster_hint
.js-feature-highlight{ disabled: true,
data: { trigger: 'manual',
container: 'body',
placement: 'right',
highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: user_callouts_path,
auto_devops_help_path: help_page_path('topics/autodevops/index.md') } }
- if project_nav_tab? :environments
= nav_link(controller: :environments, action: [:index, :folder, :show, :new, :edit, :create, :update, :stop, :terminal]) do
= link_to project_environments_path(@project), title: _('Environments'), class: 'shortcuts-environments qa-operations-environments-link' do
%span
= _('Environments')
- if project_nav_tab? :feature_flags
= nav_link(controller: :feature_flags) do
= link_to project_feature_flags_path(@project), title: _('Feature Flags'), class: 'shortcuts-feature-flags' do
%span
= _('Feature Flags')
- if project_nav_tab?(:product_analytics)
= nav_link(controller: :product_analytics) do
= link_to project_product_analytics_path(@project), title: _('Product Analytics') do
%span
= _('Product Analytics')
= render_if_exists 'layouts/nav/sidebar/project_packages_link'
- if project_nav_tab? :analytics

View File

@ -1,7 +0,0 @@
- return unless can?(current_user, :read_environment, @project)
- if project_nav_tab? :settings
= nav_link(controller: :tracings, action: [:show]) do
= link_to project_tracing_path(@project), title: _('Tracing') do
%span
= _('Tracing')

View File

@ -0,0 +1,5 @@
---
title: Remove search_track_unique_users feature flag
merge_request: 60706
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Add "active" filter to deploy tokens API
merge_request: 59582
author: Devin Christensen
type: added

View File

@ -0,0 +1,6 @@
---
title: This change captures resizes of the runner installation instructions modal
to make it usable on screens.
merge_request: 60588
author:
type: changed

View File

@ -1,8 +0,0 @@
---
name: search_track_unique_users
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40134
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/240906
milestone: '13.4'
type: development
group: group::global search
default_enabled: true

View File

@ -14,7 +14,7 @@ class InitializeConversionOfEventsIdToBigint < ActiveRecord::Migration[6.0]
def down
trigger_name = rename_trigger_name(:events, :id, :id_convert_to_bigint)
remove_rename_triggers_for_postgresql :events, trigger_name
remove_rename_triggers :events, trigger_name
remove_column :events, :id_convert_to_bigint
end

View File

@ -14,7 +14,7 @@ class InitializeConversionOfPushEventPayloadsEventIdToBigint < ActiveRecord::Mig
def down
trigger_name = rename_trigger_name(:push_event_payloads, :event_id, :event_id_convert_to_bigint)
remove_rename_triggers_for_postgresql :push_event_payloads, trigger_name
remove_rename_triggers :push_event_payloads, trigger_name
remove_column :push_event_payloads, :event_id_convert_to_bigint
end

View File

@ -10,7 +10,7 @@ class InitializeConversionOfCiBuildNeedsToBigint < ActiveRecord::Migration[6.0]
def down
trigger_name = rename_trigger_name(:ci_build_needs, :build_id, :build_id_convert_to_bigint)
remove_rename_triggers_for_postgresql :ci_build_needs, trigger_name
remove_rename_triggers :ci_build_needs, trigger_name
remove_column :ci_build_needs, :build_id_convert_to_bigint
end

View File

@ -13,7 +13,7 @@ class InitializeConversionOfCiJobArtifactsToBigint < ActiveRecord::Migration[6.0
def down
trigger_name = rename_trigger_name(TABLE, COLUMNS, TARGET_COLUMNS)
remove_rename_triggers_for_postgresql TABLE, trigger_name
remove_rename_triggers TABLE, trigger_name
TARGET_COLUMNS.each do |column|
remove_column TABLE, column

View File

@ -11,7 +11,7 @@ class InitializeConversionOfCiSourcesPipelinesSourceJobIdToBigint < ActiveRecord
def down
trigger_name = rename_trigger_name(:ci_sources_pipelines, :source_job_id, :source_job_id_convert_to_bigint)
remove_rename_triggers_for_postgresql :ci_sources_pipelines, trigger_name
remove_rename_triggers :ci_sources_pipelines, trigger_name
remove_column :ci_sources_pipelines, :source_job_id_convert_to_bigint
end

View File

@ -16,6 +16,12 @@ Get a list of all deploy tokens across the GitLab instance. This endpoint requir
GET /deploy_tokens
```
Parameters:
| Attribute | Type | Required | Description |
|-----------|----------|------------------------|-------------|
| `active` | boolean | **{dotted-circle}** No | Limit by active status. |
Example request:
```shell
@ -31,6 +37,8 @@ Example response:
"name": "MyToken",
"username": "gitlab+deploy-token-1",
"expires_at": "2020-02-14T00:00:00.000Z",
"revoked": false,
"expired": false,
"scopes": [
"read_repository",
"read_registry"
@ -55,9 +63,10 @@ GET /projects/:id/deploy_tokens
Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| Attribute | Type | Required | Description |
|:---------------|:---------------|:-----------------------|:------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `active` | boolean | **{dotted-circle}** No | Limit by active status. |
Example request:
@ -74,6 +83,8 @@ Example response:
"name": "MyToken",
"username": "gitlab+deploy-token-1",
"expires_at": "2020-02-14T00:00:00.000Z",
"revoked": false,
"expired": false,
"scopes": [
"read_repository",
"read_registry"
@ -92,13 +103,17 @@ Creates a new deploy token for a project.
POST /projects/:id/deploy_tokens
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | New deploy token's name |
| `expires_at` | datetime | no | Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
| `username` | string | no | Username for deploy token. Default is `gitlab+deploy-token-{n}` |
| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`. |
Parameters:
| Attribute | Type | Required | Description |
| ------------ | ---------------- | ---------------------- | ----------- |
| `id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | **{check-circle}** Yes | New deploy token's name |
| `expires_at` | datetime | **{dotted-circle}** No | Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
| `username` | string | **{dotted-circle}** No | Username for deploy token. Default is `gitlab+deploy-token-{n}` |
| `scopes` | array of strings | **{check-circle}** Yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`. |
Example request:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "My deploy token", "expires_at": "2021-01-01", "username": "custom-user", "scopes": ["read_repository"]}' "https://gitlab.example.com/api/v4/projects/5/deploy_tokens/"
@ -113,6 +128,8 @@ Example response:
"username": "custom-user",
"expires_at": "2021-01-01T00:00:00.000Z",
"token": "jMRvtPNxrn3crTAGukpZ",
"revoked": false,
"expired": false,
"scopes": [
"read_repository"
]
@ -129,10 +146,12 @@ Removes a deploy token from the project.
DELETE /projects/:id/deploy_tokens/:token_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `token_id` | integer | yes | The ID of the deploy token |
Parameters:
| Attribute | Type | Required | Description |
| ---------- | -------------- | ---------------------- | ----------- |
| `id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `token_id` | integer | **{check-circle}** Yes | The ID of the deploy token |
Example request:
@ -157,9 +176,10 @@ GET /groups/:id/deploy_tokens
Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| Attribute | Type | Required | Description |
|:---------------|:---------------|:-----------------------|:------------|
| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
| `active` | boolean | **{dotted-circle}** No | Limit by active status. |
Example request:
@ -176,6 +196,8 @@ Example response:
"name": "MyToken",
"username": "gitlab+deploy-token-1",
"expires_at": "2020-02-14T00:00:00.000Z",
"revoked": false,
"expired": false,
"scopes": [
"read_repository",
"read_registry"
@ -194,13 +216,15 @@ Creates a new deploy token for a group.
POST /groups/:id/deploy_tokens
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | New deploy token's name |
| `expires_at` | datetime | no | Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
| `username` | string | no | Username for deploy token. Default is `gitlab+deploy-token-{n}` |
| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`. |
Parameters:
| Attribute | Type | Required | Description |
| ------------ | ---- | --------- | ----------- |
| `id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | **{check-circle}** Yes | New deploy token's name |
| `expires_at` | datetime | **{dotted-circle}** No | Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
| `username` | string | **{dotted-circle}** No | Username for deploy token. Default is `gitlab+deploy-token-{n}` |
| `scopes` | array of strings | **{check-circle}** Yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`. |
Example request:
@ -217,6 +241,8 @@ Example response:
"username": "custom-user",
"expires_at": "2021-01-01T00:00:00.000Z",
"token": "jMRvtPNxrn3crTAGukpZ",
"revoked": false,
"expired": false,
"scopes": [
"read_registry"
]
@ -233,10 +259,12 @@ Removes a deploy token from the group.
DELETE /groups/:id/deploy_tokens/:token_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `token_id` | integer | yes | The ID of the deploy token |
Parameters:
| Attribute | Type | Required | Description |
| ----------- | -------------- | ---------------------- | ----------- |
| `id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `token_id` | integer | **{check-circle}** Yes | The ID of the deploy token |
Example request:

View File

@ -39,7 +39,7 @@ or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/cod
For approvals, we use the approval functionality found in the merge request
widget. For reviewers, we use the [reviewer functionality](../user/project/merge_requests/getting_started.md#reviewer) in the sidebar.
Reviewers can add their approval by [approving additionally](../user/project/merge_requests/approvals/index.md#adding-or-removing-an-approval).
Reviewers can add their approval by [approving additionally](../user/project/merge_requests/approvals/index.md#approve-a-merge-request).
Getting your merge request **merged** also requires a maintainer. If it requires
more than one approval, the last maintainer to review and approve merges it.

View File

@ -49,3 +49,6 @@ You can configure the following security controls:
- Click either **Enable** or **Configure** to use SAST for the current project. For more details, see [Configure SAST in the UI](../sast/index.md#configure-sast-in-the-ui).
- DAST Profiles
- Click **Manage** to manage the available DAST profiles used for on-demand scans. For more details, see [DAST on-demand scans](../dast/index.md#on-demand-scans).
- Secret Detection
- Select **Configure via Merge Request** to create a merge request with the changes required to
enable Secret Detection. For more details, see [Enable Secret Detection via an automatic merge request](../secret_detection/index.md#enable-secret-detection-via-an-automatic-merge-request).

View File

@ -133,6 +133,31 @@ The results are saved as a
that you can later download and analyze. Due to implementation limitations, we
always take the latest Secret Detection artifact available.
### Enable Secret Detection via an automatic merge request **(ULTIMATE SELF)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4496) in GitLab 13.11.
> - [Deployed behind a feature flag](../../../user/feature_flags.md), enabled by default.
> - Enabled on GitLab.com.
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-configure-secret-detection-via-a-merge-request). **(ULTIMATE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
There can be
[risks when disabling released features](../../../user/feature_flags.md#risks-when-disabling-released-features).
Refer to this feature's version history for more details.
To enable Secret Detection in a project, you can create a merge request
from the Security Configuration page.
1. In the project where you want to enable Secret Detection, go to
**Security & Compliance > Configuration**.
1. In the **Secret Detection** row, select **Configure via Merge Request**.
This automatically creates a merge request with the changes necessary to enable Secret Detection
that you can review and merge to complete the configuration.
### Customizing settings
The Secret Detection scan settings can be changed through [CI/CD variables](#available-variables)
@ -380,3 +405,22 @@ secret_detection:
variables:
GIT_DEPTH: 100
```
### Enable or disable Configure Secret Detection via a Merge Request
Configure Secret Detection via a Merge Request is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
Feature.enable(:sec_secret_detection_ui_enable)
```
To disable it:
```ruby
Feature.disable(:sec_secret_detection_ui_enable)
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -6,111 +6,124 @@ type: reference, concepts
disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html'
---
# Merge Request Approvals **(FREE)**
# Merge request approvals **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/580) in GitLab Enterprise Edition 7.2. Available in GitLab Free and higher tiers.
> - Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0.
> Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0.
Code review is an essential practice of every successful project. Approving a
merge request is an important part of the review
process, as it clearly communicates the ability to merge the change.
A [merge request approvals API](../../../../api/merge_request_approvals.md) is also available.
Successful projects depend on code reviews. Merge request approvals clearly communicate
someone's ability to merge proposed changes. Approvals [are optional](#optional-approvals)
in GitLab Free, but you can require them for your project in higher tiers.
## Optional Approvals
Merge request approvals are configured at the project level. Administrator users
of self-managed GitLab installations can also configure
[instance-level approval rules](../../../admin_area/merge_requests_approvals.md)
that cannot be overridden on a project-level basis.
With [merge request approval rules](rules.md), you can set the minimum number of
required approvals before work can merge into your project. You can also extend these
rules to define what types of users can approve work. Some examples of rules you can create include:
- Users with specific permissions can always approve work.
- [Code owners](../../code_owners.md) can approve work for files they own.
- Users with specific permissions can approve work, even if they don't have merge rights
to the repository.
- Users with specific permissions can be allowed or denied the ability
to override approval rules on a specific merge request.
You can also configure additional [settings for merge request approvals](settings.md)
for more control of the level of oversight and security your project needs, including:
- Prevent users from overriding a merge request approval rule.
- Reset approvals when new code is pushed.
- Allow (or disallow) authors and committers to approve their own merge requests.
- Require password authentication when approving.
- Require security team approval.
You can configure your merge request approval rules and settings through the GitLab
user interface or [with the API](../../../../api/merge_request_approvals.md).
## Approve a merge request
When an [eligible approver](rules.md#eligible-approvers) visits an open merge request,
GitLab displays one of these buttons after the body of the merge request:
- **Approve**: The merge request doesn't yet have the required number of approvals.
- **Approve additionally**: The merge request has the required number of approvals.
- **Revoke approval**: The user viewing the merge request has already approved
the merge request.
Eligible approvers can also use the `/approve`
[quick action](../../../project/quick_actions.md) when adding a comment to
a merge request.
After a merge request receives the [number and type of approvals](rules.md) you configure, it can merge
unless it's blocked for another reason. Merge requests can be blocked by other problems,
such as merge conflicts, [pending discussions](../../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved),
or a [failed CI/CD pipeline](../merge_when_pipeline_succeeds.md).
To prevent merge request authors from approving their own merge requests,
enable [**Prevent author approval**](settings.md#allowing-merge-request-authors-to-approve-their-own-merge-requests)
in your project's settings.
If you enable [approval rule overrides](settings.md#prevent-overriding-default-approvals),
merge requests created before a change to default approval rules are not affected.
The only exceptions are changes to the [target branch](rules.md#scoped-to-protected-branch)
of the rule.
## Optional approvals
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27426) in GitLab 13.2.
Any user with Developer or greater [permissions](../../../permissions.md) can approve a merge request in GitLab Free and higher tiers.
This provides a consistent mechanism for reviewers to approve merge requests, and ensures
maintainers know a change is ready to merge. Approvals in Free are optional, and do
not prevent a merge request from being merged when there is no approval.
GitLab allows all users with Developer or greater [permissions](../../../permissions.md)
to approve merge requests. Approvals in GitLab Free are optional, and don't prevent
a merge request from merging without approval.
## External approvals **(ULTIMATE)**
## Required approvals **(PREMIUM)**
> Moved to [GitLab Premium](https://about.gitlab.com/pricing/) in 13.9.
Required approvals enforce code reviews by the number and type of users you specify.
Without the approvals, the work cannot merge. Required approvals enable multiple use cases:
- Enforce review of all code that gets merged into a repository.
- Specify reviewers for a given proposed code change, and a minimum number
of reviewers, through [Approval rules](rules.md).
- Specify categories of reviewers, such as backend, frontend, quality assurance, or
database, for all proposed code changes.
- Use the [code owners of changed files](rules.md#code-owners-as-eligible-approvers),
to determine who should review the work.
- [Require approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests)
before merging code that could introduce a vulnerability. **(ULTIMATE)**
## Notify external services **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab Ultimate 13.10.
> - It's [deployed behind a feature flag](../../../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](../../../../api/merge_request_approvals.md#enable-or-disable-external-project-level-mr-approvals). **(ULTIMATE SELF)**
> - [Deployed behind a feature flag](../../../feature_flags.md), disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](../../../../api/merge_request_approvals.md#enable-or-disable-external-project-level-mr-approvals). **(ULTIMATE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
When you create an external approval rule, the following merge request actions sends information
about a merge request to a third party service:
You can create an external approval rule to integrate approvals with third-party tools.
When users create, change, or close merge requests, GitLab sends a notification.
The users of the third-party tools can then approve merge requests from outside of GitLab.
- Create
- Change
- Close
With this integration, you can integrate with third-party workflow tools, like
[ServiceNow](https://www.servicenow.co.uk/), or the custom tool of your choice.
You can modify your external approval rules
[by using the REST API](../../../../api/merge_request_approvals.md#external-project-level-mr-approvals).
This action enables use-cases such as:
The lack of an external approval doesn't block the merging of a merge request.
- Integration with 3rd party workflow tools, such as [ServiceNow](https://www.servicenow.co.uk/).
- Integration with custom tools designed to approve merge requests from outside of GitLab.
To learn more about use cases, feature discovery, and development timelines,
see the [External API approval rules epic](https://gitlab.com/groups/gitlab-org/-/epics/3869).
You can find more information about use-cases, development timelines and the feature discovery in
the [External API approval rules epic](https://gitlab.com/groups/gitlab-org/-/epics/3869).
## Related links
The intention for this feature is to allow those 3rd party tools to approve a merge request similarly to how users current do.
NOTE:
The lack of an external approval does not block the merging of a merge request.
You can modify external approval rules through the [REST API](../../../../api/merge_request_approvals.md#external-project-level-mr-approvals).
## Required Approvals **(PREMIUM)**
> - [Introduced](https://about.gitlab.com/releases/2015/06/22/gitlab-7-12-released/#merge-request-approvers-ee-only) in GitLab Enterprise Edition 7.12.
> - Moved to GitLab Premium in 13.9.
Required approvals enable enforced code review by requiring specified people
to approve a merge request before it can be merged.
Required approvals enable multiple use cases:
- Enforcing review of all code that gets merged into a repository.
- Specifying reviewers for a given proposed code change, as well as a minimum number
of reviewers, through [Approval rules](rules.md).
- Specifying categories of reviewers, such as backend, frontend, quality assurance,
database, and so on, for all proposed code changes.
- Designating [Code Owners as eligible approvers](rules.md#code-owners-as-eligible-approvers),
determined by the files changed in a merge request.
- [Requiring approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests)
before merging code that could introduce a vulnerability.**(ULTIMATE)**
### Adding or removing an approval
When an [eligible approver](rules.md#eligible-approvers) visits an open merge request,
one of the following is possible:
- If the required number of approvals has _not_ been yet met, they can approve
it by clicking the displayed **Approve** button.
![Approve](img/approve.png)
- If the required number of approvals has already been met, they can still
approve it by clicking the displayed **Approve additionally** button.
![Add approval](img/approve_additionally.png)
- **They have already approved this merge request**: They can remove their approval.
![Remove approval](img/remove_approval.png)
When [approval rule overrides](settings.md#prevent-overriding-default-approvals) are allowed,
changes to default approval rules will **not** be applied to existing
merge requests, except for changes to the [target branch](rules.md#scoped-to-protected-branch)
of the rule.
NOTE:
The merge request author is not allowed to approve their own merge request if
[**Prevent author approval**](settings.md#allowing-merge-request-authors-to-approve-their-own-merge-requests)
is enabled in the project settings.
After the approval rules have been met, the merge request can be merged if there is nothing
else blocking it. Note that the merge request could still be blocked by other conditions,
such as merge conflicts, [pending discussions](../../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved),
or a [failed CI/CD pipeline](../merge_when_pipeline_succeeds.md).
- [Merge request approvals API](../../../../api/merge_request_approvals.md)
- [Instance-level approval rules](../../../admin_area/merge_requests_approvals.md) for self-managed installations
<!-- ## Troubleshooting

View File

@ -18,6 +18,10 @@ module API
result_hash[:read_repository] = scopes.include?('read_repository')
result_hash
end
params :filter_params do
optional :active, type: Boolean, desc: 'Limit by active status'
end
end
desc 'Return all deploy tokens' do
@ -26,11 +30,18 @@ module API
end
params do
use :pagination
use :filter_params
end
get 'deploy_tokens' do
authenticated_as_admin!
present paginate(DeployToken.all), with: Entities::DeployToken
deploy_tokens = ::DeployTokens::TokensFinder.new(
current_user,
:all,
declared_params
).execute
present paginate(deploy_tokens), with: Entities::DeployToken
end
params do
@ -39,6 +50,7 @@ module API
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
use :pagination
use :filter_params
end
desc 'List deploy tokens for a project' do
detail 'This feature was introduced in GitLab 12.9'
@ -47,7 +59,13 @@ module API
get ':id/deploy_tokens' do
authorize!(:read_deploy_token, user_project)
present paginate(user_project.deploy_tokens), with: Entities::DeployToken
deploy_tokens = ::DeployTokens::TokensFinder.new(
current_user,
user_project,
declared_params
).execute
present paginate(deploy_tokens), with: Entities::DeployToken
end
params do
@ -98,6 +116,7 @@ module API
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
use :pagination
use :filter_params
end
desc 'List deploy tokens for a group' do
detail 'This feature was introduced in GitLab 12.9'
@ -106,7 +125,13 @@ module API
get ':id/deploy_tokens' do
authorize!(:read_deploy_token, user_group)
present paginate(user_group.deploy_tokens), with: Entities::DeployToken
deploy_tokens = ::DeployTokens::TokensFinder.new(
current_user,
user_group,
declared_params
).execute
present paginate(deploy_tokens), with: Entities::DeployToken
end
params do

View File

@ -4,7 +4,8 @@ module API
module Entities
class DeployToken < Grape::Entity
# exposing :token is a security risk and should be avoided
expose :id, :name, :username, :expires_at, :scopes
expose :id, :name, :username, :expires_at, :scopes, :revoked
expose :expired?, as: :expired
end
end
end

View File

@ -565,7 +565,7 @@ module Gitlab
check_trigger_permissions!(table)
remove_rename_triggers_for_postgresql(table, trigger_name)
remove_rename_triggers(table, trigger_name)
remove_column(table, new)
end
@ -576,8 +576,19 @@ module Gitlab
# table - The name of the table to install the trigger in.
# old_column - The name of the old column.
# new_column - The name of the new column.
def install_rename_triggers(table, old_column, new_column)
install_rename_triggers_for_postgresql(table, old_column, new_column)
# trigger_name - The name of the trigger to use (optional).
def install_rename_triggers(table, old, new, trigger_name: nil)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).create(old, new, trigger_name: trigger_name)
end
# Removes the triggers used for renaming a column concurrently.
def remove_rename_triggers(table, trigger)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).drop(trigger)
end
# Returns the (base) name to use for triggers when renaming columns.
def rename_trigger_name(table, old, new)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).name(old, new)
end
# Changes the type of a column concurrently.
@ -690,7 +701,7 @@ module Gitlab
check_trigger_permissions!(table)
remove_rename_triggers_for_postgresql(table, trigger_name)
remove_rename_triggers(table, trigger_name)
remove_column(table, old)
end
@ -982,7 +993,7 @@ module Gitlab
temporary_columns = columns.map { |column| convert_to_bigint_column(column) }
trigger_name = rename_trigger_name(table, columns, temporary_columns)
remove_rename_triggers_for_postgresql(table, trigger_name)
remove_rename_triggers(table, trigger_name)
temporary_columns.each { |column| remove_column(table, column) }
end
@ -1079,21 +1090,6 @@ module Gitlab
execute("DELETE FROM batched_background_migrations WHERE #{conditions}")
end
# Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(table, old, new, trigger_name: nil)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).create(old, new, trigger_name: trigger_name)
end
# Removes the triggers used for renaming a PostgreSQL column concurrently.
def remove_rename_triggers_for_postgresql(table, trigger)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).drop(trigger)
end
# Returns the (base) name to use for triggers when renaming columns.
def rename_trigger_name(table, old, new)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).name(old, new)
end
# Returns an Array containing the indexes for the given column
def indexes_for(table, column)
column = column.to_s

View File

@ -70,6 +70,8 @@ module Gitlab
message = base_message(payload)
payload['database_chosen'] = job[:database_chosen] if job[:database_chosen]
if job_exception
payload['message'] = "#{message}: fail: #{payload['duration_s']} sec"
payload['job_status'] = 'fail'

View File

@ -53,17 +53,14 @@
category: search
redis_slot: search
aggregation: weekly
feature_flag: search_track_unique_users
- name: i_search_advanced
category: search
redis_slot: search
aggregation: weekly
feature_flag: search_track_unique_users
- name: i_search_paid
category: search
redis_slot: search
aggregation: weekly
feature_flag: search_track_unique_users
- name: wiki_action
category: source_code
aggregation: daily

View File

@ -0,0 +1,217 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
class OperationsMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
return false unless context.project.feature_available?(:operations, context.current_user)
add_item(metrics_dashboard_menu_item)
add_item(logs_menu_item)
add_item(tracing_menu_item)
add_item(error_tracking_menu_item)
add_item(alert_management_menu_item)
add_item(incidents_menu_item)
add_item(serverless_menu_item)
add_item(terraform_menu_item)
add_item(kubernetes_menu_item)
add_item(environments_menu_item)
add_item(feature_flags_menu_item)
add_item(product_analytics_menu_item)
true
end
override :link
def link
if can?(context.current_user, :read_environment, context.project)
metrics_project_environments_path(context.project)
else
project_feature_flags_path(context.project)
end
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-operations'
}
end
override :title
def title
_('Operations')
end
override :sprite_icon
def sprite_icon
'cloud-gear'
end
override :active_routes
def active_routes
{ controller: [:user, :gcp] }
end
private
def metrics_dashboard_menu_item
return unless can?(context.current_user, :metrics_dashboard, context.project)
::Sidebars::MenuItem.new(
title: _('Metrics'),
link: project_metrics_dashboard_path(context.project),
active_routes: { path: 'metrics_dashboard#show' },
container_html_options: { class: 'shortcuts-metrics' },
item_id: :metrics
)
end
def logs_menu_item
return unless can?(context.current_user, :read_environment, context.project)
return unless can?(context.current_user, :read_pod_logs, context.project)
::Sidebars::MenuItem.new(
title: _('Logs'),
link: project_logs_path(context.project),
active_routes: { path: 'logs#index' },
item_id: :logs
)
end
def tracing_menu_item
return unless can?(context.current_user, :read_environment, context.project)
return unless can?(context.current_user, :admin_project, context.project)
::Sidebars::MenuItem.new(
title: _('Tracing'),
link: project_tracing_path(context.project),
active_routes: { path: 'tracings#show' },
item_id: :tracing
)
end
def error_tracking_menu_item
return unless can?(context.current_user, :read_sentry_issue, context.project)
::Sidebars::MenuItem.new(
title: _('Error Tracking'),
link: project_error_tracking_index_path(context.project),
active_routes: { controller: :error_tracking },
item_id: :error_tracking
)
end
def alert_management_menu_item
return unless can?(context.current_user, :read_alert_management_alert, context.project)
::Sidebars::MenuItem.new(
title: _('Alerts'),
link: project_alert_management_index_path(context.project),
active_routes: { controller: :alert_management },
item_id: :alert_management
)
end
def incidents_menu_item
return unless can?(context.current_user, :read_issue, context.project)
::Sidebars::MenuItem.new(
title: _('Incidents'),
link: project_incidents_path(context.project),
active_routes: { controller: [:incidents, :incident_management] },
item_id: :incidents
)
end
def serverless_menu_item
return unless can?(context.current_user, :read_cluster, context.project)
::Sidebars::MenuItem.new(
title: _('Serverless'),
link: project_serverless_functions_path(context.project),
active_routes: { controller: :functions },
item_id: :serverless
)
end
def terraform_menu_item
return unless can?(context.current_user, :read_terraform_state, context.project)
::Sidebars::MenuItem.new(
title: _('Terraform'),
link: project_terraform_index_path(context.project),
active_routes: { controller: :terraform },
item_id: :terraform
)
end
def kubernetes_menu_item
return unless can?(context.current_user, :read_cluster, context.project)
::Sidebars::MenuItem.new(
title: _('Kubernetes'),
link: project_clusters_path(context.project),
active_routes: { controller: [:cluster_agents, :clusters] },
container_html_options: { class: 'shortcuts-kubernetes' },
hint_html_options: kubernetes_hint_html_options,
item_id: :kubernetes
)
end
def kubernetes_hint_html_options
return {} unless context.show_cluster_hint
{ disabled: true,
data: { trigger: 'manual',
container: 'body',
placement: 'right',
highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: user_callouts_path,
auto_devops_help_path: help_page_path('topics/autodevops/index.md') } }
end
def environments_menu_item
return unless can?(context.current_user, :read_environment, context.project)
::Sidebars::MenuItem.new(
title: _('Environments'),
link: project_environments_path(context.project),
active_routes: { controller: :environments },
container_html_options: { class: 'shortcuts-environments' },
item_id: :environments
)
end
def feature_flags_menu_item
return unless can?(context.current_user, :read_feature_flag, context.project)
::Sidebars::MenuItem.new(
title: _('Feature Flags'),
link: project_feature_flags_path(context.project),
active_routes: { controller: :feature_flags },
container_html_options: { class: 'shortcuts-feature-flags' },
item_id: :feature_flags
)
end
def product_analytics_menu_item
return if Feature.disabled?(:product_analytics, context.project)
return unless can?(context.current_user, :read_product_analytics, context.project)
::Sidebars::MenuItem.new(
title: _('Product Analytics'),
link: project_product_analytics_path(context.project),
active_routes: { controller: :product_analytics },
item_id: :product_analytics
)
end
end
end
end
end
Sidebars::Projects::Menus::OperationsMenu.prepend_if_ee('EE::Sidebars::Projects::Menus::OperationsMenu')

View File

@ -16,6 +16,7 @@ module Sidebars
add_menu(Sidebars::Projects::Menus::MergeRequestsMenu.new(context))
add_menu(Sidebars::Projects::Menus::CiCdMenu.new(context))
add_menu(Sidebars::Projects::Menus::SecurityComplianceMenu.new(context))
add_menu(Sidebars::Projects::Menus::OperationsMenu.new(context))
end
override :render_raw_menus_partial

View File

@ -22571,6 +22571,12 @@ msgstr ""
msgid "OnCallSchedules|The schedule could not be updated. Please try again."
msgstr ""
msgid "OnCallSchedules|To create an escalation policy that defines which schedule is used when, visit the %{linkStart}escalation policy%{linkEnd} page."
msgstr ""
msgid "OnCallSchedules|To create an escalation policy using this schedule, visit the %{linkStart}escalation policy%{linkEnd} page."
msgstr ""
msgid "OnCallSchedules|Try adding a rotation"
msgstr ""
@ -22586,7 +22592,7 @@ msgstr ""
msgid "OnCallSchedules|You are currently a part of:"
msgstr ""
msgid "OnCallSchedules|Your schedule has been successfully created and all alerts from this project will now be routed to this schedule. Currently, only one schedule can be created per project. More coming soon! To add individual users to this schedule, use the add a rotation button."
msgid "OnCallSchedules|Your schedule has been successfully created. To add individual users to this schedule, use the add a rotation button."
msgstr ""
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."

View File

@ -12,20 +12,13 @@ module QA
base.class_eval do
include QA::Page::Project::SubMenus::Common
view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do
element :operations_link
element :operations_environments_link
element :operations_metrics_link
element :operations_incidents_link
end
end
end
def go_to_operations_environments
hover_operations do
within_submenu do
click_element(:operations_environments_link)
click_element(:sidebar_menu_item_link, menu_item: 'Environments')
end
end
end
@ -33,7 +26,7 @@ module QA
def go_to_operations_metrics
hover_operations do
within_submenu do
click_element(:operations_metrics_link)
click_element(:sidebar_menu_item_link, menu_item: 'Metrics')
end
end
end
@ -49,7 +42,7 @@ module QA
def go_to_operations_incidents
hover_operations do
within_submenu do
click_element(:operations_incidents_link)
click_element(:sidebar_menu_item_link, menu_item: 'Incidents')
end
end
end
@ -58,8 +51,8 @@ module QA
def hover_operations
within_sidebar do
scroll_to_element(:operations_link)
find_element(:operations_link).hover
scroll_to_element(:sidebar_menu_link, menu_item: 'Operations')
find_element(:sidebar_menu_link, menu_item: 'Operations').hover
yield
end

View File

@ -59,6 +59,8 @@ RSpec.describe Groups::Settings::RepositoryController do
'username' => deploy_token_params[:username],
'expires_at' => Time.zone.parse(deploy_token_params[:expires_at]),
'token' => be_a(String),
'expired' => false,
'revoked' => false,
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
key, value = kv
key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes

View File

@ -78,6 +78,8 @@ RSpec.describe Projects::Settings::RepositoryController do
'username' => deploy_token_params[:username],
'expires_at' => Time.zone.parse(deploy_token_params[:expires_at]),
'token' => be_a(String),
'expired' => false,
'revoked' => false,
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
key, value = kv
key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes

View File

@ -190,6 +190,7 @@ RSpec.describe 'Issue Sidebar' do
end
find('.js-right-sidebar').click
wait_for_requests
open_assignees_dropdown

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
let_it_be_with_reload(:project) { create(:project, :internal, :repository) }
let(:user) { create(:user) }
let(:access_level) { ProjectFeature::PUBLIC }
let(:role) { nil }
@ -37,16 +38,16 @@ RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
context 'user is not a member' do
it 'has the correct `Operations` menu items', :aggregate_failures do
expect(page).to have_selector('a.shortcuts-operations', text: 'Operations')
expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).not_to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
expect(page).not_to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Product Analytics', href: project_product_analytics_path(project))
expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link('Logs', href: project_logs_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
end
context 'when operations project feature is PRIVATE' do
@ -71,16 +72,16 @@ RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
it 'has the correct `Operations` menu items' do
expect(page).to have_selector('a.shortcuts-operations', text: 'Operations')
expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).not_to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
expect(page).not_to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Product Analytics', href: project_product_analytics_path(project))
expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link('Logs', href: project_logs_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
end
it_behaves_like 'shows Operations menu based on the access level'
@ -90,16 +91,16 @@ RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
let(:role) { :reporter }
it 'has the correct `Operations` menu items' do
expect(page).to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link('Product Analytics', href: project_product_analytics_path(project))
expect(page).not_to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link('Logs', href: project_logs_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
end
it_behaves_like 'shows Operations menu based on the access level'
@ -109,16 +110,16 @@ RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
let(:role) { :developer }
it 'has the correct `Operations` menu items' do
expect(page).to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
expect(page).to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link('Product Analytics', href: project_product_analytics_path(project))
expect(page).to have_link('Logs', href: project_logs_path(project))
expect(page).not_to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link(title: 'Kubernetes', href: project_clusters_path(project))
expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
end
it_behaves_like 'shows Operations menu based on the access level'
@ -128,15 +129,15 @@ RSpec.describe 'Operations dropdown sidebar', :aggregate_failures do
let(:role) { :maintainer }
it 'has the correct `Operations` menu items' do
expect(page).to have_link(title: 'Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link(title: 'Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link(title: 'Incidents', href: project_incidents_path(project))
expect(page).to have_link(title: 'Environments', href: project_environments_path(project))
expect(page).to have_link(title: 'Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link(title: 'Product Analytics', href: project_product_analytics_path(project))
expect(page).to have_link(title: 'Serverless', href: project_serverless_functions_path(project))
expect(page).to have_link(title: 'Logs', href: project_logs_path(project))
expect(page).to have_link(title: 'Kubernetes', href: project_clusters_path(project))
expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link('Product Analytics', href: project_product_analytics_path(project))
expect(page).to have_link('Serverless', href: project_serverless_functions_path(project))
expect(page).to have_link('Logs', href: project_logs_path(project))
expect(page).to have_link('Kubernetes', href: project_clusters_path(project))
end
it_behaves_like 'shows Operations menu based on the access level'

View File

@ -0,0 +1,135 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DeployTokens::TokensFinder do
include AdminModeHelper
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:project) { create(:project, creator_id: user.id) }
let_it_be(:group) { create(:group) }
let!(:project_deploy_token) { create(:deploy_token, projects: [project]) }
let!(:revoked_project_deploy_token) { create(:deploy_token, projects: [project], revoked: true) }
let!(:expired_project_deploy_token) { create(:deploy_token, projects: [project], expires_at: '1988-01-11T04:33:04-0600') }
let!(:group_deploy_token) { create(:deploy_token, :group, groups: [group]) }
let!(:revoked_group_deploy_token) { create(:deploy_token, :group, groups: [group], revoked: true) }
let!(:expired_group_deploy_token) { create(:deploy_token, :group, groups: [group], expires_at: '1988-01-11T04:33:04-0600') }
describe "#execute" do
let(:params) { {} }
context 'when scope is :all' do
subject { described_class.new(admin, :all, params).execute }
before do
enable_admin_mode!(admin)
end
it 'returns all deploy tokens' do
expect(subject.size).to eq(6)
is_expected.to match_array([
project_deploy_token,
revoked_project_deploy_token,
expired_project_deploy_token,
group_deploy_token,
revoked_group_deploy_token,
expired_group_deploy_token
])
end
context 'and active filter is applied' do
let(:params) { { active: true } }
it 'returns only active tokens' do
is_expected.to match_array([
project_deploy_token,
group_deploy_token
])
end
end
context 'but user is not an admin' do
subject { described_class.new(user, :all, params).execute }
it 'raises Gitlab::Access::AccessDeniedError' do
expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
context 'when scope is a Project' do
subject { described_class.new(user, project, params).execute }
before do
project.add_maintainer(user)
end
it 'returns all deploy tokens for the project' do
is_expected.to match_array([
project_deploy_token,
revoked_project_deploy_token,
expired_project_deploy_token
])
end
context 'and active filter is applied' do
let(:params) { { active: true } }
it 'returns only active tokens for the project' do
is_expected.to match_array([project_deploy_token])
end
end
context 'but user is not a member' do
subject { described_class.new(other_user, :all, params).execute }
it 'raises Gitlab::Access::AccessDeniedError' do
expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
context 'when scope is a Group' do
subject { described_class.new(user, group, params).execute }
before do
group.add_maintainer(user)
end
it 'returns all deploy tokens for the group' do
is_expected.to match_array([
group_deploy_token,
revoked_group_deploy_token,
expired_group_deploy_token
])
end
context 'and active filter is applied' do
let(:params) { { active: true } }
it 'returns only active tokens for the group' do
is_expected.to match_array([group_deploy_token])
end
end
context 'but user is not a member' do
subject { described_class.new(other_user, :all, params).execute }
it 'raises Gitlab::Access::AccessDeniedError' do
expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
context 'when scope is nil' do
subject { described_class.new(user, nil, params).execute }
it 'raises ArgumentError' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
end

View File

@ -1,4 +1,5 @@
import { GlAlert, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui';
import { GlAlert, GlButton, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@ -18,6 +19,24 @@ import {
const localVue = createLocalVue();
localVue.use(VueApollo);
let resizeCallback;
const MockResizeObserver = {
bind(el, { value }) {
resizeCallback = value;
},
mockResize(size) {
bp.getBreakpointSize.mockReturnValue(size);
resizeCallback();
},
unbind() {
resizeCallback = null;
},
};
localVue.directive('gl-resize-observer', MockResizeObserver);
jest.mock('@gitlab/ui/dist/utils');
describe('RunnerInstructionsModal component', () => {
let wrapper;
let fakeApollo;
@ -27,7 +46,8 @@ describe('RunnerInstructionsModal component', () => {
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(GlAlert);
const findPlatformButtons = () => wrapper.findAllByTestId('platform-button');
const findPlatformButtonGroup = () => wrapper.findByTestId('platform-buttons');
const findPlatformButtons = () => findPlatformButtonGroup().findAllComponents(GlButton);
const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item');
const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions');
const findRegisterCommand = () => wrapper.findByTestId('register-command');
@ -141,6 +161,22 @@ describe('RunnerInstructionsModal component', () => {
});
});
describe('when the modal resizes', () => {
it('to an xs viewport', async () => {
MockResizeObserver.mockResize('xs');
await nextTick();
expect(findPlatformButtonGroup().attributes('vertical')).toBeTruthy();
});
it('to a non-xs viewport', async () => {
MockResizeObserver.mockResize('sm');
await nextTick();
expect(findPlatformButtonGroup().props('vertical')).toBeFalsy();
});
});
describe('when apollo is loading', () => {
it('should show a skeleton loader', async () => {
createComponent();

View File

@ -477,43 +477,6 @@ RSpec.describe ProjectsHelper do
end
end
describe '#can_view_operations_tab?' do
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).and_return(false)
end
subject { helper.send(:can_view_operations_tab?, user, project) }
where(:ability) do
[
:metrics_dashboard,
:read_alert_management_alert,
:read_environment,
:read_issue,
:read_sentry_issue,
:read_cluster
]
end
with_them do
it 'includes operations tab' do
allow(helper).to receive(:can?).with(user, ability, project).and_return(true)
is_expected.to be(true)
end
context 'when operations feature is disabled' do
it 'does not include operations tab' do
allow(helper).to receive(:can?).with(user, ability, project).and_return(true)
project.project_feature.update_attribute(:operations_access_level, ProjectFeature::DISABLED)
is_expected.to be(false)
end
end
end
end
describe '#show_projects' do
let(:projects) do
Project.all

View File

@ -835,7 +835,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'renames a column concurrently' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers_for_postgresql)
expect(model).to receive(:install_rename_triggers)
.with(:users, :old, :new)
expect(model).to receive(:add_column)
@ -947,7 +947,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'reverses the operations of rename_column_concurrently' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:remove_rename_triggers_for_postgresql)
expect(model).to receive(:remove_rename_triggers)
.with(:users, /trigger_.{12}/)
expect(model).to receive(:remove_column).with(:users, :new)
@ -960,7 +960,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'cleans up the renaming procedure' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:remove_rename_triggers_for_postgresql)
expect(model).to receive(:remove_rename_triggers)
.with(:users, /trigger_.{12}/)
expect(model).to receive(:remove_column).with(:users, :old)
@ -1000,7 +1000,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'reverses the operations of cleanup_concurrent_column_rename' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers_for_postgresql)
expect(model).to receive(:install_rename_triggers)
.with(:users, :old, :new)
expect(model).to receive(:add_column)
@ -1095,7 +1095,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'reverses the operations of change_column_type_concurrently' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:remove_rename_triggers_for_postgresql)
expect(model).to receive(:remove_rename_triggers)
.with(:users, /trigger_.{12}/)
expect(model).to receive(:remove_column).with(:users, "old_for_type_change")
@ -1160,7 +1160,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:rename_column)
.with(:users, temp_undo_cleanup_column, :old)
expect(model).to receive(:install_rename_triggers_for_postgresql)
expect(model).to receive(:install_rename_triggers)
.with(:users, :old, 'old_for_type_change')
model.undo_cleanup_concurrent_column_type_change(:users, :old, :string)
@ -1186,7 +1186,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:rename_column)
.with(:users, temp_undo_cleanup_column, :old)
expect(model).to receive(:install_rename_triggers_for_postgresql)
expect(model).to receive(:install_rename_triggers)
.with(:users, :old, 'old_for_type_change')
model.undo_cleanup_concurrent_column_type_change(
@ -1207,8 +1207,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
describe '#install_rename_triggers_for_postgresql' do
it 'installs the triggers for PostgreSQL' do
describe '#install_rename_triggers' do
it 'installs the triggers' do
copy_trigger = double('copy trigger')
expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
@ -1216,11 +1216,11 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(copy_trigger).to receive(:create).with(:old, :new, trigger_name: 'foo')
model.install_rename_triggers_for_postgresql(:users, :old, :new, trigger_name: 'foo')
model.install_rename_triggers(:users, :old, :new, trigger_name: 'foo')
end
end
describe '#remove_rename_triggers_for_postgresql' do
describe '#remove_rename_triggers' do
it 'removes the function and trigger' do
copy_trigger = double('copy trigger')
@ -1229,7 +1229,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(copy_trigger).to receive(:drop).with('foo')
model.remove_rename_triggers_for_postgresql('bar', 'foo')
model.remove_rename_triggers('bar', 'foo')
end
end

View File

@ -0,0 +1,205 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::OperationsMenu do
let_it_be_with_refind(:project) { create(:project) }
let(:user) { project.owner }
let(:show_cluster_hint) { true }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, show_cluster_hint: show_cluster_hint) }
subject { described_class.new(context) }
describe '#render?' do
context 'when operations feature is disabled' do
it 'returns false' do
project.project_feature.update!(operations_access_level: Featurable::DISABLED)
expect(subject.render?).to be false
end
end
context 'when operation feature is enabled' do
context 'when menu does not have any menu items' do
it 'returns false' do
allow(subject).to receive(:has_items?).and_return(false)
expect(subject.render?).to be false
end
end
context 'when menu has menu items' do
it 'returns true' do
expect(subject.render?).to be true
end
end
end
end
describe '#link' do
context 'when metrics dashboard is visible' do
it 'returns link to the metrics dashboard page' do
expect(subject.link).to include('/-/environments/metrics')
end
end
context 'when metrics dashboard is not visible' do
it 'returns link to the feature flags page' do
project.project_feature.update!(operations_access_level: Featurable::DISABLED)
expect(subject.link).to include('/-/feature_flags')
end
end
end
context 'Menu items' do
subject { described_class.new(context).items.index { |e| e.item_id == item_id } }
describe 'Metrics Dashboard' do
let(:item_id) { :metrics }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Logs' do
let(:item_id) { :logs }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Tracing' do
let(:item_id) { :tracing }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Error Tracking' do
let(:item_id) { :error_tracking }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Alert Management' do
let(:item_id) { :alert_management }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Incidents' do
let(:item_id) { :incidents }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Serverless' do
let(:item_id) { :serverless }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Terraform' do
let(:item_id) { :terraform }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Kubernetes' do
let(:item_id) { :kubernetes }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Environments' do
let(:item_id) { :environments }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Feature Flags' do
let(:item_id) { :feature_flags }
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Product Analytics' do
let(:item_id) { :product_analytics }
specify { is_expected.not_to be_nil }
describe 'when feature flag :product_analytics is disabled' do
specify do
stub_feature_flags(product_analytics: false)
is_expected.to be_nil
end
end
end
end
end

View File

@ -8,7 +8,11 @@ RSpec.describe API::DeployTokens do
let_it_be(:project) { create(:project, creator_id: creator.id) }
let_it_be(:group) { create(:group) }
let!(:deploy_token) { create(:deploy_token, projects: [project]) }
let!(:revoked_deploy_token) { create(:deploy_token, projects: [project], revoked: true) }
let!(:expired_deploy_token) { create(:deploy_token, projects: [project], expires_at: '1988-01-11T04:33:04-0600') }
let!(:group_deploy_token) { create(:deploy_token, :group, groups: [group]) }
let!(:revoked_group_deploy_token) { create(:deploy_token, :group, groups: [group], revoked: true) }
let!(:expired_group_deploy_token) { create(:deploy_token, :group, groups: [group], expires_at: '1988-01-11T04:33:04-0600') }
describe 'GET /deploy_tokens' do
subject do
@ -36,8 +40,31 @@ RSpec.describe API::DeployTokens do
it 'returns all deploy tokens' do
subject
token_ids = json_response.map { |token| token['id'] }
expect(response).to include_pagination_headers
expect(response).to match_response_schema('public_api/v4/deploy_tokens')
expect(token_ids).to match_array([
deploy_token.id,
revoked_deploy_token.id,
expired_deploy_token.id,
group_deploy_token.id,
revoked_group_deploy_token.id,
expired_group_deploy_token.id
])
end
context 'and active=true' do
it 'only returns active deploy tokens' do
get api('/deploy_tokens?active=true', user)
token_ids = json_response.map { |token| token['id'] }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(token_ids).to match_array([
deploy_token.id,
group_deploy_token.id
])
end
end
end
end
@ -82,7 +109,22 @@ RSpec.describe API::DeployTokens do
subject
token_ids = json_response.map { |token| token['id'] }
expect(token_ids).not_to include(other_deploy_token.id)
expect(token_ids).to match_array([
deploy_token.id,
expired_deploy_token.id,
revoked_deploy_token.id
])
end
context 'and active=true' do
it 'only returns active deploy tokens for the project' do
get api("/projects/#{project.id}/deploy_tokens?active=true", user)
token_ids = json_response.map { |token| token['id'] }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(token_ids).to match_array([deploy_token.id])
end
end
end
end
@ -119,8 +161,10 @@ RSpec.describe API::DeployTokens do
it 'returns all deploy tokens for the group' do
subject
token_ids = json_response.map { |token| token['id'] }
expect(response).to include_pagination_headers
expect(response).to match_response_schema('public_api/v4/deploy_tokens')
expect(token_ids.length).to be(3)
end
it 'does not return deploy tokens for other groups' do
@ -129,6 +173,17 @@ RSpec.describe API::DeployTokens do
token_ids = json_response.map { |token| token['id'] }
expect(token_ids).not_to include(other_deploy_token.id)
end
context 'and active=true' do
it 'only returns active deploy tokens for the group' do
get api("/groups/#{group.id}/deploy_tokens?active=true", user)
token_ids = json_response.map { |token| token['id'] }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(token_ids).to eql([group_deploy_token.id])
end
end
end
end

View File

@ -348,6 +348,238 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
end
describe 'Operations' do
it 'top level navigation link is visible for user with permissions' do
render
expect(rendered).to have_link('Operations')
end
describe 'Metrics Dashboard' do
it 'has a link to the metrics dashboard page' do
render
expect(rendered).to have_link('Metrics', href: project_metrics_dashboard_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the metrics page' do
render
expect(rendered).not_to have_link('Metrics')
end
end
end
describe 'Logs' do
it 'has a link to the pod logs page' do
render
expect(rendered).to have_link('Logs', href: project_logs_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the pod logs page' do
render
expect(rendered).not_to have_link('Logs')
end
end
end
describe 'Tracing' do
it 'has a link to the tracing page' do
render
expect(rendered).to have_link('Tracing', href: project_tracing_path(project))
end
context 'without project.tracing_external_url' do
it 'has a link to the tracing page' do
render
expect(rendered).to have_link('Tracing', href: project_tracing_path(project))
end
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the tracing page' do
render
expect(rendered).not_to have_text 'Tracing'
end
end
end
describe 'Error Tracking' do
it 'has a link to the error tracking page' do
render
expect(rendered).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the error tracking page' do
render
expect(rendered).not_to have_link('Error Tracking')
end
end
end
describe 'Alert Management' do
it 'has a link to the alert management page' do
render
expect(rendered).to have_link('Alerts', href: project_alert_management_index_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the alert management page' do
render
expect(rendered).not_to have_link('Alerts')
end
end
end
describe 'Incidents' do
it 'has a link to the incidents page' do
render
expect(rendered).to have_link('Incidents', href: project_incidents_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the incidents page' do
render
expect(rendered).not_to have_link('Incidents')
end
end
end
describe 'Serverless' do
it 'has a link to the serverless page' do
render
expect(rendered).to have_link('Serverless', href: project_serverless_functions_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the serverless page' do
render
expect(rendered).not_to have_link('Serverless')
end
end
end
describe 'Terraform' do
it 'has a link to the terraform page' do
render
expect(rendered).to have_link('Terraform', href: project_terraform_index_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the terraform page' do
render
expect(rendered).not_to have_link('Terraform')
end
end
end
describe 'Kubernetes' do
it 'has a link to the kubernetes page' do
render
expect(rendered).to have_link('Kubernetes', href: project_clusters_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the kubernetes page' do
render
expect(rendered).not_to have_link('Kubernetes')
end
end
end
describe 'Environments' do
it 'has a link to the environments page' do
render
expect(rendered).to have_link('Environments', href: project_environments_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the environments page' do
render
expect(rendered).not_to have_link('Environments')
end
end
end
describe 'Feature Flags' do
it 'has a link to the feature flags page' do
render
expect(rendered).to have_link('Feature Flags', href: project_feature_flags_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the feature flags page' do
render
expect(rendered).not_to have_link('Feature Flags')
end
end
end
describe 'Product Analytics' do
it 'has a link to the product analytics page' do
render
expect(rendered).to have_link('Product Analytics', href: project_product_analytics_path(project))
end
describe 'when feature flag :product_analytics is disabled' do
it 'does not have a link to the feature flags page' do
stub_feature_flags(product_analytics: false)
render
expect(rendered).not_to have_link('Product Analytics')
end
end
end
end
describe 'packages tab' do
before do
stub_container_registry_config(enabled: true)
@ -510,6 +742,32 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
end
describe 'value stream analytics entry' do
let(:read_cycle_analytics) { true }
before do
allow(view).to receive(:can?).with(user, :read_cycle_analytics, project).and_return(read_cycle_analytics)
end
describe 'when value stream analytics is enabled' do
it 'shows the value stream analytics entry' do
render
expect(rendered).to have_link('Value Stream', href: project_cycle_analytics_path(project))
end
end
describe 'when value stream analytics is disabled' do
let(:read_cycle_analytics) { false }
it 'does not show the value stream analytics entry' do
render
expect(rendered).not_to have_link('Value Stream', href: project_cycle_analytics_path(project))
end
end
end
describe 'operations settings tab' do
describe 'archive projects' do
before do
@ -536,64 +794,6 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
end
end
describe 'Tracing' do
it 'is not visible to unauthorized user' do
allow(view).to receive(:can?).and_return(false)
render
expect(rendered).not_to have_text 'Tracing'
end
it 'links to Tracing page' do
render
expect(rendered).to have_link('Tracing', href: project_tracing_path(project))
end
context 'without project.tracing_external_url' do
it 'links to Tracing page' do
render
expect(rendered).to have_link('Tracing', href: project_tracing_path(project))
end
end
end
describe 'Alert Management' do
it 'shows the Alerts sidebar entry' do
render
expect(rendered).to have_css('a[title="Alerts"]')
end
end
end
describe 'value stream analytics entry' do
let(:read_cycle_analytics) { true }
before do
allow(view).to receive(:can?).with(user, :read_cycle_analytics, project).and_return(read_cycle_analytics)
end
describe 'when value stream analytics is enabled' do
it 'shows the value stream analytics entry' do
render
expect(rendered).to have_link('Value Stream', href: project_cycle_analytics_path(project))
end
end
describe 'when value stream analytics is disabled' do
let(:read_cycle_analytics) { false }
it 'does not show the value stream analytics entry' do
render
expect(rendered).not_to have_link('Value Stream', href: project_cycle_analytics_path(project))
end
end
end
describe 'project access tokens' do