Commit 8dc67118 authored by Yorick Peterse's avatar Yorick Peterse

Merge canonical into security master

parents f65f0914 593568df
...@@ -395,8 +395,6 @@ class IssuableFinder ...@@ -395,8 +395,6 @@ class IssuableFinder
# We want CE users to be able to say "Issues not assigned to either PersonA nor PersonB" # We want CE users to be able to say "Issues not assigned to either PersonA nor PersonB"
if not_params.assignees.present? if not_params.assignees.present?
items.not_assigned_to(not_params.assignees) items.not_assigned_to(not_params.assignees)
elsif not_params.assignee_id? || not_params.assignee_username? # assignee not found
items.none
else else
items items
end end
......
...@@ -12,10 +12,10 @@ module IssueResolverArguments ...@@ -12,10 +12,10 @@ module IssueResolverArguments
argument :iids, [GraphQL::STRING_TYPE], argument :iids, [GraphQL::STRING_TYPE],
required: false, required: false,
description: 'List of IIDs of issues. For example, [1, 2].' description: 'List of IIDs of issues. For example, [1, 2].'
argument :label_name, GraphQL::STRING_TYPE.to_list_type, argument :label_name, [GraphQL::STRING_TYPE, null: true],
required: false, required: false,
description: 'Labels applied to this issue.' description: 'Labels applied to this issue.'
argument :milestone_title, GraphQL::STRING_TYPE.to_list_type, argument :milestone_title, [GraphQL::STRING_TYPE, null: true],
required: false, required: false,
description: 'Milestone applied to this issue.' description: 'Milestone applied to this issue.'
argument :author_username, GraphQL::STRING_TYPE, argument :author_username, GraphQL::STRING_TYPE,
...@@ -55,6 +55,10 @@ module IssueResolverArguments ...@@ -55,6 +55,10 @@ module IssueResolverArguments
as: :issue_types, as: :issue_types,
description: 'Filter issues by the given issue types.', description: 'Filter issues by the given issue types.',
required: false required: false
argument :not, Types::Issues::NegatedIssueFilterInputType,
description: 'List of negated params.',
prepare: ->(negated_args, ctx) { negated_args.to_h },
required: false
end end
def resolve_with_lookahead(**args) def resolve_with_lookahead(**args)
...@@ -69,11 +73,22 @@ module IssueResolverArguments ...@@ -69,11 +73,22 @@ module IssueResolverArguments
args[:iids] ||= [args.delete(:iid)].compact if args[:iid] args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
args[:attempt_project_search_optimizations] = true if args[:search].present? args[:attempt_project_search_optimizations] = true if args[:search].present?
prepare_assignee_username_params(args)
finder = IssuesFinder.new(current_user, args) finder = IssuesFinder.new(current_user, args)
continue_issue_resolve(parent, finder, **args) continue_issue_resolve(parent, finder, **args)
end end
def ready?(**args)
if args.slice(*mutually_exclusive_assignee_username_args).compact.size > 1
arg_str = mutually_exclusive_assignee_username_args.map { |x| x.to_s.camelize(:lower) }.join(', ')
raise Gitlab::Graphql::Errors::ArgumentError, "only one of [#{arg_str}] arguments is allowed at the same time."
end
super
end
class_methods do class_methods do
def resolver_complexity(args, child_complexity:) def resolver_complexity(args, child_complexity:)
complexity = super complexity = super
...@@ -82,4 +97,15 @@ module IssueResolverArguments ...@@ -82,4 +97,15 @@ module IssueResolverArguments
complexity complexity
end end
end end
private
def prepare_assignee_username_params(args)
args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present?
args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present?
end
def mutually_exclusive_assignee_username_args
[:assignee_usernames, :assignee_username]
end
end end
...@@ -4,7 +4,7 @@ module Types ...@@ -4,7 +4,7 @@ module Types
module Boards module Boards
# Common arguments that we can be used to filter boards epics and issues # Common arguments that we can be used to filter boards epics and issues
class BoardIssuableInputBaseType < BaseInputObject class BoardIssuableInputBaseType < BaseInputObject
argument :label_name, GraphQL::STRING_TYPE.to_list_type, argument :label_name, [GraphQL::STRING_TYPE, null: true],
required: false, required: false,
description: 'Filter by label name.' description: 'Filter by label name.'
......
...@@ -8,7 +8,7 @@ module Types ...@@ -8,7 +8,7 @@ module Types
required: false, required: false,
description: 'Filter by milestone title.' description: 'Filter by milestone title.'
argument :assignee_username, GraphQL::STRING_TYPE.to_list_type, argument :assignee_username, [GraphQL::STRING_TYPE, null: true],
required: false, required: false,
description: 'Filter by assignee username.' description: 'Filter by assignee username.'
......
# frozen_string_literal: true
module Types
module Issues
class NegatedIssueFilterInputType < BaseInputObject
graphql_name 'NegatedIssueFilterInput'
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'List of IIDs of issues to exclude. For example, [1, 2].'
argument :label_name, [GraphQL::STRING_TYPE],
required: false,
description: 'Labels not applied to this issue.'
argument :milestone_title, [GraphQL::STRING_TYPE],
required: false,
description: 'Milestone not applied to this issue.'
argument :assignee_usernames, [GraphQL::STRING_TYPE],
required: false,
description: 'Usernames of users not assigned to the issue.'
argument :assignee_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of a user not assigned to the issues.'
end
end
end
Types::Issues::NegatedIssueFilterInputType.prepend_if_ee('::EE::Types::Issues::NegatedIssueFilterInputType')
...@@ -52,8 +52,6 @@ module Pages ...@@ -52,8 +52,6 @@ module Pages
return if deployment.file.file_storage? && !Feature.enabled?(:pages_serve_with_zip_file_protocol, project, default_enabled: :yaml) return if deployment.file.file_storage? && !Feature.enabled?(:pages_serve_with_zip_file_protocol, project, default_enabled: :yaml)
return if deployment.migrated? && !Feature.enabled?(:pages_serve_from_migrated_zip, project, default_enabled: true)
global_id = ::Gitlab::GlobalId.build(deployment, id: deployment.id).to_s global_id = ::Gitlab::GlobalId.build(deployment, id: deployment.id).to_s
{ {
......
...@@ -22,29 +22,29 @@ ...@@ -22,29 +22,29 @@
= f.label :scopes, _('Scopes [Select 1 or more]'), class: 'label-bold' = f.label :scopes, _('Scopes [Select 1 or more]'), class: 'label-bold'
%fieldset.form-group.form-check %fieldset.form-group.form-check
= f.check_box :read_repository, class: 'form-check-input qa-deploy-token-read-repository' = f.check_box :read_repository, class: 'form-check-input qa-deploy-token-read-repository'
= label_tag ("deploy_token_read_repository"), 'read_repository', class: 'label-bold form-check-label' = f.label :read_repository, 'read_repository', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the repository.') .text-secondary= s_('DeployTokens|Allows read-only access to the repository.')
- if container_registry_enabled?(group_or_project) - if container_registry_enabled?(group_or_project)
%fieldset.form-group.form-check %fieldset.form-group.form-check
= f.check_box :read_registry, class: 'form-check-input qa-deploy-token-read-registry' = f.check_box :read_registry, class: 'form-check-input qa-deploy-token-read-registry'
= label_tag ("deploy_token_read_registry"), 'read_registry', class: 'label-bold form-check-label' = f.label :read_registry, 'read_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to registry images.') .text-secondary= s_('DeployTokens|Allows read-only access to registry images.')
%fieldset.form-group.form-check %fieldset.form-group.form-check
= f.check_box :write_registry, class: 'form-check-input' = f.check_box :write_registry, class: 'form-check-input'
= label_tag ("deploy_token_write_registry"), 'write_registry', class: 'label-bold form-check-label' = f.label :write_registry, 'write_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows write access to registry images.') .text-secondary= s_('DeployTokens|Allows write access to registry images.')
- if packages_registry_enabled?(group_or_project) - if packages_registry_enabled?(group_or_project)
%fieldset.form-group.form-check %fieldset.form-group.form-check
= f.check_box :read_package_registry, class: 'form-check-input' = f.check_box :read_package_registry, class: 'form-check-input'
= label_tag ("deploy_token_read_package_registry"), 'read_package_registry', class: 'label-bold form-check-label' = f.label :read_package_registry, 'read_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read access to the package registry.') .text-secondary= s_('DeployTokens|Allows read access to the package registry.')
%fieldset.form-group.form-check %fieldset.form-group.form-check
= f.check_box :write_package_registry, class: 'form-check-input' = f.check_box :write_package_registry, class: 'form-check-input'
= label_tag ("deploy_token_write_package_registry"), 'write_package_registry', class: 'label-bold form-check-label' = f.label :write_package_registry, 'write_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows write access to the package registry.') .text-secondary= s_('DeployTokens|Allows write access to the package registry.')
.gl-mt-3 .gl-mt-3
......
---
title: Remove pages_serve_from_migrated_zip feature flag
merge_request: 59002
author:
type: added
---
title: Support negated filtering of issues by iids, label_name, milestone_title, assignee_usernames and assignee_id in GraphQL
merge_request: 58154
author:
type: added
---
name: pages_serve_from_migrated_zip
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52573
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300021
milestone: '13.9'
type: development
group: group::release
default_enabled: true
...@@ -5,16 +5,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -5,16 +5,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto type: howto
--- ---
# Docker Registry for a secondary node **(PREMIUM SELF)** # Docker Registry for a secondary site **(PREMIUM SELF)**
You can set up a [Docker Registry](https://docs.docker.com/registry/) on your You can set up a [Docker Registry](https://docs.docker.com/registry/) on your
**secondary** Geo node that mirrors the one on the **primary** Geo node. **secondary** Geo site that mirrors the one on the **primary** Geo site.
## Storage support ## Storage support
Docker Registry currently supports a few types of storage. If you choose a Docker Registry currently supports a few types of storage. If you choose a
distributed storage (`azure`, `gcs`, `s3`, `swift`, or `oss`) for your Docker distributed storage (`azure`, `gcs`, `s3`, `swift`, or `oss`) for your Docker
Registry on the **primary** node, you can use the same storage for a **secondary** Registry on the **primary** site, you can use the same storage for a **secondary**
Docker Registry as well. For more information, read the Docker Registry as well. For more information, read the
[Load balancing considerations](https://docs.docker.com/registry/deploying/#load-balancing-considerations) [Load balancing considerations](https://docs.docker.com/registry/deploying/#load-balancing-considerations)
when deploying the Registry, and how to set up the storage driver for the GitLab when deploying the Registry, and how to set up the storage driver for the GitLab
...@@ -24,22 +24,22 @@ integrated [Container Registry](../../packages/container_registry.md#use-object- ...@@ -24,22 +24,22 @@ integrated [Container Registry](../../packages/container_registry.md#use-object-
You can enable a storage-agnostic replication so it You can enable a storage-agnostic replication so it
can be used for cloud or local storage. Whenever a new image is pushed to the can be used for cloud or local storage. Whenever a new image is pushed to the
**primary** node, each **secondary** node will pull it to its own container **primary** site, each **secondary** site will pull it to its own container
repository. repository.
To configure Docker Registry replication: To configure Docker Registry replication:
1. Configure the [**primary** node](#configure-primary-node). 1. Configure the [**primary** site](#configure-primary-site).
1. Configure the [**secondary** node](#configure-secondary-node). 1. Configure the [**secondary** site](#configure-secondary-site).
1. Verify Docker Registry [replication](#verify-replication). 1. Verify Docker Registry [replication](#verify-replication).
### Configure **primary** node ### Configure **primary** site
Make sure that you have Container Registry set up and working on Make sure that you have Container Registry set up and working on
the **primary** node before following the next steps. the **primary** site before following the next steps.
We need to make Docker Registry send notification events to the We need to make Docker Registry send notification events to the
**primary** node. **primary** site.
1. SSH into your GitLab **primary** server and login as root: 1. SSH into your GitLab **primary** server and login as root:
...@@ -85,27 +85,29 @@ We need to make Docker Registry send notification events to the ...@@ -85,27 +85,29 @@ We need to make Docker Registry send notification events to the
gitlab-ctl reconfigure gitlab-ctl reconfigure
``` ```
### Configure **secondary** node ### Configure **secondary** site
Make sure you have Container Registry set up and working on Make sure you have Container Registry set up and working on
the **secondary** node before following the next steps. the **secondary** site before following the next steps.
The following steps should be done on each **secondary** node you're The following steps should be done on each **secondary** site you're
expecting to see the Docker images replicated. expecting to see the Docker images replicated.
Because we need to allow the **secondary** node to communicate securely with Because we need to allow the **secondary** site to communicate securely with
the **primary** node Container Registry, we need to have a single key the **primary** site Container Registry, we need to have a single key
pair for all the nodes. The **secondary** node will use this key to pair for all the sites. The **secondary** site will use this key to
generate a short-lived JWT that is pull-only-capable to access the generate a short-lived JWT that is pull-only-capable to access the
**primary** node Container Registry. **primary** site Container Registry.
1. SSH into the **secondary** node and login as the `root` user: For each application node on the **secondary** site:
1. SSH into the node and login as the `root` user:
```shell ```shell
sudo -i sudo -i
``` ```
1. Copy `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` from the **primary** to the **secondary** node. 1. Copy `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` from the **primary** to the node.
1. Edit `/etc/gitlab/gitlab.rb`: 1. Edit `/etc/gitlab/gitlab.rb`:
...@@ -114,7 +116,7 @@ generate a short-lived JWT that is pull-only-capable to access the ...@@ -114,7 +116,7 @@ generate a short-lived JWT that is pull-only-capable to access the
gitlab_rails['geo_registry_replication_primary_api_url'] = 'https://primary.example.com:5050/' # Primary registry address, it will be used by the secondary node to directly communicate to primary registry gitlab_rails['geo_registry_replication_primary_api_url'] = 'https://primary.example.com:5050/' # Primary registry address, it will be used by the secondary node to directly communicate to primary registry
``` ```
1. Reconfigure the **secondary** node for the change to take effect: 1. Reconfigure the node for the change to take effect:
```shell ```shell
gitlab-ctl reconfigure gitlab-ctl reconfigure
...@@ -123,6 +125,6 @@ generate a short-lived JWT that is pull-only-capable to access the ...@@ -123,6 +125,6 @@ generate a short-lived JWT that is pull-only-capable to access the
### Verify replication ### Verify replication
To verify Container Registry replication is working, go to **Admin Area > Geo** To verify Container Registry replication is working, go to **Admin Area > Geo**
(`/admin/geo/nodes`) on the **secondary** node. (`/admin/geo/nodes`) on the **secondary** site.
The initial replication, or "backfill", will probably still be in progress. The initial replication, or "backfill", will probably still be in progress.
You can monitor the synchronization process on each Geo node from the **primary** node's **Geo Nodes** dashboard in your browser. You can monitor the synchronization process on each Geo site from the **primary** site's **Geo Nodes** dashboard in your browser.
...@@ -19,7 +19,7 @@ module EE ...@@ -19,7 +19,7 @@ module EE
end end
def weights? def weights?
params[:weight].present? && params[:weight] != ::Issue::WEIGHT_ALL params[:weight].present? && params[:weight].to_s.casecmp(::Issue::WEIGHT_ALL) != 0
end end
def filter_by_no_weight? def filter_by_no_weight?
......
...@@ -7,13 +7,15 @@ module EE ...@@ -7,13 +7,15 @@ module EE
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
prepended do prepended do
argument :iteration_id, ::GraphQL::ID_TYPE.to_list_type, argument :iteration_id, [::GraphQL::ID_TYPE, null: true],
required: false, required: false,
description: 'Iterations applied to the issue.' description: 'Iterations applied to the issue.'
argument :epic_id, GraphQL::STRING_TYPE, argument :epic_id, GraphQL::STRING_TYPE,
required: false, required: false,
description: 'ID of an epic associated with the issues, "none" and "any" values are supported.' description: 'ID of an epic associated with the issues, "none" and "any" values are supported.'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Weight applied to the issue, "none" and "any" values are supported.'
end end
private private
......
# frozen_string_literal: true
module EE
module Types
module Issues
module NegatedIssueFilterInputType
extend ActiveSupport::Concern
prepended do
argument :epic_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of an epic not associated with the issues.'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Weight not applied to the issue.'
end
end
end
end
end
---
title: Support negated filtering of issues by epic_id and weight in GraphQL
merge_request: 58154
author:
type: added
...@@ -11,36 +11,39 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -11,36 +11,39 @@ RSpec.describe Resolvers::IssuesResolver do
context "with a project" do context "with a project" do
describe '#resolve' do describe '#resolve' do
let_it_be(:epic1) { create :epic, group: group }
let_it_be(:epic2) { create :epic, group: group }
let_it_be(:iteration1) { create(:iteration, group: group, start_date: 2.weeks.ago, due_date: 1.week.ago) }
let_it_be(:current_iteration) { create(:iteration, :started, group: group, start_date: Date.today, due_date: 1.day.from_now) }
let_it_be(:issue1) { create :issue, project: project, epic: epic1, iteration: iteration1 }
let_it_be(:issue2) { create :issue, project: project, epic: epic2, weight: 1 }
let_it_be(:issue3) { create :issue, project: project, weight: 3, iteration: current_iteration }
let_it_be(:issue4) { create :issue, :published, project: project }
before do before do
project.add_developer(current_user) project.add_developer(current_user)
end end
describe 'sorting' do describe 'sorting' do
context 'when sorting by weight' do context 'when sorting by weight' do
let_it_be(:weight_issue1) { create(:issue, project: project, weight: 5) }
let_it_be(:weight_issue2) { create(:issue, project: project, weight: nil) }
let_it_be(:weight_issue3) { create(:issue, project: project, weight: 1) }
let_it_be(:weight_issue4) { create(:issue, project: project, weight: nil) }
it 'sorts issues ascending' do it 'sorts issues ascending' do
expect(resolve_issues(sort: :weight_asc).to_a).to eq [weight_issue3, weight_issue1, weight_issue4, weight_issue2] expect(resolve_issues(sort: :weight_asc).to_a).to eq [issue2, issue3, issue4, issue1]
end end
it 'sorts issues descending' do it 'sorts issues descending' do
expect(resolve_issues(sort: :weight_desc).to_a).to eq [weight_issue1, weight_issue3, weight_issue4, weight_issue2] expect(resolve_issues(sort: :weight_desc).to_a).to eq [issue3, issue2, issue4, issue1]
end end
end end
context 'when sorting by published' do context 'when sorting by published' do
let_it_be(:not_published) { create(:issue, project: project) }
let_it_be(:published) { create(:issue, :published, project: project) }
it 'sorts issues ascending' do it 'sorts issues ascending' do
expect(resolve_issues(sort: :published_asc).to_a).to eq [not_published, published] expect(resolve_issues(sort: :published_asc).to_a).to eq [issue3, issue2, issue1, issue4]
end end
it 'sorts issues descending' do it 'sorts issues descending' do
expect(resolve_issues(sort: :published_desc).to_a).to eq [published, not_published] expect(resolve_issues(sort: :published_desc).to_a).to eq [issue4, issue3, issue2, issue1]
end end
end end
...@@ -64,32 +67,56 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -64,32 +67,56 @@ RSpec.describe Resolvers::IssuesResolver do
end end
describe 'filtering by iteration' do describe 'filtering by iteration' do
let_it_be(:iteration1) { create(:iteration, group: group) }
let_it_be(:issue_with_iteration) { create(:issue, project: project, iteration: iteration1) }
let_it_be(:issue_without_iteration) { create(:issue, project: project) }
it 'returns issues with iteration' do it 'returns issues with iteration' do
expect(resolve_issues(iteration_id: [iteration1.id])).to contain_exactly(issue_with_iteration) expect(resolve_issues(iteration_id: [iteration1.id.to_s])).to contain_exactly(issue1)
end end
end end
describe 'filter by epic' do describe 'filter by epic' do
let_it_be(:epic) { create :epic, group: group }
let_it_be(:epic2) { create :epic, group: group }
let_it_be(:issue1) { create :issue, project: project, epic: epic }
let_it_be(:issue2) { create :issue, project: project, epic: epic2 }
let_it_be(:issue3) { create :issue, project: project }
it 'returns issues without epic when epic_id is "none"' do it 'returns issues without epic when epic_id is "none"' do
expect(resolve_issues(epic_id: 'none')).to match_array([issue3]) expect(resolve_issues(epic_id: 'none')).to contain_exactly(issue4, issue3)
end end
it 'returns issues with any epic when epic_id is "any"' do it 'returns issues with any epic when epic_id is "any"' do
expect(resolve_issues(epic_id: 'any')).to match_array([issue1, issue2]) expect(resolve_issues(epic_id: 'any')).to contain_exactly(issue1, issue2)
end end
it 'returns issues with any epic when epic_id is specific' do it 'returns issues with any epic when epic_id is specific' do
expect(resolve_issues(epic_id: epic.id)).to match_array([issue1]) expect(resolve_issues(epic_id: epic1.id.to_s)).to contain_exactly(issue1)
end
end
describe 'filter by weight' do
context 'when filtering by any weight' do
it 'only returns issues that have a weight assigned' do
expect(resolve_issues(weight: 'any')).to contain_exactly(issue2, issue3)
end
end
context 'when filtering by no weight' do
it 'only returns issues that have no weight assigned' do
expect(resolve_issues(weight: 'none')).to contain_exactly(issue1, issue4)
end
end
context 'when filtering by specific weight' do
it 'only returns issues that have the specified weight assigned' do
expect(resolve_issues(weight: '3')).to contain_exactly(issue3)
end
end
end
describe 'filtering by negated params' do
describe 'filter by negated epic' do
it 'returns issues without the specified epic_id' do
expect(resolve_issues(not: { epic_id: epic2.id.to_s })).to contain_exactly(issue1, issue3, issue4)
end
end
describe 'filtering by negated weight' do
it 'only returns issues that do not have the specified weight assigned' do
expect(resolve_issues(not: { weight: '3' })).to contain_exactly(issue1, issue2, issue4)
end
end end
end end
end end
......
...@@ -49,6 +49,13 @@ RSpec.describe IssuesFinder do ...@@ -49,6 +49,13 @@ RSpec.describe IssuesFinder do
let(:expected_issuables) { [issue3, issue4] } let(:expected_issuables) { [issue3, issue4] }
end end
context 'when assignee_id does not exist' do
it_behaves_like 'assignee NOT ID filter' do
let(:params) { { not: { assignee_id: -100 } } }
let(:expected_issuables) { [issue1, issue2, issue3, issue4, issue5] }
end
end
context 'filter by username' do context 'filter by username' do
let_it_be(:user3) { create(:user) } let_it_be(:user3) { create(:user) }
...@@ -71,6 +78,17 @@ RSpec.describe IssuesFinder do ...@@ -71,6 +78,17 @@ RSpec.describe IssuesFinder do
let(:params) { { not: { assignee_username: [user.username, user2.username] } } } let(:params) { { not: { assignee_username: [user.username, user2.username] } } }
let(:expected_issuables) { [issue3, issue4] } let(:expected_issuables) { [issue3, issue4] }
end end
context 'when assignee_username does not exist' do
it_behaves_like 'assignee NOT username filter' do
before do
issue2.assignees = [user2]
end
let(:params) { { not: { assignee_username: 'non_existent_username' } } }
let(:expected_issuables) { [issue1, issue2, issue3, issue4, issue5] }
end
end
end end
it_behaves_like 'no assignee filter' do it_behaves_like 'no assignee filter' do
......
...@@ -69,6 +69,14 @@ RSpec.describe Resolvers::IssueStatusCountsResolver do ...@@ -69,6 +69,14 @@ RSpec.describe Resolvers::IssueStatusCountsResolver do
expect(result.closed).to eq 1 expect(result.closed).to eq 1
end end
context 'when both assignee_username and assignee_usernames are provided' do
it 'raises a mutually exclusive filter error' do
expect do
resolve_issue_status_counts(assignee_usernames: [current_user.username], assignee_username: current_user.username)
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.')
end
end
private private
def resolve_issue_status_counts(args = {}, context = { current_user: current_user }) def resolve_issue_status_counts(args = {}, context = { current_user: current_user })
......
...@@ -46,10 +46,6 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -46,10 +46,6 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(milestone_title: [milestone.title])).to contain_exactly(issue1) expect(resolve_issues(milestone_title: [milestone.title])).to contain_exactly(issue1)
end end
it 'filters by assignee_username' do
expect(resolve_issues(assignee_username: [assignee.username])).to contain_exactly(issue2)
end
it 'filters by two assignees' do it 'filters by two assignees' do
assignee2 = create(:user) assignee2 = create(:user)
issue2.update!(assignees: [assignee, assignee2]) issue2.update!(assignees: [assignee, assignee2])
...@@ -78,6 +74,24 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -78,6 +74,24 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
end end
describe 'filters by assignee_username' do
it 'filters by assignee_username' do
expect(resolve_issues(assignee_username: [assignee.username])).to contain_exactly(issue2)
end
it 'filters by assignee_usernames' do
expect(resolve_issues(assignee_usernames: [assignee.username])).to contain_exactly(issue2)
end
context 'when both assignee_username and assignee_usernames are provided' do
it 'raises a mutually exclusive filter error' do
expect do
resolve_issues(assignee_usernames: [assignee.username], assignee_username: assignee.username)
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.')
end
end
end
describe 'filters by created_at' do describe 'filters by created_at' do
it 'filters by created_before' do it 'filters by created_before' do
expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
...@@ -144,6 +158,29 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -144,6 +158,29 @@ RSpec.describe Resolvers::IssuesResolver do
end end
end end
describe 'filters by negated params' do
it 'returns issues without the specified iids' do
expect(resolve_issues(not: { iids: [issue1.iid] })).to contain_exactly(issue2)
end
it 'returns issues without the specified label names' do
expect(resolve_issues(not: { label_name: [label1.title] })).to be_empty
expect(resolve_issues(not: { label_name: [label2.title] })).to contain_exactly(issue1)
end
it 'returns issues without the specified milestone' do
expect(resolve_issues(not: { milestone_title: [milestone.title] })).to contain_exactly(issue2)
end
it 'returns issues without the specified assignee_usernames' do
expect(resolve_issues(not: { assignee_usernames: [assignee.username] })).to contain_exactly(issue1)
end
it 'returns issues without the specified assignee_id' do
expect(resolve_issues(not: { assignee_id: [assignee.id] })).to contain_exactly(issue1)
end
end
describe 'sorting' do describe 'sorting' do
context 'when sorting by created' do context 'when sorting by created' do
it 'sorts issues ascending' do it 'sorts issues ascending' do
......
...@@ -136,14 +136,6 @@ RSpec.describe Pages::LookupPath do ...@@ -136,14 +136,6 @@ RSpec.describe Pages::LookupPath do
) )
end end
end end
context 'when pages_serve_from_migrated_zip feature flag is disabled' do
before do
stub_feature_flags(pages_serve_from_migrated_zip: false)
end
include_examples 'uses disk storage'
end
end end
end end
end end
......
...@@ -12,6 +12,7 @@ RSpec.describe 'getting an issue list for a project' do ...@@ -12,6 +12,7 @@ RSpec.describe 'getting an issue list for a project' do
let_it_be(:issues, reload: true) { [issue_a, issue_b] } let_it_be(:issues, reload: true) { [issue_a, issue_b] }
let(:issues_data) { graphql_data['project']['issues']['edges'] } let(:issues_data) { graphql_data['project']['issues']['edges'] }
let(:issue_filter_params) { {} }
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
...@@ -27,7 +28,7 @@ RSpec.describe 'getting an issue list for a project' do ...@@ -27,7 +28,7 @@ RSpec.describe 'getting an issue list for a project' do
graphql_query_for( graphql_query_for(
'project', 'project',
{ 'fullPath' => project.full_path }, { 'fullPath' => project.full_path },
query_graphql_field('issues', {}, fields) query_graphql_field('issues', issue_filter_params, fields)
) )
end end
...@@ -50,6 +51,16 @@ RSpec.describe 'getting an issue list for a project' do ...@@ -50,6 +51,16 @@ RSpec.describe 'getting an issue list for a project' do
expect(issues_data[1]['node']['discussionLocked']).to eq(true) expect(issues_data[1]['node']['discussionLocked']).to eq(true)
end end
context 'when both assignee_username filters are provided' do
let(:issue_filter_params) { { assignee_username: current_user.username, assignee_usernames: [current_user.username] } }
it 'returns a mutually exclusive param error' do
post_graphql(query, current_user: current_user)
expect_graphql_errors_to_include('only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.')
end
end
context 'when limiting the number of results' do context 'when limiting the number of results' do
let(:query) do let(:query) do
<<~GQL <<~GQL
......
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