diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 39359d7b827..dde08b15bc3 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -77,7 +77,7 @@ review-build-cng: variables: HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" - GITLAB_HELM_CHART_REF: "v5.10.0" + GITLAB_HELM_CHART_REF: "41d7632d9eba84f5816d808b98ccf3f5623e9fb5" environment: name: review/${CI_COMMIT_REF_SLUG}${FREQUENCY} url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN} diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 1423cb7383f..ccc33aa1d18 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -76,7 +76,7 @@ .if-merge-request-labels-jh-contribution: &if-merge-request-labels-jh-contribution if: '$CI_MERGE_REQUEST_LABELS =~ /JiHu contribution/' -.if-merge-request-labels-run-search-tests: &if-merge-request-labels-run-search-tests +.if-merge-request-labels-group-global-search: &if-merge-request-labels-group-global-search if: '$CI_MERGE_REQUEST_LABELS =~ /group::global search/' .if-security-merge-request: &if-security-merge-request @@ -263,7 +263,7 @@ - "config.ru" # List explicitly all the app/ dirs that are backend (i.e. all except app/assets). - "{,ee/,jh/}{app/channels,app/controllers,app/finders,app/graphql,app/helpers,app/mailers,app/models,app/policies,app/presenters,app/serializers,app/services,app/uploaders,app/validators,app/views,app/workers}/**/*" - - "{,ee/,jh/}{bin,cable,config,db,generator_templates,lib}/**/*" + - "{,ee/,jh/}{bin,config,db,generator_templates,lib}/**/*" - "{,ee/,jh/}spec/**/*" # CI changes - ".gitlab-ci.yml" @@ -273,6 +273,14 @@ # Mapped patterns (see tests.yml) - "data/whats_new/*.yml" +.search-backend-patterns: &search-backend-patterns + - "{,jh/}Gemfile.lock" + - "GITLAB_ELASTICSEARCH_INDEXER_VERSION" + # List explicitly all the app/ dirs that are backend (i.e. all except app/assets). + - "{,ee/,jh/}{app/channels,app/controllers,app/finders,app/graphql,app/helpers,app/mailers,app/models,app/policies,app/presenters,app/serializers,app/services,app/uploaders,app/validators,app/views,app/workers}/**/*" + - "{,ee/,jh/}{bin,config,db,generator_templates,lib}/**/*" + - "{,ee/,jh/}spec/**/*" + # DB patterns + .ci-patterns .db-patterns: &db-patterns - "{,ee/,jh/}{,spec/}{db,migrations}/**/*" @@ -521,7 +529,8 @@ .rails:rules:run-search-tests: rules: - - <<: *if-merge-request-labels-run-search-tests + - <<: *if-merge-request-labels-group-global-search + changes: *search-backend-patterns .rails:rules:ee-and-foss-default-rules: rules: diff --git a/.rubocop_todo/layout/first_hash_element_indentation.yml b/.rubocop_todo/layout/first_hash_element_indentation.yml index 6d6d99ab869..6000d3ea1af 100644 --- a/.rubocop_todo/layout/first_hash_element_indentation.yml +++ b/.rubocop_todo/layout/first_hash_element_indentation.yml @@ -208,7 +208,6 @@ Layout/FirstHashElementIndentation: - 'ee/spec/serializers/issues/linked_issue_feature_flag_entity_spec.rb' - 'ee/spec/serializers/license_entity_spec.rb' - 'ee/spec/serializers/linked_feature_flag_issue_entity_spec.rb' - - 'ee/spec/services/alert_management/network_alert_service_spec.rb' - 'ee/spec/services/analytics/cycle_analytics/data_loader_service_spec.rb' - 'ee/spec/services/app_sec/dast/profile_schedules/audit/update_service_spec.rb' - 'ee/spec/services/app_sec/dast/profiles/audit/update_service_spec.rb' diff --git a/.rubocop_todo/layout/hash_alignment.yml b/.rubocop_todo/layout/hash_alignment.yml index 137f6cb7a05..d7228ff381e 100644 --- a/.rubocop_todo/layout/hash_alignment.yml +++ b/.rubocop_todo/layout/hash_alignment.yml @@ -426,7 +426,6 @@ Layout/HashAlignment: - 'ee/spec/requests/projects/security/dast_site_profiles_controller_spec.rb' - 'ee/spec/requests/rack_attack_global_spec.rb' - 'ee/spec/serializers/integrations/zentao_serializers/issue_entity_spec.rb' - - 'ee/spec/services/alert_management/network_alert_service_spec.rb' - 'ee/spec/services/app_sec/dast/profiles/create_associations_service_spec.rb' - 'ee/spec/services/audit_events/protected_branch_audit_event_service_spec.rb' - 'ee/spec/services/ci/create_pipeline_service/cross_needs_artifacts_spec.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 974883663ae..b718a6772a3 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -2776,7 +2776,6 @@ Layout/LineLength: - 'ee/spec/serializers/vulnerabilities/finding_reports_comparer_entity_spec.rb' - 'ee/spec/serializers/vulnerabilities/finding_serializer_spec.rb' - 'ee/spec/services/alert_management/metric_images/upload_service_spec.rb' - - 'ee/spec/services/alert_management/network_alert_service_spec.rb' - 'ee/spec/services/alert_management/process_prometheus_alert_service_spec.rb' - 'ee/spec/services/analytics/cycle_analytics/consistency_check_service_spec.rb' - 'ee/spec/services/analytics/cycle_analytics/data_loader_service_spec.rb' diff --git a/.rubocop_todo/rspec/any_instance_of.yml b/.rubocop_todo/rspec/any_instance_of.yml index 51bc272cd63..55d2627402c 100644 --- a/.rubocop_todo/rspec/any_instance_of.yml +++ b/.rubocop_todo/rspec/any_instance_of.yml @@ -85,7 +85,6 @@ RSpec/AnyInstanceOf: - ee/spec/requests/groups_controller_spec.rb - ee/spec/requests/omniauth_kerberos_spnego_spec.rb - ee/spec/requests/repositories/git_http_controller_spec.rb - - ee/spec/services/alert_management/network_alert_service_spec.rb - ee/spec/services/ci/expire_pipeline_cache_service_spec.rb - ee/spec/services/ci/run_dast_scan_service_spec.rb - ee/spec/services/ee/git/branch_push_service_spec.rb diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb index 762058acf3d..c2208e469c5 100644 --- a/app/graphql/mutations/container_expiration_policies/update.rb +++ b/app/graphql/mutations/container_expiration_policies/update.rb @@ -7,7 +7,7 @@ module Mutations include FindsProject - authorize :destroy_container_image + authorize :admin_container_image argument :project_path, GraphQL::Types::ID, diff --git a/app/graphql/mutations/notes/reposition_image_diff_note.rb b/app/graphql/mutations/notes/reposition_image_diff_note.rb index ec68f077c84..9c3377b1f96 100644 --- a/app/graphql/mutations/notes/reposition_image_diff_note.rb +++ b/app/graphql/mutations/notes/reposition_image_diff_note.rb @@ -26,6 +26,7 @@ module Mutations def resolve(note:, position:) authorize!(note) + position = position.to_h.compact pre_update_checks!(note, position) updated_note = ::Notes::UpdateService.new( @@ -46,7 +47,7 @@ module Mutations # just a `DiffNote` with a particular kind of `Gitlab::Diff::Position`. # In addition to accepting a `DiffNote` Global ID we also need to # perform this check. - def pre_update_checks!(note, position) + def pre_update_checks!(note, _position) unless note.position&.on_image? raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Resource is not an ImageDiffNote' diff --git a/app/graphql/mutations/notes/update/image_diff_note.rb b/app/graphql/mutations/notes/update/image_diff_note.rb index 284c0f1bb20..8e4dbb06d46 100644 --- a/app/graphql/mutations/notes/update/image_diff_note.rb +++ b/app/graphql/mutations/notes/update/image_diff_note.rb @@ -54,7 +54,7 @@ module Mutations original_position = note.position.to_h - Gitlab::Diff::Position.new(original_position.merge(args[:position])) + Gitlab::Diff::Position.new(original_position.merge(args[:position].to_h)) end end end diff --git a/app/graphql/resolvers/concerns/time_frame_arguments.rb b/app/graphql/resolvers/concerns/time_frame_arguments.rb index 0ec3c642f29..87b7a96045c 100644 --- a/app/graphql/resolvers/concerns/time_frame_arguments.rb +++ b/app/graphql/resolvers/concerns/time_frame_arguments.rb @@ -24,10 +24,13 @@ module TimeFrameArguments # TODO: remove when the start_date and end_date arguments are removed def validate_timeframe_params!(args) return unless %i[start_date end_date timeframe].any? { |k| args[k].present? } - return if args[:timeframe] && %i[start_date end_date].all? { |k| args[k].nil? } + + # the timeframe is passed in as a TimeframeInputType + timeframe = args[:timeframe].to_h if args[:timeframe] + return if timeframe && %i[start_date end_date].all? { |k| args[k].nil? } error_message = - if args[:timeframe].present? + if timeframe.present? "startDate and endDate are deprecated in favor of timeframe. Please use only timeframe." elsif args[:start_date].nil? || args[:end_date].nil? "Both startDate and endDate must be present." @@ -42,7 +45,7 @@ module TimeFrameArguments def transform_timeframe_parameters(args) if args[:timeframe] - args[:timeframe].transform_keys { |k| :"#{k}_date" } + args[:timeframe].to_h.transform_keys { |k| :"#{k}_date" } else args.slice(:start_date, :end_date) end diff --git a/app/graphql/types/boards/board_issue_input_type.rb b/app/graphql/types/boards/board_issue_input_type.rb index afa66c1c510..0dd7fbc87da 100644 --- a/app/graphql/types/boards/board_issue_input_type.rb +++ b/app/graphql/types/boards/board_issue_input_type.rb @@ -7,7 +7,6 @@ module Types argument :not, NegatedBoardIssueInputType, required: false, - prepare: ->(negated_args, ctx) { negated_args.to_h }, description: 'List of negated arguments.' argument :search, GraphQL::Types::String, diff --git a/app/graphql/types/container_expiration_policy_type.rb b/app/graphql/types/container_expiration_policy_type.rb index 0e9534be684..22020aff36b 100644 --- a/app/graphql/types/container_expiration_policy_type.rb +++ b/app/graphql/types/container_expiration_policy_type.rb @@ -6,7 +6,7 @@ module Types description 'A tag expiration policy designed to keep only the images that matter most' - authorize :destroy_container_image + authorize :admin_container_image field :cadence, Types::ContainerExpirationPolicyCadenceEnum, null: false, description: 'This container expiration policy schedule.' field :created_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was created.' diff --git a/app/graphql/types/notes/update_diff_image_position_input_type.rb b/app/graphql/types/notes/update_diff_image_position_input_type.rb index 0c6e4a16434..913d24cb513 100644 --- a/app/graphql/types/notes/update_diff_image_position_input_type.rb +++ b/app/graphql/types/notes/update_diff_image_position_input_type.rb @@ -28,6 +28,8 @@ module Types raise GraphQL::ExecutionError, "At least one property of `#{self.class.graphql_name}` must be set" end end + + super end end end diff --git a/app/graphql/types/range_input_type.rb b/app/graphql/types/range_input_type.rb index 9580b37d6c0..15b8121da93 100644 --- a/app/graphql/types/range_input_type.rb +++ b/app/graphql/types/range_input_type.rb @@ -21,7 +21,7 @@ module Types raise ::Gitlab::Graphql::Errors::ArgumentError, 'start must be before end' end - to_h + super end end end diff --git a/app/services/container_expiration_policies/update_service.rb b/app/services/container_expiration_policies/update_service.rb index 2f34941d692..32ee47457b7 100644 --- a/app/services/container_expiration_policies/update_service.rb +++ b/app/services/container_expiration_policies/update_service.rb @@ -28,7 +28,7 @@ module ContainerExpirationPolicies end def allowed? - Ability.allowed?(current_user, :destroy_container_image, @container) + Ability.allowed?(current_user, :admin_container_image, @container) end def container_expiration_policy_params diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md index cdbc674e0a5..dca71413564 100644 --- a/doc/development/internal_api/index.md +++ b/doc/development/internal_api/index.md @@ -478,28 +478,6 @@ curl --request POST --header "Gitlab-Kas-Api-Request: " --header "Con --data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics" ``` -### GitLab agent alert metrics - -Called from GitLab agent server (KAS) to save alerts derived from Cilium on Kubernetes -Cluster. - -| Attribute | Type | Required | Description | -|:----------|:-------|:---------|:------------| -| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../../operations/incident_management/integrations.md#customize-the-alert-payload-outside-of-gitlab). | - -```plaintext -POST internal/kubernetes/modules/cilium_alert -``` - -Example Request: - -```shell -curl --request POST --header "Gitlab-Kas-Api-Request: " \ - --header "Authorization: Bearer " --header "Content-Type: application/json" \ - --data '"{\"alert\":{\"title\":\"minimal\",\"message\":\"network problem\",\"evalMatches\":[{\"value\":1,\"metric\":\"Count\",\"tags\":{}}]}}"' \ - "http://localhost:3000/api/v4/internal/kubernetes/modules/cilium_alert" -``` - ### Create Starboard vulnerability Called from the GitLab agent server (`kas`) to create a security vulnerability diff --git a/doc/user/packages/container_registry/reduce_container_registry_storage.md b/doc/user/packages/container_registry/reduce_container_registry_storage.md index b190ff3ae2e..7b52a6a8ce3 100644 --- a/doc/user/packages/container_registry/reduce_container_registry_storage.md +++ b/doc/user/packages/container_registry/reduce_container_registry_storage.md @@ -24,6 +24,7 @@ which doesn't include the Container Registry. To track work on this, see the epi > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15398) in GitLab 12.8. > - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/218737) from "expiration policy" to "cleanup policy" in GitLab 13.2. +> - [Required permissions](https://gitlab.com/gitlab-org/gitlab/-/issues/350682) changed from developer to maintainer in GitLab 15.0. The cleanup policy is a scheduled job you can use to remove tags from the Container Registry. For the project where it's defined, tags matching the regex pattern are removed. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 134bc0c4206..f2077cc92ca 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -72,7 +72,7 @@ The following table lists project permissions available for each role: | [Clusters](infrastructure/clusters/index.md):
View [pod logs](project/clusters/kubernetes_pod_logs.md) | | | ✓ | ✓ | ✓ | | [Clusters](infrastructure/clusters/index.md):
View clusters | | | ✓ | ✓ | ✓ | | [Clusters](infrastructure/clusters/index.md):
Manage clusters | | | | ✓ | ✓ | -| [Container Registry](packages/container_registry/index.md):
Create, edit, delete [cleanup policies](packages/container_registry/index.md#delete-images-by-using-a-cleanup-policy) | | | ✓ | ✓ | ✓ | +| [Container Registry](packages/container_registry/index.md):
Create, edit, delete [cleanup policies](packages/container_registry/index.md#delete-images-by-using-a-cleanup-policy) | | | | ✓ | ✓ | | [Container Registry](packages/container_registry/index.md):
Push an image to the Container Registry | | | ✓ | ✓ | ✓ | | [Container Registry](packages/container_registry/index.md):
Pull an image from the Container Registry | ✓ (*20*) | ✓ (*20*) | ✓ | ✓ | ✓ | | [Container Registry](packages/container_registry/index.md):
Remove a Container Registry image | | | ✓ | ✓ | ✓ | diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake index 2eedbfa1a3e..6574bfd2549 100644 --- a/lib/tasks/gitlab/tw/codeowners.rake +++ b/lib/tasks/gitlab/tw/codeowners.rake @@ -40,7 +40,6 @@ namespace :tw do CodeOwnerRule.new('Infrastructure', '@sselhorn'), CodeOwnerRule.new('Integrations', '@kpaizee'), CodeOwnerRule.new('Knowledge', '@aqualls'), - CodeOwnerRule.new('License', '@sselhorn'), CodeOwnerRule.new('Memory', '@sselhorn'), CodeOwnerRule.new('Monitor', '@msedlakjakubowski'), CodeOwnerRule.new('Observability', 'msedlakjakubowski'), @@ -53,8 +52,8 @@ namespace :tw do CodeOwnerRule.new('Product Intelligence', '@claytoncornell'), CodeOwnerRule.new('Product Planning', '@msedlakjakubowski'), CodeOwnerRule.new('Project Management', '@msedlakjakubowski'), - CodeOwnerRule.new('Provision', '@sselhorn'), - CodeOwnerRule.new('Purchase', '@sselhorn'), + CodeOwnerRule.new('Provision', '@fneill'), + CodeOwnerRule.new('Purchase', '@fneill'), CodeOwnerRule.new('Redirect', 'Redirect'), CodeOwnerRule.new('Release', '@rdickenson'), CodeOwnerRule.new('Respond', '@msedlakjakubowski'), @@ -66,7 +65,7 @@ namespace :tw do CodeOwnerRule.new('Style Guide', '@sselhorn'), CodeOwnerRule.new('Testing', '@eread'), CodeOwnerRule.new('Threat Insights', '@claytoncornell'), - CodeOwnerRule.new('Utilization', '@sselhorn'), + CodeOwnerRule.new('Utilization', '@fneill'), CodeOwnerRule.new('Vulnerability Research', '@claytoncornell'), CodeOwnerRule.new('Workspace', '@fneill') ].freeze diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 1375384d1aa..13bce49e6d1 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -6,13 +6,8 @@ RSpec.describe 'Filter issues', :js do include FilteredSearchHelpers let(:project) { create(:project) } - - # NOTE: The short name here is actually important - # - # When the name is longer, the filtered search input can end up scrolling - # horizontally, and PhantomJS can't handle it. - let(:user) { create(:user, name: 'Ann') } - let(:user2) { create(:user, name: 'jane') } + let(:user) { create(:user) } + let(:user2) { create(:user) } let!(:bug_label) { create(:label, project: project, title: 'bug') } let!(:caps_sensitive_label) { create(:label, project: project, title: 'CaPs') } @@ -24,6 +19,7 @@ RSpec.describe 'Filter issues', :js do end before do + stub_feature_flags(vue_issues_list: true) project.add_maintainer(user) create(:issue, project: project, author: user2, title: "Bug report 1") @@ -64,31 +60,25 @@ RSpec.describe 'Filter issues', :js do it 'filters by all available tokens' do search_term = 'issue' + select_tokens 'Assignee', '=', user.username, 'Author', '=', user.username, 'Label', '=', caps_sensitive_label.title, 'Milestone', '=', milestone.title + send_keys search_term, :enter - input_filtered_search("assignee:=@#{user.username} author:=@#{user.username} label:=~#{caps_sensitive_label.title} milestone:=%#{milestone.title} #{search_term}") - - wait_for_requests - - expect_tokens([ - assignee_token(user.name), - author_token(user.name), - label_token(caps_sensitive_label.title), - milestone_token(milestone.title) - ]) + expect_assignee_token(user.name) + expect_author_token(user.name) + expect_label_token(caps_sensitive_label.title) + expect_milestone_token(milestone.title) expect_issues_list_count(1) - expect_filtered_search_input(search_term) + expect_search_term(search_term) end describe 'filter issues by author' do context 'only author' do it 'filters issues by searched author' do - input_filtered_search("author:=@#{user.username}") + select_tokens 'Author', '=', user.username, submit: true - wait_for_requests - - expect_tokens([author_token(user.name)]) + expect_author_token(user.name) expect_issues_list_count(5) - expect_filtered_search_input_empty + expect_empty_search_term end end end @@ -96,46 +86,30 @@ RSpec.describe 'Filter issues', :js do describe 'filter issues by assignee' do context 'only assignee' do it 'filters issues by searched assignee' do - input_filtered_search("assignee:=@#{user.username}") + select_tokens 'Assignee', '=', user.username, submit: true - wait_for_requests - - expect_tokens([assignee_token(user.name)]) + expect_assignee_token(user.name) expect_issues_list_count(5) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by no assignee' do - input_filtered_search('assignee:=none') + select_tokens 'Assignee', '=', 'None', submit: true - expect_tokens([assignee_token('None')]) + expect_assignee_token 'None' expect_issues_list_count(3) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by invalid assignee' do skip('to be tested, issue #26546') end - - it 'filters issues by multiple assignees' do - create(:issue, project: project, author: user, assignees: [user2, user]) - - input_filtered_search("assignee:=@#{user.username} assignee:=@#{user2.username}") - - expect_tokens([ - assignee_token(user.name), - assignee_token(user2.name) - ]) - - expect_issues_list_count(1) - expect_filtered_search_input_empty - end end end describe 'filter by reviewer' do it 'does not allow filtering by reviewer' do - find('.filtered-search').click + click_filtered_search_bar expect(page).not_to have_button('Reviewer') end @@ -144,57 +118,53 @@ RSpec.describe 'Filter issues', :js do describe 'filter issues by label' do context 'only label' do it 'filters issues by searched label' do - input_filtered_search("label:=~#{bug_label.title}") + select_tokens 'Label', '=', bug_label.title, submit: true - expect_tokens([label_token(bug_label.title)]) + expect_label_token(bug_label.title) expect_issues_list_count(2) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues not containing searched label' do - input_filtered_search("label:!=~#{bug_label.title}") + select_tokens 'Label', '!=', bug_label.title, submit: true - expect_tokens([label_token(bug_label.title)]) + expect_negated_label_token(bug_label.title) expect_issues_list_count(6) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by any label' do - input_filtered_search('label:=any') + select_tokens 'Label', '=', 'Any', submit: true - expect_tokens([label_token('Any', false)]) + expect_label_token 'Any' expect_issues_list_count(4) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by no label' do - input_filtered_search('label:=none') + select_tokens 'Label', '=', 'None', submit: true - expect_tokens([label_token('None', false)]) + expect_label_token 'None' expect_issues_list_count(4) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by multiple labels' do - input_filtered_search("label:=~#{bug_label.title} label:=~#{caps_sensitive_label.title}") + select_tokens 'Label', '=', bug_label.title, 'Label', '=', caps_sensitive_label.title, submit: true - expect_tokens([ - label_token(bug_label.title), - label_token(caps_sensitive_label.title) - ]) + expect_label_token(bug_label.title) + expect_label_token(caps_sensitive_label.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by multiple labels with not operator' do - input_filtered_search("label:!=~#{bug_label.title} label:=~#{caps_sensitive_label.title}") + select_tokens 'Label', '!=', bug_label.title, 'Label', '=', caps_sensitive_label.title, submit: true - expect_tokens([ - label_token(bug_label.title), - label_token(caps_sensitive_label.title) - ]) + expect_negated_label_token(bug_label.title) + expect_label_token(caps_sensitive_label.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by label containing special characters' do @@ -202,11 +172,11 @@ RSpec.describe 'Filter issues', :js do special_issue = create(:issue, title: "Issue with special character label", project: project) special_issue.labels << special_label - input_filtered_search("label:=~#{special_label.title}") + select_tokens 'Label', '=', special_label.title, submit: true - expect_tokens([label_token(special_label.title)]) + expect_label_token(special_label.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by label not containing special characters' do @@ -214,21 +184,21 @@ RSpec.describe 'Filter issues', :js do special_issue = create(:issue, title: "Issue with special character label", project: project) special_issue.labels << special_label - input_filtered_search("label:!=~#{special_label.title}") + select_tokens 'Label', '!=', special_label.title, submit: true - expect_tokens([label_token(special_label.title)]) + expect_negated_label_token(special_label.title) expect_issues_list_count(8) - expect_filtered_search_input_empty + expect_empty_search_term end it 'does not show issues for unused labels' do new_label = create(:label, project: project, title: 'new_label') - input_filtered_search("label:=~#{new_label.title}") + select_tokens 'Label', '=', new_label.title, submit: true - expect_tokens([label_token(new_label.title)]) + expect_label_token(new_label.title) expect_no_issues_list - expect_filtered_search_input_empty + expect_empty_search_term end end @@ -238,31 +208,28 @@ RSpec.describe 'Filter issues', :js do special_multiple_issue = create(:issue, title: "Issue with special character multiple words label", project: project) special_multiple_issue.labels << special_multiple_label - input_filtered_search("label:=~'#{special_multiple_label.title}'") + select_tokens 'Label', '=', special_multiple_label.title, submit: true # Check for search results (which makes sure that the page has changed) expect_issues_list_count(1) - - # filtered search defaults quotations to double quotes - expect_tokens([label_token("\"#{special_multiple_label.title}\"")]) - - expect_filtered_search_input_empty + expect_label_token(special_multiple_label.title) + expect_empty_search_term end it 'single quotes' do - input_filtered_search("label:=~'#{multiple_words_label.title}'") + select_tokens 'Label', '=', multiple_words_label.title, submit: true expect_issues_list_count(1) - expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) - expect_filtered_search_input_empty + expect_label_token(multiple_words_label.title) + expect_empty_search_term end it 'double quotes' do - input_filtered_search("label:=~\"#{multiple_words_label.title}\"") + select_tokens 'Label', '=', multiple_words_label.title, submit: true - expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) + expect_label_token(multiple_words_label.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'single quotes containing double quotes' do @@ -270,11 +237,11 @@ RSpec.describe 'Filter issues', :js do double_quotes_label_issue = create(:issue, title: "Issue with double quotes label", project: project) double_quotes_label_issue.labels << double_quotes_label - input_filtered_search("label:=~'#{double_quotes_label.title}'") + select_tokens 'Label', '=', double_quotes_label.title, submit: true - expect_tokens([label_token("'#{double_quotes_label.title}'")]) + expect_label_token(double_quotes_label.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'double quotes containing single quotes' do @@ -282,49 +249,41 @@ RSpec.describe 'Filter issues', :js do single_quotes_label_issue = create(:issue, title: "Issue with single quotes label", project: project) single_quotes_label_issue.labels << single_quotes_label - input_filtered_search("label:=~\"#{single_quotes_label.title}\"") + select_tokens 'Label', '=', single_quotes_label.title, submit: true - expect_tokens([label_token("\"#{single_quotes_label.title}\"")]) + expect_label_token(single_quotes_label.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end end context 'multiple labels with other filters' do it 'filters issues by searched label, label2, author, assignee, milestone and text' do search_term = 'bug' + select_tokens 'Label', '=', bug_label.title, 'Label', '=', caps_sensitive_label.title, 'Author', '=', user.username, 'Assignee', '=', user.username, 'Milestone', '=', milestone.title + send_keys search_term, :enter - input_filtered_search("label:=~#{bug_label.title} label:=~#{caps_sensitive_label.title} author:=@#{user.username} assignee:=@#{user.username} milestone:=%#{milestone.title} #{search_term}") - - wait_for_requests - - expect_tokens([ - label_token(bug_label.title), - label_token(caps_sensitive_label.title), - author_token(user.name), - assignee_token(user.name), - milestone_token(milestone.title) - ]) + expect_label_token(bug_label.title) + expect_label_token(caps_sensitive_label.title) + expect_author_token(user.name) + expect_assignee_token(user.name) + expect_milestone_token(milestone.title) expect_issues_list_count(1) - expect_filtered_search_input(search_term) + expect_search_term(search_term) end it 'filters issues by searched label, label2, author, assignee, not included in a milestone' do search_term = 'bug' + select_tokens 'Label', '=', bug_label.title, 'Label', '=', caps_sensitive_label.title, 'Author', '=', user.username, 'Assignee', '=', user.username, 'Milestone', '!=', milestone.title + send_keys search_term, :enter - input_filtered_search("label:=~#{bug_label.title} label:=~#{caps_sensitive_label.title} author:=@#{user.username} assignee:=@#{user.username} milestone:!=%#{milestone.title} #{search_term}") - - wait_for_requests - - expect_tokens([ - label_token(bug_label.title), - label_token(caps_sensitive_label.title), - author_token(user.name), - assignee_token(user.name), - milestone_token(milestone.title, false, '!=') - ]) + expect_label_token(bug_label.title) + expect_label_token(caps_sensitive_label.title) + expect_author_token(user.name) + expect_assignee_token(user.name) + expect_negated_milestone_token(milestone.title) expect_issues_list_count(0) - expect_filtered_search_input(search_term) + expect_search_term(search_term) end end @@ -333,8 +292,8 @@ RSpec.describe 'Filter issues', :js do click_link multiple_words_label.title expect_issues_list_count(1) - expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) - expect_filtered_search_input_empty + expect_label_token(multiple_words_label.title) + expect_empty_search_term end end end @@ -342,19 +301,19 @@ RSpec.describe 'Filter issues', :js do describe 'filter issues by milestone' do context 'only milestone' do it 'filters issues by searched milestone' do - input_filtered_search("milestone:=%#{milestone.title}") + select_tokens 'Milestone', '=', milestone.title, submit: true - expect_tokens([milestone_token(milestone.title)]) + expect_milestone_token(milestone.title) expect_issues_list_count(5) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by no milestone' do - input_filtered_search("milestone:=none") + select_tokens 'Milestone', '=', 'None', submit: true - expect_tokens([milestone_token('None', false)]) + expect_milestone_token 'None' expect_issues_list_count(3) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by upcoming milestones' do @@ -362,11 +321,11 @@ RSpec.describe 'Filter issues', :js do create(:issue, project: project, milestone: future_milestone, author: user) end - input_filtered_search("milestone:=upcoming") + select_tokens 'Milestone', '=', 'Upcoming', submit: true - expect_tokens([milestone_token('Upcoming', false)]) + expect_milestone_token 'Upcoming' expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by negation of upcoming milestones' do @@ -378,72 +337,72 @@ RSpec.describe 'Filter issues', :js do create(:issue, project: project, milestone: past_milestone, author: user) end - input_filtered_search("milestone:!=upcoming") + select_tokens 'Milestone', '!=', 'Upcoming', submit: true - expect_tokens([milestone_token('Upcoming', false, '!=')]) + expect_negated_milestone_token 'Upcoming' expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by started milestones' do - input_filtered_search("milestone:=started") + select_tokens 'Milestone', '=', 'Started', submit: true - expect_tokens([milestone_token('Started', false)]) + expect_milestone_token 'Started' expect_issues_list_count(5) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by negation of started milestones' do milestone2 = create(:milestone, title: "9", project: project, start_date: 2.weeks.from_now) create(:issue, project: project, author: user, title: "something else", milestone: milestone2) - input_filtered_search("milestone:!=started") + select_tokens 'Milestone', '!=', 'Started', submit: true - expect_tokens([milestone_token('Started', false, '!=')]) + expect_negated_milestone_token 'Started' expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by milestone containing special characters' do special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project) create(:issue, project: project, milestone: special_milestone) - input_filtered_search("milestone:=%#{special_milestone.title}") + select_tokens 'Milestone', '=', special_milestone.title, submit: true - expect_tokens([milestone_token(special_milestone.title)]) + expect_milestone_token(special_milestone.title) expect_issues_list_count(1) - expect_filtered_search_input_empty + expect_empty_search_term end it 'filters issues by milestone not containing special characters' do special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project) create(:issue, project: project, milestone: special_milestone) - input_filtered_search("milestone:!=%#{special_milestone.title}") + select_tokens 'Milestone', '!=', special_milestone.title, submit: true - expect_tokens([milestone_token(special_milestone.title, false, '!=')]) + expect_negated_milestone_token(special_milestone.title) expect_issues_list_count(8) - expect_filtered_search_input_empty + expect_empty_search_term end it 'does not show issues for unused milestones' do new_milestone = create(:milestone, title: 'new', project: project) - input_filtered_search("milestone:=%#{new_milestone.title}") + select_tokens 'Milestone', '=', new_milestone.title, submit: true - expect_tokens([milestone_token(new_milestone.title)]) + expect_milestone_token(new_milestone.title) expect_no_issues_list - expect_filtered_search_input_empty + expect_empty_search_term end it 'show issues for unused milestones' do new_milestone = create(:milestone, title: 'new', project: project) - input_filtered_search("milestone:!=%#{new_milestone.title}") + select_tokens 'Milestone', '!=', new_milestone.title, submit: true - expect_tokens([milestone_token(new_milestone.title, false, '!=')]) + expect_negated_milestone_token(new_milestone.title) expect_issues_list_count(8) - expect_filtered_search_input_empty + expect_empty_search_term end end end @@ -452,47 +411,47 @@ RSpec.describe 'Filter issues', :js do context 'only text' do it 'filters issues by searched text' do search = 'Bug' - input_filtered_search(search) + submit_search_term(search) expect_issues_list_count(4) - expect_filtered_search_input(search) + expect_search_term(search) end it 'filters issues by multiple searched text' do search = 'Bug report' - input_filtered_search(search) + submit_search_term(search) expect_issues_list_count(3) - expect_filtered_search_input(search) + expect_search_term(search) end it 'filters issues by case insensitive searched text' do search = 'bug report' - input_filtered_search(search) + submit_search_term(search) expect_issues_list_count(3) - expect_filtered_search_input(search) + expect_search_term(search) end it 'filters issues by searched text containing single quotes' do issue = create(:issue, project: project, author: user, title: "issue with 'single quotes'") - search = "'single quotes'" - input_filtered_search(search) + search = 'single quotes' + submit_search_term "'#{search}'" expect_issues_list_count(1) - expect_filtered_search_input(search) + expect_search_term(search) expect(page).to have_content(issue.title) end it 'filters issues by searched text containing double quotes' do issue = create(:issue, project: project, author: user, title: "issue with \"double quotes\"") - search = '"double quotes"' - input_filtered_search(search) + search = 'double quotes' + submit_search_term "\"#{search}\"" expect_issues_list_count(1) - expect_filtered_search_input(search) + expect_search_term(search) expect(page).to have_content(issue.title) end @@ -502,36 +461,43 @@ RSpec.describe 'Filter issues', :js do issue = create(:issue, project: project, author: user, title: "issue with !@\#{$%^&*()-+") search = '!@#{$%^&*()-+' - input_filtered_search(search) + submit_search_term(search) expect_issues_list_count(1) - expect_filtered_search_input(search) + expect_search_term(search) expect(page).to have_content(issue.title) end it 'does not show any issues' do search = 'testing' - input_filtered_search(search) + submit_search_term(search) expect_no_issues_list - expect_filtered_search_input(search) + expect_search_term(search) end it 'filters issues by issue reference' do search = '#1' - input_filtered_search(search) + submit_search_term(search) expect_issues_list_count(1) - expect_filtered_search_input(search) + expect_search_term(search) end end context 'searched text with other filters' do it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do - input_filtered_search("bug author:=@#{user.username} report label:=~#{bug_label.title} label:=~#{caps_sensitive_label.title} milestone:=%#{milestone.title} foo") + click_filtered_search_bar + send_keys 'bug ' + select_tokens 'Author', '=', user.username + send_keys 'report ' + select_tokens 'Label', '=', bug_label.title + select_tokens 'Label', '=', caps_sensitive_label.title + select_tokens 'Milestone', '=', milestone.title + send_keys 'foo', :enter expect_issues_list_count(1) - expect_filtered_search_input('bug report foo') + expect_search_term('bug report foo') end end @@ -549,17 +515,11 @@ RSpec.describe 'Filter issues', :js do author: user, created_at: 5.days.ago) - input_filtered_search('days ago') + submit_search_term 'days ago' expect_issues_list_count(2) - - sort_toggle = find('.filter-dropdown-container .dropdown') - sort_toggle.click - - find('.filter-dropdown-container .dropdown-menu li a', text: 'Created date').click - wait_for_requests - - expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(new_issue.title) + expect(page).to have_button 'Created date' + expect(page).to have_css('.issue:first-of-type .issue-title', text: new_issue.title) end end end @@ -568,7 +528,7 @@ RSpec.describe 'Filter issues', :js do let!(:closed_issue) { create(:issue, :closed, project: project, title: 'closed bug') } before do - input_filtered_search('bug') + submit_search_term 'bug' # This ensures that the search is performed expect_issues_list_count(4, 1) @@ -599,19 +559,17 @@ RSpec.describe 'Filter issues', :js do end it 'milestone dropdown loads milestones' do - input_filtered_search("milestone:=", submit: false) + select_tokens 'Milestone', '=' - within('#js-dropdown-milestone') do - expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) - end + # Expect None, Any, Upcoming, Started, 8 + expect_suggestion_count 5 end it 'label dropdown load labels' do - input_filtered_search("label:=", submit: false) + select_tokens 'Label', '=' - within('#js-dropdown-label') do - expect(page).to have_selector('.filter-dropdown .filter-dropdown-item', count: 3) - end + # Dropdown shows None, Any, and 3 labels + expect_suggestion_count 5 end end end diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb index e22fb951172..e070336ef76 100644 --- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb +++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do let(:container_expiration_policy) { project.container_expiration_policy } let(:params) { { project_path: project.full_path, cadence: '3month', keep_n: 100, older_than: '14d' } } - specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } + specify { expect(described_class).to require_graphql_authorizations(:admin_container_image) } describe '#resolve' do subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(**params) } @@ -76,7 +76,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do context 'with existing container expiration policy' do where(:user_role, :shared_examples_name) do :maintainer | 'updating the container expiration policy' - :developer | 'updating the container expiration policy' + :developer | 'denying access to container expiration policy' :reporter | 'denying access to container expiration policy' :guest | 'denying access to container expiration policy' :anonymous | 'denying access to container expiration policy' @@ -96,7 +96,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do where(:user_role, :shared_examples_name) do :maintainer | 'creating the container expiration policy' - :developer | 'creating the container expiration policy' + :developer | 'denying access to container expiration policy' :reporter | 'denying access to container expiration policy' :guest | 'denying access to container expiration policy' :anonymous | 'denying access to container expiration policy' diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index 263122e5d5f..83edd670695 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Mutations::Issues::SetDueDate do it 'returns the issue with updated due date', :aggregate_failures do expect(mutated_issue).to eq(issue) - expect(mutated_issue.due_date).to eq(Date.today + 2.days) + expect(mutated_issue.due_date).to eq(due_date.to_date) expect(subject[:errors]).to be_empty end diff --git a/spec/graphql/types/container_expiration_policy_type_spec.rb b/spec/graphql/types/container_expiration_policy_type_spec.rb index 9e9ddaf1cb0..95c2be9dfcc 100644 --- a/spec/graphql/types/container_expiration_policy_type_spec.rb +++ b/spec/graphql/types/container_expiration_policy_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['ContainerExpirationPolicy'] do specify { expect(described_class.description).to eq('A tag expiration policy designed to keep only the images that matter most') } - specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } + specify { expect(described_class).to require_graphql_authorizations(:admin_container_image) } describe 'older_than field' do subject { described_class.fields['olderThan'] } diff --git a/spec/graphql/types/range_input_type_spec.rb b/spec/graphql/types/range_input_type_spec.rb index cf0c7170abc..239f4e4ba15 100644 --- a/spec/graphql/types/range_input_type_spec.rb +++ b/spec/graphql/types/range_input_type_spec.rb @@ -12,7 +12,7 @@ RSpec.describe ::Types::RangeInputType do input = { start: 1, end: 10 } output = { start: 1, end: 10 } - expect(type.coerce_isolated_input(input)).to eq(output) + expect(type.coerce_isolated_input(input).to_h).to eq(output) end it 'rejects inverted ranges' do diff --git a/spec/graphql/types/timeframe_type_spec.rb b/spec/graphql/types/timeframe_type_spec.rb index dfde3242897..288124ad24b 100644 --- a/spec/graphql/types/timeframe_type_spec.rb +++ b/spec/graphql/types/timeframe_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Timeframe'] do let(:output) { { start: Date.parse(input[:start]), end: Date.parse(input[:end]) } } it 'coerces ISO-dates into Time objects' do - expect(described_class.coerce_isolated_input(input)).to eq(output) + expect(described_class.coerce_isolated_input(input).to_h).to eq(output) end it 'rejects invalid input' do @@ -20,7 +20,7 @@ RSpec.describe GitlabSchema.types['Timeframe'] do it 'accepts times as input' do with_time = input.merge(start: '2018-06-04T13:48:14Z') - expect(described_class.coerce_isolated_input(with_time)).to eq(output) + expect(described_class.coerce_isolated_input(with_time).to_h).to eq(output) end it 'requires both ends of the range' do diff --git a/spec/policies/container_expiration_policy_policy_spec.rb b/spec/policies/container_expiration_policy_policy_spec.rb new file mode 100644 index 00000000000..4b39dd8dace --- /dev/null +++ b/spec/policies/container_expiration_policy_policy_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerExpirationPolicyPolicy do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project) } + + subject { described_class.new(user, project.container_expiration_policy) } + + where(:user_type, :allowed_to_destroy_container_image) do + :anonymous | false + :guest | false + :developer | false + :maintainer | true + end + + with_them do + context "for user type #{params[:user_type]}" do + before do + project.public_send("add_#{user_type}", user) unless user_type == :anonymous + end + + if params[:allowed_to_destroy_container_image] + it { is_expected.to be_allowed(:admin_container_image) } + else + it { is_expected.not_to be_allowed(:admin_container_image) } + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb index 0156142dc6f..ca7c1b2ce5f 100644 --- a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb +++ b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb @@ -135,7 +135,7 @@ RSpec.describe 'Updating the container expiration policy' do context 'with existing container expiration policy' do where(:user_role, :shared_examples_name) do :maintainer | 'accepting the mutation request updating the container expiration policy' - :developer | 'accepting the mutation request updating the container expiration policy' + :developer | 'denying the mutation request' :reporter | 'denying the mutation request' :guest | 'denying the mutation request' :anonymous | 'denying the mutation request' @@ -155,7 +155,7 @@ RSpec.describe 'Updating the container expiration policy' do where(:user_role, :shared_examples_name) do :maintainer | 'accepting the mutation request creating the container expiration policy' - :developer | 'accepting the mutation request creating the container expiration policy' + :developer | 'denying the mutation request' :reporter | 'denying the mutation request' :guest | 'denying the mutation request' :anonymous | 'denying the mutation request' diff --git a/spec/services/container_expiration_policies/update_service_spec.rb b/spec/services/container_expiration_policies/update_service_spec.rb index d4b6715ae86..7d949b77de7 100644 --- a/spec/services/container_expiration_policies/update_service_spec.rb +++ b/spec/services/container_expiration_policies/update_service_spec.rb @@ -63,7 +63,7 @@ RSpec.describe ContainerExpirationPolicies::UpdateService do context 'with existing container expiration policy' do where(:user_role, :shared_examples_name) do :maintainer | 'updating the container expiration policy' - :developer | 'updating the container expiration policy' + :developer | 'denying access to container expiration policy' :reporter | 'denying access to container expiration policy' :guest | 'denying access to container expiration policy' :anonymous | 'denying access to container expiration policy' @@ -83,7 +83,7 @@ RSpec.describe ContainerExpirationPolicies::UpdateService do where(:user_role, :shared_examples_name) do :maintainer | 'creating the container expiration policy' - :developer | 'creating the container expiration policy' + :developer | 'denying access to container expiration policy' :reporter | 'denying access to container expiration policy' :guest | 'denying access to container expiration policy' :anonymous | 'denying access to container expiration policy' diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 35c8e0b75d6..5b74f347a44 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -217,6 +217,11 @@ module FilteredSearchHelpers all('.gl-filtered-search-suggestion').size end + def submit_search_term(value) + click_filtered_search_bar + send_keys(value, :enter) + end + def click_filtered_search_bar find('.gl-filtered-search-last-item').click end @@ -253,6 +258,22 @@ module FilteredSearchHelpers expect(page).to have_css '.gl-filtered-search-token', text: "Author = #{value}" end + def expect_label_token(value) + expect(page).to have_css '.gl-filtered-search-token', text: "Label = ~#{value}" + end + + def expect_negated_label_token(value) + expect(page).to have_css '.gl-filtered-search-token', text: "Label != ~#{value}" + end + + def expect_milestone_token(value) + expect(page).to have_css '.gl-filtered-search-token', text: "Milestone = %#{value}" + end + + def expect_negated_milestone_token(value) + expect(page).to have_css '.gl-filtered-search-token', text: "Milestone != %#{value}" + end + def expect_search_term(value) value.split(' ').each do |term| expect(page).to have_css '.gl-filtered-search-term', text: term