Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6d19e491d1
commit
69d28d313c
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove search_track_unique_users feature flag
|
||||
merge_request: 60706
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add "active" filter to deploy tokens API
|
||||
merge_request: 59582
|
||||
author: Devin Christensen
|
||||
type: added
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
- If the required number of approvals has already been met, they can still
|
||||
approve it by clicking the displayed **Approve additionally** button.
|
||||
|
||||

|
||||
|
||||
- **They have already approved this merge request**: They can remove their approval.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ RSpec.describe 'Issue Sidebar' do
|
|||
end
|
||||
|
||||
find('.js-right-sidebar').click
|
||||
wait_for_requests
|
||||
|
||||
open_assignees_dropdown
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue