Commit e10cc93c authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 80cc3c94 5f5ff212
......@@ -188,7 +188,7 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
'gl-py-3 gl-h-full!': !list.isExpanded && !isSwimlanesHeader,
'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"
......
import Vue from 'vue';
import Clusters from './components/clusters.vue';
import { createStore } from './store';
import loadClusters from './load_clusters';
export default () => {
const entryPoint = document.querySelector('#js-clusters-list-app');
if (!entryPoint) {
return;
}
// eslint-disable-next-line no-new
new Vue({
el: '#js-clusters-list-app',
store: createStore(entryPoint.dataset),
render(createElement) {
return createElement(Clusters);
},
});
loadClusters(Vue);
};
import Clusters from './components/clusters.vue';
import { createStore } from './store';
export default Vue => {
const el = document.querySelector('#js-clusters-list-app');
if (!el) {
return null;
}
return new Vue({
el,
store: createStore(el.dataset),
render(createElement) {
return createElement(Clusters);
},
});
};
import initClustersListApp from 'ee_else_ce/clusters_list';
import PersistentUserCallout from '~/persistent_user_callout';
import initClustersListApp from '~/clusters_list';
document.addEventListener('DOMContentLoaded', () => {
const callout = document.querySelector('.gcp-signup-offer');
......
......@@ -12,6 +12,18 @@ module ClustersHelper
end
end
def display_cluster_agents?(_clusterable)
false
end
def js_cluster_agents_list_data(clusterable_project)
{
default_branch_name: clusterable_project.default_branch,
empty_state_image: image_path('illustrations/clusters_empty.svg'),
project_path: clusterable_project.full_path
}
end
def js_clusters_list_data(path = nil)
{
ancestor_help_path: help_page_path('user/group/clusters/index', anchor: 'cluster-precedence'),
......
......@@ -1688,6 +1688,10 @@ class MergeRequest < ApplicationRecord
Feature.enabled?(:merge_request_reviewers, project)
end
def allows_multiple_reviewers?
false
end
private
def with_rebase_lock
......
......@@ -110,6 +110,10 @@ module MergeRequests
return
end
unless merge_request.allows_multiple_reviewers?
params[:reviewer_ids] = params[:reviewer_ids].first(1)
end
reviewer_ids = params[:reviewer_ids].select { |reviewer_id| user_can_read?(merge_request, reviewer_id) }
if params[:reviewer_ids].map(&:to_s) == [IssuableFinder::Params::NONE]
......
- if clusters.empty?
= render 'empty_state'
- else
.top-area.adjust
.gl-display-block.gl-text-right.gl-my-4.gl-w-full
- if clusterable.can_add_cluster?
= link_to s_('ClusterIntegration|Connect cluster with certificate'), clusterable.new_path, class: 'btn gl-button btn-success js-add-cluster gl-py-2', qa_selector: :integrate_kubernetes_cluster_button
- else
%span.btn.gl-button.btn-success.js-add-cluster.disabled.gl-py-2
= s_("ClusterIntegration|Connect cluster with certificate")
#js-clusters-list-app{ data: js_clusters_list_data(clusterable.index_path(format: :json)) }
......@@ -3,12 +3,12 @@
.svg-content= image_tag 'illustrations/clusters_empty.svg'
.col-12
.text-content
%h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation')
%p
%h4.gl-text-center= s_('ClusterIntegration|Integrate Kubernetes with a cluster certificate')
%p.gl-text-center
= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
= clusterable.empty_state_help_text
= clusterable.learn_more_link
- if clusterable.can_add_cluster?
.text-center
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success'
.gl-text-center
= link_to s_('ClusterIntegration|Integrate with a cluster certificate'), clusterable.new_path, class: 'btn btn-success'
......@@ -3,18 +3,24 @@
= render_gcp_signup_offer
.clusters-container
- if @clusters.empty?
= render "empty_state"
- else
.top-area.adjust
.nav-text
= s_('ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project')
.nav-controls
- if clusterable.can_add_cluster?
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn gl-button btn-success js-add-cluster'
- else
%span.btn.gl-button.btn-success.js-add-cluster.disabled
= s_("ClusterIntegration|Add Kubernetes cluster")
.clusters-container.gl-my-2
- if display_cluster_agents?(clusterable)
.js-toggle-container
%ul.nav-links.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
%a.nav-link.active{ href: "#certificate-clusters-pane", id: "certificate-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
%span= s_('ClusterIntegration|Clusters connected with a certificate')
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: "#agent-clusters-pane", id: "agent-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
%span= s_('ClusterIntegration|GitLab Agent managed clusters')
.tab-content
.tab-pane.active{ id: 'certificate-clusters-pane', role: 'tabpanel' }
= render 'cluster_list', clusters: @clusters
#js-clusters-list-app{ data: js_clusters_list_data(clusterable.index_path(format: :json)) }
.tab-pane{ id: 'agent-clusters-pane', role: 'tabpanel' }
#js-cluster-agents-list{ data: js_cluster_agents_list_data(clusterable) }
- else
= render 'cluster_list', clusters: @clusters
- add_to_breadcrumbs _("Releases"), project_releases_path(@project)
- page_title @release.name
- page_description @release.description_html
#js-show-release-page{ data: { project_id: @project.id, tag_name: @release.tag } }
---
title: Add og:description meta tag to individual "Release" page
merge_request: 42889
author:
type: added
---
name: cluster_agent_list
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/228845
rollout_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/3834
group: group::configure
type: development
default_enabled: true
......@@ -480,7 +480,7 @@ indexed](#maximum-file-size-indexed)).
- For self-managed installations it is unlimited by default
This limit can be configured for self-managed installations when [enabling
Elasticsearch](../integration/elasticsearch.md#enabling-elasticsearch).
Elasticsearch](../integration/elasticsearch.md#enabling-advanced-search).
NOTE: **Note:**
Set the limit to `0` to disable it.
......
......@@ -206,7 +206,7 @@ The best place to start is to determine if the issue is with creating an empty i
If it is, check on the Elasticsearch side to determine if the `gitlab-production` (the
name for the GitLab index) exists. If it exists, manually delete it on the Elasticsearch
side and attempt to recreate it from the
[`recreate_index`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks)
[`recreate_index`](../../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks)
Rake task.
If you still encounter issues, try creating an index manually on the Elasticsearch
......@@ -225,8 +225,8 @@ during the indexing of projects. If errors do occur, they will either stem from
If the indexing process does not present errors, you will want to check the status of the indexed projects. You can do this via the following Rake tasks:
- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) (shows the overall status)
- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) (shows specific projects that are not indexed)
- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks) (shows the overall status)
- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks) (shows specific projects that are not indexed)
If:
......
......@@ -330,7 +330,7 @@ Consul is a tool for service discovery and configuration. Consul is distributed,
- [Source](../integration/elasticsearch.md)
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/elasticsearch.md)
- Layer: Core Service (Data)
- GitLab.com: [Get Advanced Global Search working on GitLab.com](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
- GitLab.com: [Get Advanced Search working on GitLab.com](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
Elasticsearch is a distributed RESTful search engine built for the cloud.
......
......@@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
This area is to maintain a compendium of useful information when working with Elasticsearch.
Information on how to enable Elasticsearch and perform the initial indexing is in
the [Elasticsearch integration documentation](../integration/elasticsearch.md#enabling-elasticsearch).
the [Elasticsearch integration documentation](../integration/elasticsearch.md#enabling-advanced-search).
## Deep Dive
......
......@@ -223,6 +223,25 @@ Examples:
sum(JiraImportState.finished, :imported_issues_count)
```
### Grouping & Batch Operations
The `count`, `distinct_count`, and `sum` batch counters can accept an `ActiveRecord::Relation`
object, which groups by a specified column. With a grouped relation, the methods do batch counting,
handle errors, and returns a hash table of key-value pairs.
Examples:
```ruby
count(Namespace.group(:type))
# returns => {nil=>179, "Group"=>54}
distinct_count(Project.group(:visibility_level), :creator_id)
# returns => {0=>1, 10=>1, 20=>11}
sum(Issue.group(:state_id), :weight))
# returns => {1=>3542, 2=>6820}
```
### Redis Counters
Handles `::Redis::CommandError` and `Gitlab::UsageDataCounters::BaseCounter::UnknownEvent`
......
This diff is collapsed.
---
stage: Monitor
group: Health
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Generic alerts integration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
GitLab can accept alerts from any source via a generic webhook receiver.
When you set up the generic alerts integration, a unique endpoint will
be created which can receive a payload in JSON format, and will in turn
create an issue with the payload in the body of the issue. You can always
[customize the payload](#customizing-the-payload) to your liking.
The entire payload will be posted in the issue discussion as a comment
authored by the GitLab Alert Bot.
NOTE: **Note:**
In GitLab versions 13.1 and greater, you can configure
[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances)
to use this endpoint.
## Setting up generic alerts
To obtain credentials for setting up a generic alerts integration:
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to the **Operations** page for your project, depending on your
installed version of GitLab:
- *In GitLab versions 13.1 and greater,* navigate to **Settings > Operations**
in your project.
- *In GitLab versions prior to 13.1,* navigate to **Settings > Integrations**
in your project. GitLab will display a banner encouraging you to enable
the Alerts endpoint in **Settings > Operations** instead.
1. Click **Alerts endpoint**.
1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
for the webhook configuration.
## Customizing the payload
You can customize the payload by sending the following parameters. All fields
other than `title` are optional:
| Property | Type | Description |
| ------------------------- | --------------- | ----------- |
| `title` | String | The title of the incident. Required. |
| `description` | String | A high-level summary of the problem. |
| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue will be used. |
| `end_time` | DateTime | For existing alerts only. When provided, the alert is resolved and the associated incident is closed. |
| `service` | String | The affected service. |
| `monitoring_tool` | String | The name of the associated monitoring tool. |
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
| `gitlab_environment_name` | String | The name of the associated GitLab [environment](../../ci/environments/index.md). This can be used to associate your alert to your environment. |
You can also add custom fields to the alert's payload. The values of extra
parameters aren't limited to primitive types (such as strings or numbers), but
can be a nested JSON object. For example:
```json
{ "foo": { "bar": { "baz": 42 } } }
```
TIP: **Payload size:**
Ensure your requests are smaller than the [payload application limits](../../administration/instance_limits.md#generic-alert-json-payloads).
Example request:
```shell
curl --request POST \
--data '{"title": "Incident title"}' \
--header "Authorization: Bearer <authorization_key>" \
--header "Content-Type: application/json" \
<url>
```
The `<authorization_key>` and `<url>` values can be found when [setting up generic alerts](#setting-up-generic-alerts).
Example payload:
```json
{
"title": "Incident title",
"description": "Short description of the incident",
"start_time": "2019-09-12T06:00:55Z",
"service": "service affected",
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
"foo": {
"bar": {
"baz": 42
}
}
}
```
## Triggering test alerts
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
After a [project maintainer or owner](#setting-up-generic-alerts)
[configures generic alerts](#setting-up-generic-alerts), you can trigger a test
alert to confirm your integration works properly.
1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
1. Navigate to **Settings > Operations** in your project.
1. Click **Alerts endpoint** to expand the section.
1. Enter a sample payload in **Alert test payload** (valid JSON is required).
1. Click **Test alert payload**.
GitLab displays an error or success message, depending on the outcome of your test.
## Automatic grouping of identical alerts **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
In GitLab versions 13.2 and greater, GitLab groups alerts based on their
payload. When an incoming alert contains the same payload as another alert
(excluding the `start_time` and `hosts` attributes), GitLab groups these alerts
together and displays a counter on the [Alert Management List](./incidents.md)
and details pages.
If the existing alert is already `resolved`, GitLab creates a new alert instead.
![Alert Management List](./img/alert_list_v13_1.png)
......@@ -71,10 +71,10 @@ To populate the alerts with data, see [External Prometheus instances](../metrics
### Enable a Generic Alerts endpoint
GitLab provides the Generic Alerts endpoint so you can accept alerts from a
third-party alerts service. Read the [instructions for toggling generic alerts](generic_alerts.md#setting-up-generic-alerts)
third-party alerts service. Read the [instructions for toggling generic alerts](alert_integrations.md#setting-up-generic-alerts)
to add this option. After configuring the endpoint, the Alerts list is enabled.
To populate the alerts with data, see [Customizing the payload](generic_alerts.md#customizing-the-payload)
To populate the alerts with data, see [Customizing the payload](alert_integrations.md#customizing-the-payload)
for requests to the alerts endpoint.
### Opsgenie integration **(PREMIUM)**
......
---
stage: Monitor
group: Health
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
redirect_to: alert_notifications.md
---
# Generic alerts integration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
GitLab can accept alerts from any source via a generic webhook receiver.
When you set up the generic alerts integration, a unique endpoint will
be created which can receive a payload in JSON format, and will in turn
create an issue with the payload in the body of the issue. You can always
[customize the payload](#customizing-the-payload) to your liking.
The entire payload will be posted in the issue discussion as a comment
authored by the GitLab Alert Bot.
NOTE: **Note:**
In GitLab versions 13.1 and greater, you can configure
[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances)
to use this endpoint.
## Setting up generic alerts
To obtain credentials for setting up a generic alerts integration:
- Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md) for a project.
- Navigate to the **Operations** page for your project, depending on your installed version of GitLab:
- *In GitLab versions 13.1 and greater,* navigate to **Settings > Operations** in your project.
- *In GitLab versions prior to 13.1,* navigate to **Settings > Integrations** in your project. GitLab will display a banner encouraging you to enable the Alerts endpoint in **Settings > Operations** instead.
- Click **Alerts endpoint**.
- Toggle the **Active** alert setting to display the **URL** and **Authorization Key** for the webhook configuration.
## Customizing the payload
You can customize the payload by sending the following parameters. All fields other than `title` are optional:
| Property | Type | Description |
| -------- | ---- | ----------- |
| `title` | String | The title of the incident. Required. |
| `description` | String | A high-level summary of the problem. |
| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue will be used. |
| `end_time` | DateTime | For existing alerts only. When provided, the alert is resolved and the associated incident is closed. |
| `service` | String | The affected service. |
| `monitoring_tool` | String | The name of the associated monitoring tool. |
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
| `gitlab_environment_name` | String | The name of the associated GitLab [environment](../../ci/environments/index.md). This can be used to associate your alert to your environment. |
You can also add custom fields to the alert's payload. The values of extra parameters
are not limited to primitive types, such as strings or numbers, but can be a nested
JSON object. For example:
```json
{ "foo": { "bar": { "baz": 42 } } }
```
TIP: **Payload size:**
Ensure your requests are smaller than the [payload application limits](../../administration/instance_limits.md#generic-alert-json-payloads).
Example request:
```shell
curl --request POST \
--data '{"title": "Incident title"}' \
--header "Authorization: Bearer <authorization_key>" \
--header "Content-Type: application/json" \
<url>
```
The `<authorization_key>` and `<url>` values can be found when [setting up generic alerts](#setting-up-generic-alerts).
Example payload:
```json
{
"title": "Incident title",
"description": "Short description of the incident",
"start_time": "2019-09-12T06:00:55Z",
"service": "service affected",
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
"foo": {
"bar": {
"baz": 42
}
}
}
```
## Triggering test alerts
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
After a [project maintainer or owner](#setting-up-generic-alerts)
[configures generic alerts](#setting-up-generic-alerts), you can trigger a
test alert to confirm your integration works properly.
1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
1. Navigate to **Settings > Operations** in your project.
1. Click **Alerts endpoint** to expand the section.
1. Enter a sample payload in **Alert test payload** (valid JSON is required).
1. Click **Test alert payload**.
GitLab displays an error or success message, depending on the outcome of your test.
## Automatic grouping of identical alerts **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
In GitLab versions 13.2 and greater, GitLab groups alerts based on their payload.
When an incoming alert contains the same payload as another alert (excluding the
`start_time` and `hosts` attributes), GitLab groups these alerts together and
displays a counter on the
[Alert Management List](./incidents.md)
and details pages.
If the existing alert is already `resolved`, then a new alert will be created instead.
![Alert Management List](./img/alert_list_v13_1.png)
This document was moved to [another location](alert_notifications.md).
......@@ -21,7 +21,7 @@ The following are available Rake tasks:
| [Clean up](cleanup.md) | Clean up unneeded items from GitLab instances. |
| [Development](../development/rake_tasks.md) | Tasks for GitLab contributors. |
| [Doctor tasks](../administration/raketasks/doctor.md) | Checks for data integrity issues. |
| [Elasticsearch](../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) **(STARTER ONLY)** | Maintain Elasticsearch in a GitLab instance. |
| [Elasticsearch](../integration/elasticsearch.md#gitlab-advanced-search-rake-tasks) **(STARTER ONLY)** | Maintain Elasticsearch in a GitLab instance. |
| [Enable namespaces](features.md) | Enable usernames and namespaces for user projects. |
| [General maintenance](../administration/raketasks/maintenance.md) | General maintenance and self-check tasks. |
| [Geo maintenance](../administration/raketasks/geo.md) **(PREMIUM ONLY)** | [Geo](../administration/geo/index.md)-related maintenance. |
......
......@@ -30,7 +30,7 @@ Access the default page for admin area settings by navigating to **Admin Area >
| Option | Description |
| ------ | ----------- |
| [Elasticsearch](../../../integration/elasticsearch.md#enabling-elasticsearch) | Elasticsearch integration. Elasticsearch AWS IAM. |
| [Elasticsearch](../../../integration/elasticsearch.md#enabling-advanced-search) | Elasticsearch integration. Elasticsearch AWS IAM. |
| [PlantUML](../../../administration/integration/plantuml.md#gitlab) | Allow rendering of PlantUML diagrams in AsciiDoc documents. |
| [Slack application](../../../user/project/integrations/gitlab_slack_application.md#configuration) **(FREE ONLY)** | Slack integration allows you to interact with GitLab via slash commands in a chat window. This option is only available on GitLab.com, though it may be [available for self-managed instances in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/28164). |
| [Third party offers](third_party_offers.md) | Control the display of third party offers. |
......
......@@ -140,7 +140,7 @@ export default {
<template>
<div
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal gl-display-flex! gl-flex-shrink-0"
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal gl-display-flex gl-flex-shrink-0"
:class="{ 'is-collapsed': !list.isExpanded }"
>
<div class="board-inner gl-rounded-base gl-relative gl-w-full">
......
<script>
import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
export default {
components: {
GlButton,
GlEmptyState,
GlLink,
GlSprintf,
},
props: {
image: {
type: String,
required: true,
},
},
};
</script>
<template>
<gl-empty-state
:svg-path="image"
:title="s__('ClusterAgents|Integrate Kubernetes with a GitLab Agent')"
>
<template #description>
<p>
<gl-sprintf
:message="
s__(
'ClusterAgents|The GitLab Kubernetes Agent allows an Infrastructure as Code, GitOps approach to integrating Kubernetes clusters with GitLab. %{linkStart}Learn more.%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link href="https://docs.gitlab.com/ee/user/clusters/agent/" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
<p>
<gl-sprintf
:message="
s__(
'ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link
href="https://docs.gitlab.com/ee/user/clusters/agent/#install-the-agent-server"
target="_blank"
>
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
</template>
<template #actions>
<gl-button
category="primary"
variant="success"
href="https://docs.gitlab.com/ee/user/clusters/agent/#get-started-with-gitops-and-the-gitlab-agent"
target="_blank"
>
{{ s__('ClusterAgents|Integrate with the GitLab Agent') }}
</gl-button>
</template>
</gl-empty-state>
</template>
<script>
import { GlButton, GlLink, GlTable } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlButton,
GlLink,
GlTable,
},
props: {
agents: {
required: true,
type: Array,
},
},
computed: {
fields() {
return [
{
key: 'name',
label: s__('ClusterAgents|Name'),
},
{
key: 'configuration',
label: s__('ClusterAgents|Configuration'),
},
];
},
},
};
</script>
<template>
<div>
<div class="gl-display-block gl-text-right gl-my-4">
<gl-button
category="primary"
href="https://docs.gitlab.com/ee/user/clusters/agent/#get-started-with-gitops-and-the-gitlab-agent"
target="_blank"
variant="success"
>
{{ s__('ClusterAgents|Connect your cluster with the GitLab Agent') }}
</gl-button>
</div>
<gl-table :items="agents" :fields="fields" stacked="md" data-testid="cluster-agent-list-table">
<template #cell(configuration)=" { item }">
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
<gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
.gitlab/agents/{{ item.name }}
</gl-link>
<p v-else>.gitlab/agents/{{ item.name }}</p>
</template>
</gl-table>
</div>
</template>
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { sortBy } from 'lodash';
import AgentEmptyState from './agent_empty_state.vue';
import AgentTable from './agent_table.vue';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
export default {
apollo: {
agents: {
query: getAgentsQuery,
variables() {
return {
defaultBranchName: this.defaultBranchName,
projectPath: this.projectPath,
};
},
update: data => {
let agentList = data.project.clusterAgents.nodes;
const configFolders = data.project.repository.tree?.trees?.nodes;
if (configFolders) {
agentList = agentList.map(agent => {
const configFolder = configFolders.find(({ name }) => name === agent.name);
return { ...agent, configFolder };
});
}
return sortBy(agentList, 'name');
},
},
},
components: {
AgentEmptyState,
AgentTable,
GlLoadingIcon,
},
props: {
emptyStateImage: {
required: true,
type: String,
},
defaultBranchName: {
default: '.noBranch',
required: false,
type: String,
},
projectPath: {
required: true,
type: String,
},
},
};
</script>
<template>
<section v-if="agents" class="gl-mt-3">
<AgentTable v-if="agents.length" :agents="agents" />
<AgentEmptyState v-else :image="emptyStateImage" />
</section>
<gl-loading-icon v-else size="md" class="gl-mt-3" />
</template>
query getAgents($defaultBranchName: String!, $projectPath: ID!) {
project(fullPath: $projectPath) {
clusterAgents {
nodes {
id
name
}
}
repository {
tree(path: ".gitlab/agents", ref: $defaultBranchName) {
trees {
nodes {
name
path
webPath
}
}
}
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadAgents from './load_agents';
import loadClusters from '~/clusters_list/load_clusters';
Vue.use(VueApollo);
export default () => {
loadClusters(Vue);
loadAgents(Vue, VueApollo);
};
import Agents from './components/agents.vue';
import createDefaultClient from '~/lib/graphql';
export default (Vue, VueApollo) => {
const el = document.querySelector('#js-cluster-agents-list');
if (!el) {
return null;
}
const defaultClient = createDefaultClient();
defaultClient.cache.writeData({
/* eslint-disable @gitlab/require-i18n-strings */
data: {
project: {
__typename: 'Project',
clusterAgents: {
__typename: 'ClusterAgents',
nodes: [],
},
repository: {
__typename: 'Repository',
tree: {
__typename: 'Tree',
trees: {
__typename: 'Trees',
nodes: [],
},
},
},
},
},
});
const { emptyStateImage, defaultBranchName, projectPath } = el.dataset;
return new Vue({
el,
apolloProvider: new VueApollo({ defaultClient }),
render(createElement) {
return createElement(Agents, {
props: {
emptyStateImage,
defaultBranchName,
projectPath,
},
});
},
});
};
# frozen_string_literal: true
module EE
module ClustersHelper
extend ::Gitlab::Utils::Override
override :display_cluster_agents?
def display_cluster_agents?(clusterable)
return unless ::Feature.enabled?(:cluster_agent_list, default_enabled: true)
clusterable.is_a?(Project) && clusterable.feature_available?(:cluster_agents)
end
end
end
......@@ -120,6 +120,10 @@ module EE
project.feature_available?(:multiple_merge_request_assignees)
end
def allows_multiple_reviewers?
project.feature_available?(:multiple_merge_request_reviewers)
end
def visible_blocking_merge_requests(user)
Ability.merge_requests_readable_by_user(blocking_merge_requests, user)
end
......
......@@ -29,6 +29,7 @@ class License < ApplicationRecord
multiple_issue_assignees
multiple_ldap_servers
multiple_merge_request_assignees
multiple_merge_request_reviewers
project_merge_request_analytics
protected_refs_for_users
push_rules
......
---
title: Add Agent List to Cluster List View
merge_request: 42115
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'ClusterAgents', :js do
let_it_be(:agent) { create(:cluster_agent) }
let(:project) { agent.project }
let(:user) { project.creator }
before do
gitlab_sign_in(user)
end
context 'non-premium user' do
before do
stub_licensed_features(cluster_agents: false)
end
context 'when user visits agents index page' do
before do
visit project_clusters_path(project)
end
it 'does not display agent information', :aggregate_failures do
expect(page).to have_content('Integrate with a cluster certificate')
expect(page).not_to have_content('GitLab Agent managed clusters')
end
end
end
context 'premium user' do
before do
stub_licensed_features(cluster_agents: true)
end
context 'when user does not have any agents and visits the index page' do
let(:empty_project) { create(:project) }
before do
empty_project.add_maintainer(user)
visit project_clusters_path(empty_project)
end
it 'displays empty state', :aggregate_failures do
click_link 'GitLab Agent managed clusters'
expect(page).to have_link('Integrate with the GitLab Agent')
expect(page).to have_selector('.empty-state')
end
end
context 'when user has an agent and visits the index page' do
before do
visit project_clusters_path(project)
end
it 'displays a table with agent', :aggregate_failures do
click_link 'GitLab Agent managed clusters'
expect(page).to have_content(agent.name)
expect(page).to have_selector('[data-testid="cluster-agent-list-table"] tbody tr', count: 1)
end
end
end
end
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AgentEmptyState from 'ee/clusters_list/components/agent_empty_state.vue';
describe('AgentEmptyStateComponent', () => {
let wrapper;
const propsData = {
image: '/image/path',
};
beforeEach(() => {
wrapper = shallowMount(AgentEmptyState, { propsData, stubs: { GlEmptyState, GlSprintf } });
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
it('should render content', () => {
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
expect(wrapper.text()).toContain('Integrate with the GitLab Agent');
});
});
import { GlButton, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import AgentTable from 'ee/clusters_list/components/agent_table.vue';
const propsData = {
agents: [
{
name: 'agent-1',
configFolder: {
webPath: '/agent/full/path',
},
},
{
name: 'agent-2',
},
],
};
describe('AgentTable', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(AgentTable, { propsData });
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
it('displays header button', () => {
expect(wrapper.find(GlButton).text()).toBe('Connect your cluster with the GitLab Agent');
});
describe('agent table', () => {
it.each`
agentName | lineNumber
${'agent-1'} | ${0}
${'agent-2'} | ${1}
`('displays agent name', ({ agentName, lineNumber }) => {
const agents = wrapper.findAll(
'[data-testid="cluster-agent-list-table"] tbody tr > td:first-child',
);
const agent = agents.at(lineNumber);
expect(agent.text()).toBe(agentName);
});
it.each`
agentPath | hasLink | lineNumber
${'.gitlab/agents/agent-1'} | ${true} | ${0}
${'.gitlab/agents/agent-2'} | ${false} | ${1}
`('displays config file path', ({ agentPath, hasLink, lineNumber }) => {
const agents = wrapper.findAll(
'[data-testid="cluster-agent-list-table"] tbody tr > td:nth-child(2)',
);
const agent = agents.at(lineNumber);
expect(agent.find(GlLink).exists()).toBe(hasLink);
expect(agent.text()).toBe(agentPath);
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo';
import Agents from 'ee/clusters_list/components/agents.vue';
import AgentEmptyState from 'ee/clusters_list/components/agent_empty_state.vue';
import AgentTable from 'ee/clusters_list/components/agent_table.vue';
import getAgentsQuery from 'ee/clusters_list/graphql/queries/get_agents.query.graphql';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('Agents', () => {
let wrapper;
const createWrapper = ({ agents }) => {
const apolloQueryResponse = {
data: {
project: {
clusterAgents: { nodes: agents },
repository: { tree: { trees: { nodes: [] } } },
},
},
};
const apolloProvider = createMockApollo([
[getAgentsQuery, jest.fn().mockResolvedValue(apolloQueryResponse)],
]);
wrapper = shallowMount(Agents, {
localVue,
apolloProvider,
propsData: {
emptyStateImage: '/path/to/image',
defaultBranchName: 'default',
projectPath: 'path/to/project',
},
});
return wrapper.vm.$nextTick();
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
describe('when there is a list of agents', () => {
const agents = [
{
id: '1',
name: 'agent-1',
},
{
id: '2',
name: 'agent-2',
},
];
beforeEach(() => {
return createWrapper({ agents });
});
it('should render agent table', () => {
expect(wrapper.find(AgentTable).exists()).toBe(true);
expect(wrapper.find(AgentEmptyState).exists()).toBe(false);
});
});
describe('when the agent list is empty', () => {
beforeEach(() => {
return createWrapper({ agents: [] });
});
it('should render empty state', () => {
expect(wrapper.find(AgentTable).exists()).toBe(false);
expect(wrapper.find(AgentEmptyState).exists()).toBe(true);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ClustersHelper do
describe '#display_cluster_agents?' do
let(:clusterable) { build(:project) }
subject { helper.display_cluster_agents?(clusterable) }
context 'without premium license' do
it 'does not allows agents to display' do
expect(subject).to be_falsey
end
end
context 'with premium license' do
before do
stub_licensed_features(cluster_agents: true)
end
context 'when clusterable is a project' do
it 'allows agents to display' do
expect(subject).to be_truthy
end
end
context 'when clusterable is a group' do
let(:clusterable) { build(:group) }
it 'does not allows agents to display' do
expect(subject).to be_falsey
end
end
context 'when cluster_agent_list feature flag is disabled' do
before do
stub_feature_flags(cluster_agent_list: false)
end
it 'does not allows agents to display' do
expect(subject).to be_falsey
end
end
end
end
end
......@@ -118,6 +118,24 @@ RSpec.describe MergeRequest do
end
end
describe '#allows_multiple_reviewers?' do
it 'returns false without license' do
stub_licensed_features(multiple_merge_request_reviewers: false)
merge_request = build_stubbed(:merge_request)
expect(merge_request.allows_multiple_reviewers?).to be(false)
end
it 'returns true when licensed' do
stub_licensed_features(multiple_merge_request_reviewers: true)
merge_request = build(:merge_request)
expect(merge_request.allows_multiple_reviewers?).to be(true)
end
end
describe '#participants' do
context 'with approval rule' do
before do
......
......@@ -51,6 +51,10 @@ RSpec.describe MergeRequests::CreateService do
it_behaves_like 'new issuable with scoped labels' do
let(:parent) { project }
end
it_behaves_like 'service with multiple reviewers' do
let(:execute) { service.execute }
end
end
describe '#execute with blocking merge requests', :clean_gitlab_redis_shared_state do
......
......@@ -36,6 +36,11 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
let(:parent) { project }
end
it_behaves_like 'service with multiple reviewers' do
let(:opts) { {} }
let(:execute) { update_merge_request(opts) }
end
def update_merge_request(opts)
described_class.new(project, user, opts).execute(merge_request)
end
......
......@@ -96,3 +96,38 @@ RSpec.shared_examples 'merge validation hooks' do |args|
end
end
end
RSpec.shared_examples 'service with multiple reviewers' do
context 'with multiple reviewer assignments' do
let(:opts) { super().merge(reviewer_ids_param) }
let(:reviewer_ids_param) { { reviewer_ids: [reviewer1.id, reviewer2.id] } }
let(:reviewer1) { create(:user) }
let(:reviewer2) { create(:user) }
before do
stub_feature_flags(merge_request_reviewer: true)
project.add_developer(reviewer1)
project.add_developer(reviewer2)
end
context 'with multiple_merge_request_reviewers feature on' do
before do
stub_licensed_features(multiple_merge_request_reviewers: true)
end
it 'allows multiple reviewers' do
expect(execute.reviewers).to contain_exactly(reviewer1, reviewer2)
end
end
context 'with multiple_merge_request_reviewers feature off' do
before do
stub_licensed_features(multiple_merge_request_reviewers: false)
end
it 'only allows one reviewer' do
expect(execute.reviewers).to contain_exactly(reviewer1)
end
end
end
end
......@@ -5340,6 +5340,27 @@ msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
msgid "ClusterAgents|Configuration"
msgstr ""
msgid "ClusterAgents|Connect your cluster with the GitLab Agent"
msgstr ""
msgid "ClusterAgents|Integrate Kubernetes with a GitLab Agent"
msgstr ""
msgid "ClusterAgents|Integrate with the GitLab Agent"
msgstr ""
msgid "ClusterAgents|Name"
msgstr ""
msgid "ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}"
msgstr ""
msgid "ClusterAgents|The GitLab Kubernetes Agent allows an Infrastructure as Code, GitOps approach to integrating Kubernetes clusters with GitLab. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "ClusterAgent|This feature is only available for premium plans"
msgstr ""
......@@ -5514,6 +5535,12 @@ msgstr ""
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Clusters connected with a certificate"
msgstr ""
msgid "ClusterIntegration|Connect cluster with certificate"
msgstr ""
msgid "ClusterIntegration|Connect existing cluster"
msgstr ""
......@@ -5670,6 +5697,9 @@ msgstr ""
msgid "ClusterIntegration|Fluentd is an open source data collector, which lets you unify the data collection and consumption for a better use and understanding of data. It requires at least one of the following logs to be successfully installed."
msgstr ""
msgid "ClusterIntegration|GitLab Agent managed clusters"
msgstr ""
msgid "ClusterIntegration|GitLab Container Network Policies"
msgstr ""
......@@ -5745,7 +5775,10 @@ msgstr ""
msgid "ClusterIntegration|Instance type"
msgstr ""
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgid "ClusterIntegration|Integrate Kubernetes with a cluster certificate"
msgstr ""
msgid "ClusterIntegration|Integrate with a cluster certificate"
msgstr ""
msgid "ClusterIntegration|Issuer Email"
......@@ -5793,9 +5826,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
msgstr ""
msgid "ClusterIntegration|Kubernetes version"
msgstr ""
......
......@@ -7,11 +7,11 @@ module QA
module Kubernetes
class Index < Page::Base
view 'app/views/clusters/clusters/_empty_state.html.haml' do
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Integrate with a cluster certificate')" # rubocop:disable QA/ElementWithPattern
end
def add_kubernetes_cluster
click_on 'Add Kubernetes cluster'
click_on 'Connect cluster with certificate'
end
def has_cluster?(cluster)
......
......@@ -13,7 +13,7 @@ RSpec.describe 'Instance-level AWS EKS Cluster', :js do
before do
visit admin_clusters_path
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
end
context 'when user creates a cluster on AWS EKS' do
......
......@@ -19,7 +19,7 @@ RSpec.describe 'Group AWS EKS Cluster', :js do
before do
visit group_clusters_path(group)
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
end
context 'when user creates a cluster on AWS EKS' do
......
......@@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do
before do
visit group_clusters_path(group)
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
click_link 'Connect existing cluster'
end
......@@ -129,7 +129,7 @@ RSpec.describe 'User Cluster', :js do
it 'user sees creation form with the successful message' do
expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
expect(page).to have_link('Add Kubernetes cluster')
expect(page).to have_link('Integrate with a cluster certificate')
end
end
end
......
......@@ -19,7 +19,7 @@ RSpec.describe 'AWS EKS Cluster', :js do
before do
visit project_clusters_path(project)
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
end
context 'when user creates a cluster on AWS EKS' do
......
......@@ -33,7 +33,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
before do
visit project_clusters_path(project)
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
end
......@@ -143,7 +143,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
before do
visit project_clusters_path(project)
click_link 'Add Kubernetes cluster'
click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
end
......@@ -162,7 +162,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
it 'user sees creation form with the successful message' do
expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
expect(page).to have_link('Add Kubernetes cluster')
expect(page).to have_link('Integrate with a cluster certificate')
end
end
end
......@@ -178,7 +178,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
it 'user sees offer on cluster create page' do
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
expect(page).to have_css('.gcp-signup-offer')
end
......@@ -195,7 +195,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
find('.gcp-signup-offer .js-close').click
wait_for_requests
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
expect(page).not_to have_css('.gcp-signup-offer')
end
......
......@@ -25,7 +25,7 @@ RSpec.describe 'User Cluster', :js do
before do
visit project_clusters_path(project)
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
click_link 'Connect existing cluster'
end
......@@ -116,7 +116,7 @@ RSpec.describe 'User Cluster', :js do
it 'user sees creation form with the successful message' do
expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
expect(page).to have_link('Add Kubernetes cluster')
expect(page).to have_link('Integrate with a cluster certificate')
end
end
end
......
......@@ -19,7 +19,7 @@ RSpec.describe 'Clusters', :js do
end
it 'sees empty state' do
expect(page).to have_link('Add Kubernetes cluster')
expect(page).to have_link('Integrate with a cluster certificate')
expect(page).to have_selector('.empty-state')
end
end
......@@ -41,7 +41,7 @@ RSpec.describe 'Clusters', :js do
context 'when user filled form with environment scope' do
before do
click_link 'Add Kubernetes cluster'
click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: 'staging/*'
......@@ -70,7 +70,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
click_link 'Add Kubernetes cluster'
click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: '*'
......@@ -116,7 +116,7 @@ RSpec.describe 'Clusters', :js do
context 'when user filled form with environment scope' do
before do
click_link 'Add Kubernetes cluster'
click_link 'Connect cluster with certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
......@@ -161,7 +161,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
click_link 'Add Kubernetes cluster'
click_link 'Connect cluster with certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
......@@ -214,7 +214,7 @@ RSpec.describe 'Clusters', :js do
before do
visit project_clusters_path(project)
click_link 'Add Kubernetes cluster'
click_link 'Integrate with a cluster certificate'
click_link 'Create new cluster'
end
......
......@@ -4,17 +4,25 @@ require 'spec_helper'
RSpec.describe 'User views Release', :js do
let(:project) { create(:project, :repository) }
let(:release) { create(:release, project: project, name: 'The first release' ) }
let(:user) { create(:user) }
let(:release) do
create(:release,
project: project,
name: 'The first release',
description: '**Lorem** _ipsum_ dolor sit [amet](https://example.com)')
end
before do
project.add_developer(user)
gitlab_sign_in(user)
sign_in(user)
visit project_release_path(project, release)
end
it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet'
it 'renders the breadcrumbs' do
within('.breadcrumbs') do
expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}")
......@@ -31,7 +39,7 @@ RSpec.describe 'User views Release', :js do
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
expect(page).to have_content(release.commit.short_id)
expect(page).to have_content(release.description)
expect(page).to have_content('Lorem ipsum dolor sit amet')
end
end
end
......@@ -59,6 +59,24 @@ RSpec.describe ClustersHelper do
end
end
describe '#js_cluster_agents_list_data' do
let_it_be(:project) { build(:project, :repository) }
subject { helper.js_cluster_agents_list_data(project) }
it 'displays project default branch' do
expect(subject[:default_branch_name]).to eq(project.default_branch)
end
it 'displays image path' do
expect(subject[:empty_state_image]).to match(%r(/illustrations/logos/clusters_empty|svg))
end
it 'displays project path' do
expect(subject[:project_path]).to eq(project.full_path)
end
end
describe '#js_clusters_list_data' do
subject { helper.js_clusters_list_data('/path') }
......
......@@ -13,11 +13,10 @@ RSpec.shared_examples 'reviewer_ids filter' do
end
context 'with reviewer_ids' do
let(:reviewer_ids_param) { { reviewer_ids: [reviewer1.id, reviewer2.id, reviewer3.id] } }
let(:reviewer_ids_param) { { reviewer_ids: [reviewer1.id, reviewer2.id] } }
let(:reviewer1) { create(:user) }
let(:reviewer2) { create(:user) }
let(:reviewer3) { create(:user) }
context 'when the current user can admin the merge_request' do
context 'when merge_request_reviewer feature is enabled' do
......@@ -25,14 +24,13 @@ RSpec.shared_examples 'reviewer_ids filter' do
stub_feature_flags(merge_request_reviewer: true)
end
context 'with reviewers who can read the merge_request' do
context 'with a reviewer who can read the merge_request' do
before do
project.add_developer(reviewer1)
project.add_developer(reviewer2)
end
it 'contains reviewers who can read the merge_request' do
expect(execute.reviewers).to contain_exactly(reviewer1, reviewer2)
expect(execute.reviewers).to contain_exactly(reviewer1)
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment