Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-19 06:09:08 +00:00
parent 4e901aff71
commit 731d58455c
36 changed files with 565 additions and 37 deletions

View file

@ -1 +1 @@
13.9.0
13.10.0

View file

@ -108,4 +108,44 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => {
IssuableTokenKeys.tokenKeys.splice(tokenPosition, 0, ...[approvedBy.token]);
IssuableTokenKeys.tokenKeysWithAlternative.splice(tokenPosition, 0, ...[approvedBy.token]);
IssuableTokenKeys.conditions.push(...approvedBy.condition);
if (gon?.features?.deploymentFilters) {
const environmentToken = {
formattedKey: __('Environment'),
key: 'environment',
type: 'string',
param: '',
symbol: '',
icon: 'cloud-gear',
tag: 'environment',
};
const deployedBeforeToken = {
formattedKey: __('Deployed-before'),
key: 'deployed-before',
type: 'string',
param: '',
symbol: '',
icon: 'clock',
tag: 'deployed_before',
};
const deployedAfterToken = {
formattedKey: __('Deployed-after'),
key: 'deployed-after',
type: 'string',
param: '',
symbol: '',
icon: 'clock',
tag: 'deployed_after',
};
IssuableTokenKeys.tokenKeys.push(environmentToken, deployedBeforeToken, deployedAfterToken);
IssuableTokenKeys.tokenKeysWithAlternative.push(
environmentToken,
deployedBeforeToken,
deployedAfterToken,
);
}
};

View file

@ -15,6 +15,7 @@ export default class AvailableDropdownMappings {
labelsEndpoint,
milestonesEndpoint,
releasesEndpoint,
environmentsEndpoint,
groupsOnly,
includeAncestorGroups,
includeDescendantGroups,
@ -24,6 +25,7 @@ export default class AvailableDropdownMappings {
this.labelsEndpoint = labelsEndpoint;
this.milestonesEndpoint = milestonesEndpoint;
this.releasesEndpoint = releasesEndpoint;
this.environmentsEndpoint = environmentsEndpoint;
this.groupsOnly = groupsOnly;
this.includeAncestorGroups = includeAncestorGroups;
this.includeDescendantGroups = includeDescendantGroups;
@ -149,6 +151,16 @@ export default class AvailableDropdownMappings {
},
element: this.container.querySelector('#js-dropdown-target-branch'),
},
environment: {
reference: null,
gl: DropdownNonUser,
extraArguments: {
endpoint: this.getEnvironmentsEndpoint(),
symbol: '',
preprocessing: data => data.map(env => ({ title: env })),
},
element: this.container.querySelector('#js-dropdown-environment'),
},
};
}
@ -194,6 +206,10 @@ export default class AvailableDropdownMappings {
return mergeUrlParams(params, endpoint);
}
getEnvironmentsEndpoint() {
return `${this.environmentsEndpoint}.json`;
}
getGroupId() {
return this.filteredSearchInput.getAttribute('data-group-id') || '';
}

View file

@ -13,6 +13,7 @@ export default class FilteredSearchDropdownManager {
labelsEndpoint = '',
milestonesEndpoint = '',
releasesEndpoint = '',
environmentsEndpoint = '',
epicsEndpoint = '',
tokenizer,
page,
@ -29,6 +30,7 @@ export default class FilteredSearchDropdownManager {
this.milestonesEndpoint = removeTrailingSlash(milestonesEndpoint);
this.releasesEndpoint = removeTrailingSlash(releasesEndpoint);
this.epicsEndpoint = removeTrailingSlash(epicsEndpoint);
this.environmentsEndpoint = removeTrailingSlash(environmentsEndpoint);
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');

View file

@ -110,6 +110,7 @@ export default class FilteredSearchManager {
labelsEndpoint = '',
milestonesEndpoint = '',
releasesEndpoint = '',
environmentsEndpoint = '',
epicsEndpoint = '',
} = this.filteredSearchInput.dataset;
@ -118,6 +119,7 @@ export default class FilteredSearchManager {
labelsEndpoint,
milestonesEndpoint,
releasesEndpoint,
environmentsEndpoint,
epicsEndpoint,
tokenizer: this.tokenizer,
page: this.page,

View file

@ -30,6 +30,7 @@ class GroupsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:vue_issuables_list, @group)
push_frontend_feature_flag(:deployment_filters)
end
before_action do
@ -53,7 +54,7 @@ class GroupsController < Groups::ApplicationController
feature_category :audit_events, [:activity]
feature_category :issue_tracking, [:issues, :issues_calendar, :preview_markdown]
feature_category :code_review, [:merge_requests]
feature_category :code_review, [:merge_requests, :unfoldered_environment_names]
feature_category :projects, [:projects]
feature_category :importers, [:export, :download_export]
@ -179,6 +180,16 @@ class GroupsController < Groups::ApplicationController
end
end
def unfoldered_environment_names
return render_404 unless Feature.enabled?(:deployment_filters)
respond_to do |format|
format.json do
render json: EnvironmentNamesFinder.new(@group, current_user).execute
end
end
end
protected
def render_show_html

View file

@ -47,6 +47,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
push_frontend_feature_flag(:deployment_filters)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]

View file

@ -56,6 +56,7 @@ class ProjectsController < Projects::ApplicationController
feature_category :issue_tracking, [:preview_markdown, :new_issuable_address]
feature_category :importers, [:export, :remove_export, :generate_new_export, :download_export]
feature_category :audit_events, [:activity]
feature_category :code_review, [:unfoldered_environment_names]
def index
redirect_to(current_user ? root_path : explore_root_path)
@ -315,6 +316,16 @@ class ProjectsController < Projects::ApplicationController
end
end
def unfoldered_environment_names
return render_404 unless Feature.enabled?(:deployment_filters)
respond_to do |format|
format.json do
render json: EnvironmentNamesFinder.new(@project, current_user).execute
end
end
end
private
# Render project landing depending of which features are available

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
# Finder for obtaining the unique environment names of a project or group.
#
# This finder exists so that the merge requests "environments" filter can be
# populated with a unique list of environment names. If we retrieve _just_ the
# environments, duplicates may be present (e.g. multiple projects in a group
# having a "staging" environment).
#
# In addition, this finder only produces unfoldered environments. We do this
# because when searching for environments we want to exclude review app
# environments.
class EnvironmentNamesFinder
attr_reader :project_or_group, :current_user
def initialize(project_or_group, current_user)
@project_or_group = project_or_group
@current_user = current_user
end
def execute
all_environments.unfoldered.order_by_name.pluck_unique_names
end
def all_environments
if project_or_group.is_a?(Namespace)
namespace_environments
else
project_environments
end
end
def namespace_environments
projects =
project_or_group.all_projects.public_or_visible_to_user(current_user)
Environment.for_project(projects)
end
def project_environments
if current_user.can?(:read_environment, project_or_group)
project_or_group.environments
else
Environment.none
end
end
end

View file

@ -33,7 +33,17 @@ class MergeRequestsFinder < IssuableFinder
include MergedAtFilter
def self.scalar_params
@scalar_params ||= super + [:wip, :draft, :target_branch, :merged_after, :merged_before, :approved_by_ids]
@scalar_params ||= super + [
:approved_by_ids,
:deployed_after,
:deployed_before,
:draft,
:environment,
:merged_after,
:merged_before,
:target_branch,
:wip
]
end
def self.array_params
@ -46,12 +56,13 @@ class MergeRequestsFinder < IssuableFinder
def filter_items(_items)
items = by_commit(super)
items = by_deployment(items)
items = by_source_branch(items)
items = by_draft(items)
items = by_target_branch(items)
items = by_merged_at(items)
items = by_approvals(items)
items = by_deployments(items)
by_source_project_id(items)
end
@ -85,17 +96,21 @@ class MergeRequestsFinder < IssuableFinder
items.where(target_branch: target_branch)
end
# rubocop: enable CodeReuse/ActiveRecord
def source_project_id
@source_project_id ||= params[:source_project_id].presence
end
# rubocop: disable CodeReuse/ActiveRecord
def by_source_project_id(items)
return items unless source_project_id
items.where(source_project_id: source_project_id)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def by_draft(items)
draft_param = params[:draft] || params[:wip]
@ -107,6 +122,7 @@ class MergeRequestsFinder < IssuableFinder
items
end
end
# rubocop: enable CodeReuse/ActiveRecord
# WIP is deprecated in favor of Draft. Currently both options are supported
def wip_match(table)
@ -126,12 +142,14 @@ class MergeRequestsFinder < IssuableFinder
.or(table[:title].matches('(Draft)%'))
end
# rubocop: disable CodeReuse/ActiveRecord
def by_deployment(items)
return items unless deployment_id
items.includes(:deployment_merge_requests)
.where(deployment_merge_requests: { deployment_id: deployment_id })
end
# rubocop: enable CodeReuse/ActiveRecord
def deployment_id
@deployment_id ||= params[:deployment_id].presence
@ -149,6 +167,33 @@ class MergeRequestsFinder < IssuableFinder
def items_assigned_to(items, user)
MergeRequest.from_union([super, items.reviewer_assigned_to(user)])
end
def by_deployments(items)
# Until this feature flag is enabled permanently, we retain the old
# filtering behaviour/code.
return by_deployment(items) unless Feature.enabled?(:deployment_filters)
env = params[:environment]
before = params[:deployed_before]
after = params[:deployed_after]
id = params[:deployment_id]
return items if !env && !before && !after && !id
# Each filter depends on the same JOIN+WHERE. To prevent this JOIN+WHERE
# from being duplicated for every filter, we only produce it once. The
# filter methods in turn expect the JOIN+WHERE to already be present.
#
# This approach ensures that query performance doesn't degrade as the number
# of deployment related filters increases.
deploys = DeploymentMergeRequest.join_deployments_for_merge_requests
deploys = deploys.by_deployment_id(id) if id
deploys = deploys.deployed_to(env) if env
deploys = deploys.deployed_before(before) if before
deploys = deploys.deployed_after(after) if after
items.where_exists(deploys)
end
end
MergeRequestsFinder.prepend_if_ee('EE::MergeRequestsFinder')

View file

@ -262,11 +262,15 @@ module SearchHelper
opts[:data]['labels-endpoint'] = project_labels_path(@project)
opts[:data]['milestones-endpoint'] = project_milestones_path(@project)
opts[:data]['releases-endpoint'] = project_releases_path(@project)
opts[:data]['environments-endpoint'] =
unfoldered_environment_names_project_path(@project)
elsif @group.present?
opts[:data]['group-id'] = @group.id
opts[:data]['labels-endpoint'] = group_labels_path(@group)
opts[:data]['milestones-endpoint'] = group_milestones_path(@group)
opts[:data]['releases-endpoint'] = group_releases_path(@group)
opts[:data]['environments-endpoint'] =
unfoldered_environment_names_group_path(@group)
else
opts[:data]['labels-endpoint'] = dashboard_labels_path
opts[:data]['milestones-endpoint'] = dashboard_milestones_path

View file

@ -3,4 +3,25 @@
class DeploymentMergeRequest < ApplicationRecord
belongs_to :deployment, optional: false
belongs_to :merge_request, optional: false
def self.join_deployments_for_merge_requests
joins(deployment: :environment)
.where('deployment_merge_requests.merge_request_id = merge_requests.id')
end
def self.by_deployment_id(id)
where('deployments.id = ?', id)
end
def self.deployed_to(name)
where('environments.name = ?', name)
end
def self.deployed_after(time)
where('deployments.finished_at > ?', time)
end
def self.deployed_before(time)
where('deployments.finished_at < ?', time)
end
end

View file

@ -70,6 +70,7 @@ class Environment < ApplicationRecord
scope :order_by_last_deployed_at_desc, -> do
order(Gitlab::Database.nulls_last_order("(#{max_deployment_id_sql})", 'DESC'))
end
scope :order_by_name, -> { order('environments.name ASC') }
scope :in_review_folder, -> { where(environment_type: "review") }
scope :for_name, -> (name) { where(name: name) }
@ -122,6 +123,10 @@ class Environment < ApplicationRecord
pluck(:name)
end
def self.pluck_unique_names
pluck('DISTINCT(environments.name)')
end
def self.find_or_create_by_name(name)
find_or_create_by(name: name)
end

View file

@ -161,6 +161,11 @@
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value.monospace
{{title}}
#js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value{ type: 'button' }
{{title}}
= render_if_exists 'shared/issuable/filter_weight', type: type

View file

@ -0,0 +1,5 @@
---
title: Update gitlab-shell to v13.10.0
merge_request: 45408
author:
type: changed

View file

@ -0,0 +1,7 @@
---
name: deployment_filters
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44041
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267561
type: development
group: group::source code
default_enabled: false

View file

@ -17,6 +17,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
put :transfer, as: :transfer_group # rubocop:disable Cop/PutGroupRoutesUnderScope
post :export, as: :export_group # rubocop:disable Cop/PutGroupRoutesUnderScope
get :download_export, as: :download_export_group # rubocop:disable Cop/PutGroupRoutesUnderScope
get :unfoldered_environment_names, as: :unfoldered_environment_names_group # rubocop:disable Cop/PutGroupRoutesUnderScope
# TODO: Remove as part of refactor in https://gitlab.com/gitlab-org/gitlab-foss/issues/49693
get 'shared', action: :show, as: :group_shared # rubocop:disable Cop/PutGroupRoutesUnderScope

View file

@ -578,6 +578,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :activity
get :refs
put :new_issuable_address
get :unfoldered_environment_names
end
end
# rubocop: enable Cop/PutProjectRoutesUnderScope

View file

@ -1,5 +1,5 @@
---
# Warning: gitlab.AdminArea
# Warning: gitlab.Admin
#
# You should not use "admin", but "Admin Area" is OK.
#
@ -10,4 +10,4 @@ link: https://docs.gitlab.com/ee/development/documentation/styleguide.html
level: warning
ignorecase: true
swap:
'admin ?\w*': '(?:Admin Area|[Aa]dministrat(ion|or|e))'
'admin ?\w*': '(?:Admin Area|[Aa]dminist(ration|rator|er))'

View file

@ -378,22 +378,22 @@ curl --head --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.exampl
The response will then be:
```http
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 1103
Content-Type: application/json
Date: Mon, 18 Jan 2016 09:43:18 GMT
Link: <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3>; rel="prev", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3>; rel="next", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3>; rel="first", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3>; rel="last"
Status: 200 OK
Vary: Origin
X-Next-Page: 3
X-Page: 2
X-Per-Page: 3
X-Prev-Page: 1
X-Request-Id: 732ad4ee-9870-4866-a199-a9db0cde3c86
X-Runtime: 0.108688
X-Total: 8
X-Total-Pages: 3
HTTP/2 200 OK
cache-control: no-cache
content-length: 1103
content-type: application/json
date: Mon, 18 Jan 2016 09:43:18 GMT
link: <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3>; rel="prev", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3>; rel="next", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3>; rel="first", <https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3>; rel="last"
status: 200 OK
vary: Origin
x-next-page: 3
x-page: 2
x-per-page: 3
x-prev-page: 1
x-request-id: 732ad4ee-9870-4866-a199-a9db0cde3c86
x-runtime: 0.108688
x-total: 8
x-total-pages: 3
```
#### Other pagination headers
@ -402,12 +402,12 @@ GitLab also returns the following additional pagination headers:
| Header | Description |
| --------------- | --------------------------------------------- |
| `X-Total` | The total number of items |
| `X-Total-Pages` | The total number of pages |
| `X-Per-Page` | The number of items per page |
| `X-Page` | The index of the current page (starting at 1) |
| `X-Next-Page` | The index of the next page |
| `X-Prev-Page` | The index of the previous page |
| `x-total` | The total number of items |
| `x-total-pages` | The total number of pages |
| `x-per-page` | The number of items per page |
| `x-page` | The index of the current page (starting at 1) |
| `x-next-page` | The index of the next page |
| `X-prev-page` | The index of the previous page |
NOTE: **Note:**
For GitLab.com users, [some pagination headers may not be returned](../user/gitlab_com/index.md#pagination-response-headers).

View file

@ -72,6 +72,9 @@ Parameters:
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
| `not` | Hash | no | Return merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji` |
| `environment` | string | no | Returns merge requests deployed to the given environment
| `deployed_before` | datetime | no | Return merge requests deployed before the given date/time
| `deployed_after` | datetime | no | Return merge requests deployed after the given date/time
NOTE: **Note:**
[Starting in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890),

View file

@ -308,7 +308,7 @@ const resolvers = {
export default resolvers;
```
We need to pass resolvers object to our existing Apollo Client:
We need to pass a resolvers object to our existing Apollo Client:
```javascript
// graphql.js
@ -319,13 +319,13 @@ import resolvers from './graphql/resolvers';
const defaultClient = createDefaultClient(resolvers);
```
Now every single time on attempt to fetch a version, our client will fetch `id` and `sha` from the remote API endpoint and will assign our hardcoded values to `author` and `createdAt` version properties. With this data, frontend developers are able to work on UI part without being blocked by backend. When actual response is added to the API, a custom local resolver can be removed fast and the only change to query/fragment is `@client` directive removal.
For each attempt to fetch a version, our client will fetch `id` and `sha` from the remote API endpoint and will assign our hardcoded values to the `author` and `createdAt` version properties. With this data, frontend developers are able to work on their UI without being blocked by backend. When the actual response is added to the API, our custom local resolver can be removed and the only change to the query/fragment is to remove the `@client` directive.
Read more about local state management with Apollo in the [Vue Apollo documentation](https://vue-apollo.netlify.app/guide/local-state.html#local-state).
### Using with Vuex
When Apollo Client is used within Vuex and fetched data is stored in the Vuex store, there is no need in keeping Apollo Client cache enabled. Otherwise we would have data from the API stored in two places - Vuex store and Apollo Client cache. More to say, with Apollo's default settings, a subsequent fetch from the GraphQL API could result in fetching data from Apollo cache (in the case where we have the same query and variables). To prevent this behavior, we need to disable Apollo Client cache passing a valid `fetchPolicy` option to its constructor:
When Apollo Client is used within Vuex and fetched data is stored in the Vuex store, there is no need to keep Apollo Client cache enabled. Otherwise we would have data from the API stored in two places - Vuex store and Apollo Client cache. With Apollo's default settings, a subsequent fetch from the GraphQL API could result in fetching data from Apollo cache (in the case where we have the same query and variables). To prevent this behavior, we need to disable Apollo Client cache by passing a valid `fetchPolicy` option to its constructor:
```javascript
import fetchPolicies from '~/graphql_shared/fetch_policy_constants';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -69,12 +69,15 @@ At the project level, the Security Dashboard displays the vulnerabilities merged
to **Security & Compliance > Security Dashboard**. By default, the Security Dashboard displays all
detected and confirmed vulnerabilities.
The Security Dashboard first displays the total number of vulnerabilities by severity (for example,
The Security Dashboard first displays the time at which the last pipeline completed on the project's
default branch. There's also a link to view this in more detail.
The Security Dashboard next displays the total number of vulnerabilities by severity (for example,
Critical, High, Medium, Low, Info, Unknown). Below this, a table shows each vulnerability's status, severity,
and description. Clicking a vulnerability takes you to its [Vulnerability Details](../vulnerabilities)
page to view more information about that vulnerability.
![Project Security Dashboard](img/project_security_dashboard_v13_4.png)
![Project Security Dashboard](img/project_security_dashboard_v13_5.png)
You can filter the vulnerabilities by one or more of the following:

View file

@ -542,9 +542,9 @@ Source:
For performance reasons, if a query returns more than 10,000 records, GitLab
doesn't return the following headers:
- `X-Total`.
- `X-Total-Pages`.
- `rel="last"` `Link`.
- `x-total`.
- `x-total-pages`.
- `rel="last"` `link`.
### Rack Attack initializer

View file

@ -565,7 +565,7 @@ over `https`, you must manually obtain and install TLS certificates.
The simplest way to accomplish this is to use Certbot to
[manually obtain Let's Encrypt certificates](https://knative.dev/docs/serving/using-a-tls-cert/#using-certbot-to-manually-obtain-let-s-encrypt-certificates).
Certbot is a free, open source software tool for automatically using Lets Encrypt
certificates on manually-administrated websites to enable HTTPS.
certificates on manually-administered websites to enable HTTPS.
The following instructions relate to installing and running Certbot on a Linux
server that has Python 3 installed, and may not work on other operating systems

View file

@ -73,6 +73,13 @@ module API
optional :not, type: Hash, desc: 'Parameters to negate' do
use :merge_requests_negatable_params
end
optional :deployed_before,
'Return merge requests deployed before the given date/time'
optional :deployed_after,
'Return merge requests deployed after the given date/time'
optional :environment,
'Returns merge requests deployed to the given environment'
end
params :optional_scope_param do

View file

@ -8981,6 +8981,12 @@ msgstr ""
msgid "Deployed to"
msgstr ""
msgid "Deployed-after"
msgstr ""
msgid "Deployed-before"
msgstr ""
msgid "Deploying to"
msgstr ""
@ -26019,6 +26025,9 @@ msgstr ""
msgid "The Prometheus server responded with \"bad request\". Please check your queries are correct and are supported in your Prometheus version. %{documentationLink}"
msgstr ""
msgid "The Security Dashboard shows the results of the last successful pipeline run on the default branch."
msgstr ""
msgid "The URL defined on the primary node that secondary nodes should use to contact it."
msgstr ""

View file

@ -0,0 +1,93 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Merge Requests > User filters by deployments', :js do
include FilteredSearchHelpers
let!(:project) { create(:project, :public, :repository) }
let!(:user) { project.creator }
let!(:gstg) { create(:environment, project: project, name: 'gstg') }
let!(:gprd) { create(:environment, project: project, name: 'gprd') }
let(:mr1) do
create(
:merge_request,
:simple,
:merged,
author: user,
source_project: project,
target_project: project
)
end
let(:mr2) do
create(
:merge_request,
:simple,
:merged,
author: user,
source_project: project,
target_project: project
)
end
let(:deploy1) do
create(
:deployment,
:success,
deployable: nil,
environment: gstg,
project: project,
sha: mr1.diff_head_sha,
finished_at: Time.utc(2020, 10, 1, 0, 0)
)
end
let(:deploy2) do
create(
:deployment,
:success,
deployable: nil,
environment: gprd,
project: project,
sha: mr2.diff_head_sha,
finished_at: Time.utc(2020, 10, 2, 0, 0)
)
end
before do
deploy1.link_merge_requests(MergeRequest.where(id: mr1.id))
deploy2.link_merge_requests(MergeRequest.where(id: mr2.id))
sign_in(user)
visit(project_merge_requests_path(project, state: :merged))
end
describe 'filtering by deployed-before' do
it 'applies the filter' do
input_filtered_search('deployed-before:=2020-10-02')
expect(page).to have_issuable_counts(open: 0, merged: 1, all: 1)
expect(page).to have_content mr1.title
end
end
describe 'filtering by deployed-after' do
it 'applies the filter' do
input_filtered_search('deployed-after:=2020-10-01')
expect(page).to have_issuable_counts(open: 0, merged: 1, all: 1)
expect(page).to have_content mr2.title
end
end
describe 'filtering by environment' do
it 'applies the filter' do
input_filtered_search('environment:=gstg')
expect(page).to have_issuable_counts(open: 0, merged: 1, all: 1)
expect(page).to have_content mr1.title
end
end
end

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EnvironmentNamesFinder do
describe '#execute' do
let!(:group) { create(:group) }
let!(:project1) { create(:project, :public, namespace: group) }
let!(:project2) { create(:project, :private, namespace: group) }
let!(:user) { create(:user) }
before do
create(:environment, name: 'gstg', project: project1)
create(:environment, name: 'gprd', project: project1)
create(:environment, name: 'gprd', project: project2)
create(:environment, name: 'gcny', project: project2)
end
context 'using a group and a group member' do
it 'returns environment names for all projects' do
group.add_developer(user)
names = described_class.new(group, user).execute
expect(names).to eq(%w[gcny gprd gstg])
end
end
context 'using a group and a guest' do
it 'returns environment names for all public projects' do
names = described_class.new(group, user).execute
expect(names).to eq(%w[gprd gstg])
end
end
context 'using a public project and a project member' do
it 'returns all the unique environment names' do
project1.team.add_developer(user)
names = described_class.new(project1, user).execute
expect(names).to eq(%w[gprd gstg])
end
end
context 'using a public project and a guest' do
it 'returns all the unique environment names' do
names = described_class.new(project1, user).execute
expect(names).to eq(%w[gprd gstg])
end
end
context 'using a private project and a guest' do
it 'returns all the unique environment names' do
names = described_class.new(project2, user).execute
expect(names).to be_empty
end
end
end
end

View file

@ -510,6 +510,83 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
end
end
context 'filtering by the merge request deployments' do
let(:gstg) { create(:environment, project: project4, name: 'gstg') }
let(:gprd) { create(:environment, project: project4, name: 'gprd') }
let(:mr1) do
create(
:merge_request,
:simple,
:merged,
author: user,
source_project: project4,
target_project: project4
)
end
let(:mr2) do
create(
:merge_request,
:simple,
:merged,
author: user,
source_project: project4,
target_project: project4
)
end
let(:deploy1) do
create(
:deployment,
:success,
deployable: nil,
environment: gstg,
project: project4,
sha: mr1.diff_head_sha,
finished_at: Time.utc(2020, 10, 1, 12, 0)
)
end
let(:deploy2) do
create(
:deployment,
:success,
deployable: nil,
environment: gprd,
project: project4,
sha: mr2.diff_head_sha,
finished_at: Time.utc(2020, 10, 2, 15, 0)
)
end
before do
deploy1.link_merge_requests(MergeRequest.where(id: mr1.id))
deploy2.link_merge_requests(MergeRequest.where(id: mr2.id))
end
it 'filters merge requests deployed to a given environment' do
mrs = described_class.new(user, environment: 'gstg').execute
expect(mrs).to eq([mr1])
end
it 'filters merge requests deployed before a given date' do
mrs =
described_class.new(user, deployed_before: '2020-10-02').execute
expect(mrs).to eq([mr1])
end
it 'filters merge requests deployed after a given date' do
mrs = described_class
.new(user, deployed_after: '2020-10-01 12:00')
.execute
expect(mrs).to eq([mr2])
end
end
end
describe '#row_count', :request_store do

View file

@ -856,6 +856,55 @@ RSpec.describe API::MergeRequests do
expect(json_response.first['id']).to eq merge_request_closed.id
end
context 'when filtering by deployments' do
let_it_be(:mr) do
create(:merge_request, :merged, source_project: project, target_project: project)
end
before do
env = create(:environment, project: project, name: 'staging')
deploy = create(:deployment, :success, environment: env, deployable: nil)
deploy.link_merge_requests(MergeRequest.where(id: mr.id))
end
it 'supports getting merge requests deployed to an environment' do
get api(endpoint_path, user), params: { environment: 'staging' }
expect(json_response.first['id']).to eq mr.id
end
it 'does not return merge requests for an environment without deployments' do
get api(endpoint_path, user), params: { environment: 'bla' }
expect_empty_array_response
end
it 'supports getting merge requests deployed after a date' do
get api(endpoint_path, user), params: { deployed_after: '1990-01-01' }
expect(json_response.first['id']).to eq mr.id
end
it 'does not return merge requests not deployed after a given date' do
get api(endpoint_path, user), params: { deployed_after: '2100-01-01' }
expect_empty_array_response
end
it 'supports getting merge requests deployed before a date' do
get api(endpoint_path, user), params: { deployed_before: '2100-01-01' }
expect(json_response.first['id']).to eq mr.id
end
it 'does not return merge requests not deployed before a given date' do
get api(endpoint_path, user), params: { deployed_before: '1990-01-01' }
expect_empty_array_response
end
end
context 'a project which enforces all discussions to be resolved' do
let_it_be(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: true) }