diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-10 15:08:15 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-10 15:08:15 +0300 |
commit | be2696666feee6e1045e0991309b71ce7f38a413 (patch) | |
tree | 9078d7b0fb2d9ed14e22fbf783f75db4666c67e7 | |
parent | 1f64fe671ba1a368ff7e67948448b4805cdfc2db (diff) |
Add latest changes from gitlab-org/gitlab@master
95 files changed, 676 insertions, 903 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 4cd468c8a8d..35df4de6513 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -3,20 +3,16 @@ review-cleanup: - .default-retry - .review:rules:review-cleanup image: ${REVIEW_APPS_IMAGE} - resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment stage: prepare environment: - name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it + name: review/regular-cleanup action: stop before_script: - source scripts/utils.sh - - source scripts/review_apps/review-apps.sh - source scripts/review_apps/gcp_cleanup.sh - install_gitlab_gem - setup_gcp_dependencies script: - - delete_release - - delete_namespace - scripts/review_apps/automated_cleanup.rb - gcp_cleanup diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index 75a753b125a..bc736200046 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -31,6 +31,8 @@ These labels cause the MR to be added to code verification QA issues. Documentation-related MRs should be reviewed by a Technical Writer for a non-blocking review, based on [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and the [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/). +If you aren't sure which tech writer to ask, use [roulette](https://gitlab-org.gitlab.io/gitlab-roulette/?sortKey=stats.avg30&order=-1&hourFormat24=true&visible=maintainer%7Cdocs) or ask in the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) Slack channel. + - [ ] If the content requires it, ensure the information is reviewed by a subject matter expert. - Technical writer review items: - [ ] Ensure docs metadata is present and up-to-date. diff --git a/.rubocop.yml b/.rubocop.yml index bf5e3c0fb56..a76e2a6c2ce 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -311,6 +311,10 @@ Rails/SquishedSQLHeredocs: Rails/WhereExists: Enabled: false +# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94061#note_1160343775 +Rails/SkipsModelValidations: + Enabled: false + # See https://gitlab.com/gitlab-org/gitlab/-/issues/378105#note_1138487716 Rails/HasManyOrHasOneDependent: Enabled: false diff --git a/.rubocop_todo/rails/skips_model_validations.yml b/.rubocop_todo/rails/skips_model_validations.yml deleted file mode 100644 index f5aa7411c89..00000000000 --- a/.rubocop_todo/rails/skips_model_validations.yml +++ /dev/null @@ -1,730 +0,0 @@ ---- -Rails/SkipsModelValidations: - # Offense count: 1424 - # Temporarily disabled due to too many offenses - Enabled: false - Exclude: - - 'app/controllers/import/github_controller.rb' - - 'app/controllers/projects/environments_controller.rb' - - 'app/controllers/projects/notes_controller.rb' - - 'app/models/alert_management/alert.rb' - - 'app/models/analytics/cycle_analytics/aggregation.rb' - - 'app/models/chat_name.rb' - - 'app/models/ci/build.rb' - - 'app/models/ci/build_trace_chunks/database.rb' - - 'app/models/ci/build_trace_metadata.rb' - - 'app/models/ci/daily_build_group_report_result.rb' - - 'app/models/ci/deleted_object.rb' - - 'app/models/ci/namespace_mirror.rb' - - 'app/models/ci/pending_build.rb' - - 'app/models/ci/pipeline_schedule.rb' - - 'app/models/ci/processable.rb' - - 'app/models/ci/project_mirror.rb' - - 'app/models/ci/resource_group.rb' - - 'app/models/ci/runner.rb' - - 'app/models/ci/running_build.rb' - - 'app/models/ci/unit_test.rb' - - 'app/models/commit_status.rb' - - 'app/models/concerns/batch_nullify_dependent_associations.rb' - - 'app/models/concerns/board_recent_visit.rb' - - 'app/models/concerns/cache_markdown_field.rb' - - 'app/models/concerns/can_move_repository_storage.rb' - - 'app/models/concerns/cascading_namespace_setting_attribute.rb' - - 'app/models/concerns/counter_attribute.rb' - - 'app/models/concerns/deprecated_assignee.rb' - - 'app/models/concerns/file_store_mounter.rb' - - 'app/models/concerns/has_wiki_page_meta_attributes.rb' - - 'app/models/concerns/noteable.rb' - - 'app/models/concerns/packages/debian/distribution.rb' - - 'app/models/concerns/relative_positioning.rb' - - 'app/models/concerns/repository_storage_movable.rb' - - 'app/models/concerns/resolvable_note.rb' - - 'app/models/concerns/schedulable.rb' - - 'app/models/concerns/subscribable.rb' - - 'app/models/container_expiration_policy.rb' - - 'app/models/customer_relations/contact.rb' - - 'app/models/customer_relations/organization.rb' - - 'app/models/deployment.rb' - - 'app/models/diff_note_position.rb' - - 'app/models/environment.rb' - - 'app/models/gpg_key.rb' - - 'app/models/group.rb' - - 'app/models/group_import_state.rb' - - 'app/models/hooks/web_hook.rb' - - 'app/models/internal_id.rb' - - 'app/models/issue.rb' - - 'app/models/jira_import_state.rb' - - 'app/models/loose_foreign_keys/deleted_record.rb' - - 'app/models/merge_request.rb' - - 'app/models/merge_request/diff_commit_user.rb' - - 'app/models/merge_request_diff.rb' - - 'app/models/namespace.rb' - - 'app/models/note.rb' - - 'app/models/project.rb' - - 'app/models/project_authorization.rb' - - 'app/models/project_import_state.rb' - - 'app/models/project_statistics.rb' - - 'app/models/project_wiki.rb' - - 'app/models/projects/ci_feature_usage.rb' - - 'app/models/projects/repository_storage_move.rb' - - 'app/models/projects/topic.rb' - - 'app/models/raw_usage_data.rb' - - 'app/models/remote_mirror.rb' - - 'app/models/route.rb' - - 'app/models/todo.rb' - - 'app/models/u2f_registration.rb' - - 'app/models/user.rb' - - 'app/models/user_custom_attribute.rb' - - 'app/models/user_interacted_project.rb' - - 'app/services/boards/lists/base_destroy_service.rb' - - 'app/services/boards/lists/move_service.rb' - - 'app/services/bulk_create_integration_service.rb' - - 'app/services/bulk_update_integration_service.rb' - - 'app/services/ci/abort_pipelines_service.rb' - - 'app/services/ci/disable_user_pipeline_schedules_service.rb' - - 'app/services/ci/expire_pipeline_cache_service.rb' - - 'app/services/ci/job_artifacts/create_service.rb' - - 'app/services/ci/job_artifacts/destroy_batch_service.rb' - - 'app/services/ci/job_artifacts/expire_project_build_artifacts_service.rb' - - 'app/services/ci/job_artifacts/update_unknown_locked_status_service.rb' - - 'app/services/ci/test_failure_history_service.rb' - - 'app/services/ci/update_build_state_service.rb' - - 'app/services/ci/update_pending_build_service.rb' - - 'app/services/clusters/agent_tokens/track_usage_service.rb' - - 'app/services/clusters/agents/refresh_authorization_service.rb' - - 'app/services/clusters/integrations/prometheus_health_check_service.rb' - - 'app/services/deployments/archive_in_project_service.rb' - - 'app/services/event_create_service.rb' - - 'app/services/groups/transfer_service.rb' - - 'app/services/issuable_base_service.rb' - - 'app/services/issues/move_service.rb' - - 'app/services/issues/set_crm_contacts_service.rb' - - 'app/services/keys/expiry_notification_service.rb' - - 'app/services/keys/last_used_service.rb' - - 'app/services/labels/promote_service.rb' - - 'app/services/labels/transfer_service.rb' - - 'app/services/merge_requests/bulk_remove_attention_requested_service.rb' - - 'app/services/merge_requests/cleanup_refs_service.rb' - - 'app/services/merge_requests/ff_merge_service.rb' - - 'app/services/merge_requests/merge_service.rb' - - 'app/services/merge_requests/rebase_service.rb' - - 'app/services/merge_requests/reopen_service.rb' - - 'app/services/milestones/promote_service.rb' - - 'app/services/milestones/transfer_service.rb' - - 'app/services/packages/composer/create_package_service.rb' - - 'app/services/packages/debian/generate_distribution_service.rb' - - 'app/services/packages/generic/create_package_file_service.rb' - - 'app/services/packages/mark_package_files_for_destruction_service.rb' - - 'app/services/packages/npm/create_tag_service.rb' - - 'app/services/packages/pypi/create_package_service.rb' - - 'app/services/packages/rubygems/create_dependencies_service.rb' - - 'app/services/personal_access_tokens/last_used_service.rb' - - 'app/services/projects/destroy_service.rb' - - 'app/services/projects/detect_repository_languages_service.rb' - - 'app/services/projects/move_deploy_keys_projects_service.rb' - - 'app/services/projects/move_forks_service.rb' - - 'app/services/projects/move_lfs_objects_projects_service.rb' - - 'app/services/projects/move_notification_settings_service.rb' - - 'app/services/projects/move_project_authorizations_service.rb' - - 'app/services/projects/move_project_group_links_service.rb' - - 'app/services/projects/move_project_members_service.rb' - - 'app/services/projects/move_users_star_projects_service.rb' - - 'app/services/projects/repository_languages_service.rb' - - 'app/services/projects/unlink_fork_service.rb' - - 'app/services/reset_project_cache_service.rb' - - 'app/services/spam/akismet_mark_as_spam_service.rb' - - 'app/services/spam/ham_service.rb' - - 'app/services/suggestions/apply_service.rb' - - 'app/services/suggestions/outdate_service.rb' - - 'app/services/users/activity_service.rb' - - 'app/services/users/migrate_to_ghost_user_service.rb' - - 'app/services/users/respond_to_terms_service.rb' - - 'app/services/users/set_status_service.rb' - - 'app/services/users/upsert_credit_card_validation_service.rb' - - 'app/services/x509_certificate_revoke_service.rb' - - 'app/uploaders/file_mover.rb' - - 'app/uploaders/object_storage.rb' - - 'app/workers/analytics/usage_trends/counter_job_worker.rb' - - 'app/workers/concerns/dependency_proxy/expireable.rb' - - 'app/workers/concerns/packages/cleanup_artifact_worker.rb' - - 'app/workers/container_expiration_policy_worker.rb' - - 'app/workers/packages/helm/extraction_worker.rb' - - 'app/workers/packages/nuget/extraction_worker.rb' - - 'app/workers/packages/rubygems/extraction_worker.rb' - - 'app/workers/personal_access_tokens/expired_notification_worker.rb' - - 'app/workers/personal_access_tokens/expiring_worker.rb' - - 'app/workers/pipeline_metrics_worker.rb' - - 'app/workers/process_commit_worker.rb' - - 'app/workers/repository_check/clear_worker.rb' - - 'app/workers/repository_check/single_repository_worker.rb' - - 'app/workers/stuck_merge_jobs_worker.rb' - - 'app/workers/x509_issuer_crl_check_worker.rb' - - 'db/migrate/20210428151144_update_invalid_web_hooks.rb' - - 'db/migrate/20210629031900_associate_existing_dast_builds_with_variables.rb' - - 'db/migrate/20210630224625_generate_customers_dot_jwt_signing_key.rb' - - 'db/migrate/20210729123101_confirm_security_bot.rb' - - 'db/migrate/20220413054910_backfill_delayed_group_deletion.rb' - - 'db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb' - - 'db/post_migrate/20210303165302_cleanup_cluster_tokens_with_null_name.rb' - - 'db/post_migrate/20210406144743_backfill_total_tuple_count_for_batched_migrations.rb' - - 'db/post_migrate/20210513155546_backfill_nuget_temporary_packages_to_processing_status.rb' - - 'db/post_migrate/20210601073400_fix_total_stage_in_vsa.rb' - - 'db/post_migrate/20210615234935_fix_batched_migrations_old_format_job_arguments.rb' - - 'db/post_migrate/20210722042939_update_issuable_slas_where_issue_closed.rb' - - 'db/post_migrate/20210731132939_backfill_stage_event_hash.rb' - - 'db/post_migrate/20210809123658_orphaned_invite_tokens_cleanup.rb' - - 'db/post_migrate/20210811122206_update_external_project_bots.rb' - - 'db/post_migrate/20210825150212_cleanup_remaining_orphan_invites.rb' - - 'db/post_migrate/20210826171758_initialize_throttle_unauthenticated_api_columns.rb' - - 'db/post_migrate/20210901153324_slice_merge_request_diff_commit_migrations.rb' - - 'db/post_migrate/20210908132335_disable_job_token_scope_when_unused.rb' - - 'db/post_migrate/20210914095310_cleanup_orphan_project_access_tokens.rb' - - 'db/post_migrate/20211217174331_mark_recalculate_finding_signatures_as_completed.rb' - - 'db/post_migrate/20211220123956_update_invalid_member_states.rb' - - 'db/post_migrate/20220305223212_add_security_training_providers.rb' - - 'db/post_migrate/20220307203459_rename_user_email_lookup_limit_setting_to_search_settings_cleanup.rb' - - 'db/post_migrate/20220322132242_update_pages_onboarding_state.rb' - - 'ee/app/controllers/ee/clusters/clusters_controller.rb' - - 'ee/app/models/approval_merge_request_rule.rb' - - 'ee/app/models/ci/minutes/namespace_monthly_usage.rb' - - 'ee/app/models/ci/minutes/project_monthly_usage.rb' - - 'ee/app/models/concerns/deprecated_approvals_before_merge.rb' - - 'ee/app/models/concerns/epic_tree_sorting.rb' - - 'ee/app/models/concerns/geo/replicable_registry.rb' - - 'ee/app/models/concerns/geo/verification_state.rb' - - 'ee/app/models/ee/description_version.rb' - - 'ee/app/models/ee/environment.rb' - - 'ee/app/models/ee/epic.rb' - - 'ee/app/models/ee/event.rb' - - 'ee/app/models/ee/group.rb' - - 'ee/app/models/ee/iteration.rb' - - 'ee/app/models/ee/namespace_setting.rb' - - 'ee/app/models/ee/project_wiki.rb' - - 'ee/app/models/geo/container_repository_registry.rb' - - 'ee/app/models/geo/design_registry.rb' - - 'ee/app/models/geo/project_registry.rb' - - 'ee/app/models/geo_node.rb' - - 'ee/app/models/incident_management/oncall_rotation.rb' - - 'ee/app/models/vulnerabilities/feedback.rb' - - 'ee/app/services/app_sec/dast/profiles/create_associations_service.rb' - - 'ee/app/services/ci/minutes/additional_packs/change_namespace_service.rb' - - 'ee/app/services/ci/minutes/batch_reset_service.rb' - - 'ee/app/services/ci/minutes/refresh_cached_data_service.rb' - - 'ee/app/services/ci/minutes/reset_usage_service.rb' - - 'ee/app/services/ci/minutes/update_project_and_namespace_usage_service.rb' - - 'ee/app/services/ci/sync_reports_to_approval_rules_service.rb' - - 'ee/app/services/ee/issues/move_service.rb' - - 'ee/app/services/ee/labels/promote_service.rb' - - 'ee/app/services/ee/milestones/promote_service.rb' - - 'ee/app/services/ee/projects/transfer_service.rb' - - 'ee/app/services/ee/users/migrate_to_ghost_user_service.rb' - - 'ee/app/services/epics/strategies/due_date_inherited_strategy.rb' - - 'ee/app/services/epics/strategies/start_date_inherited_strategy.rb' - - 'ee/app/services/geo/repository_verification_reset.rb' - - 'ee/app/services/incident_management/oncall_rotations/edit_service.rb' - - 'ee/app/services/incident_management/oncall_rotations/remove_participant_service.rb' - - 'ee/app/services/iterations/cadences/create_iterations_in_advance_service.rb' - - 'ee/app/services/iterations/cadences/destroy_service.rb' - - 'ee/app/services/iterations/delete_service.rb' - - 'ee/app/services/iterations/roll_over_issues_service.rb' - - 'ee/app/services/ldap_group_reset_service.rb' - - 'ee/app/services/personal_access_tokens/revoke_invalid_tokens.rb' - - 'ee/app/services/security/findings/cleanup_service.rb' - - 'ee/app/services/security/ingestion/mark_as_resolved_service.rb' - - 'ee/app/services/security/store_findings_metadata_service.rb' - - 'ee/app/services/security/store_scan_service.rb' - - 'ee/app/services/security/update_training_service.rb' - - 'ee/app/services/vulnerabilities/starboard_vulnerability_resolve_service.rb' - - 'ee/app/workers/import_software_licenses_worker.rb' - - 'ee/app/workers/iterations_update_status_worker.rb' - - 'ee/app/workers/sync_seat_link_request_worker.rb' - - 'ee/lib/api/geo_replication.rb' - - 'ee/lib/ee/api/protected_branches.rb' - - 'ee/lib/ee/gitlab/auth/ldap/sync/group.rb' - - 'ee/lib/ee/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb' - - 'ee/lib/ee/gitlab/background_migration/migrate_job_artifact_registry_to_ssf.rb' - - 'ee/lib/ee/gitlab/background_migration/migrate_requirements_to_work_items.rb' - - 'ee/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column.rb' - - 'ee/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings.rb' - - 'ee/lib/gitlab/geo/replicator.rb' - - 'ee/lib/tasks/migrate/ldap.rake' - - 'ee/spec/controllers/admin/geo/projects_controller_spec.rb' - - 'ee/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb' - - 'ee/spec/controllers/groups/ldaps_controller_spec.rb' - - 'ee/spec/controllers/projects/merge_requests_controller_spec.rb' - - 'ee/spec/factories/import_states.rb' - - 'ee/spec/features/admin/admin_settings_spec.rb' - - 'ee/spec/features/epic_boards/epic_boards_sidebar_spec.rb' - - 'ee/spec/features/projects/settings/ee/service_desk_setting_spec.rb' - - 'ee/spec/features/projects/settings/issues_settings_spec.rb' - - 'ee/spec/features/projects/settings/protected_environments_spec.rb' - - 'ee/spec/features/projects/user_applies_custom_file_template_spec.rb' - - 'ee/spec/features/trials/select_namespace_spec.rb' - - 'ee/spec/finders/geo/repository_verification_finder_spec.rb' - - 'ee/spec/finders/security/findings_finder_spec.rb' - - 'ee/spec/finders/security/training_urls_finder_spec.rb' - - 'ee/spec/finders/template_finder_spec.rb' - - 'ee/spec/graphql/mutations/issues/set_epic_spec.rb' - - 'ee/spec/graphql/mutations/issues/set_escalation_policy_spec.rb' - - 'ee/spec/graphql/mutations/issues/set_iteration_spec.rb' - - 'ee/spec/graphql/resolvers/boards/epic_lists_resolvers_spec.rb' - - 'ee/spec/helpers/ee/blob_helper_spec.rb' - - 'ee/spec/helpers/push_rules_helper_spec.rb' - - 'ee/spec/lib/banzai/filter/references/epic_reference_filter_spec.rb' - - 'ee/spec/lib/banzai/filter/references/iteration_reference_filter_spec.rb' - - 'ee/spec/lib/banzai/filter/references/vulnerability_reference_filters_spec.rb' - - 'ee/spec/lib/ee/api/helpers_spec.rb' - - 'ee/spec/lib/ee/audit/group_changes_auditor_spec.rb' - - 'ee/spec/lib/ee/audit/project_changes_auditor_spec.rb' - - 'ee/spec/lib/ee/audit/project_ci_cd_setting_changes_auditor_spec.rb' - - 'ee/spec/lib/ee/audit/project_feature_changes_auditor_spec.rb' - - 'ee/spec/lib/ee/audit/protected_branches_changes_auditor_spec.rb' - - 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb' - - 'ee/spec/lib/ee/gitlab/background_migration/fix_incorrect_max_seats_used_spec.rb' - - 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb' - - 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb' - - 'ee/spec/lib/gitlab/auth/saml/user_spec.rb' - - 'ee/spec/lib/gitlab/custom_file_templates_spec.rb' - - 'ee/spec/lib/gitlab/geo/geo_tasks_spec.rb' - - 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb' - - 'ee/spec/lib/gitlab/geo/signed_data_spec.rb' - - 'ee/spec/lib/gitlab/git_access_spec.rb' - - 'ee/spec/models/application_setting_spec.rb' - - 'ee/spec/models/ci/minutes/namespace_monthly_usage_spec.rb' - - 'ee/spec/models/concerns/elastic/note_spec.rb' - - 'ee/spec/models/concerns/geo/verification_state_spec.rb' - - 'ee/spec/models/dast/profile_schedule_spec.rb' - - 'ee/spec/models/ee/group_spec.rb' - - 'ee/spec/models/ee/groups/feature_setting_spec.rb' - - 'ee/spec/models/ee/iteration_spec.rb' - - 'ee/spec/models/ee/iterations/cadence_spec.rb' - - 'ee/spec/models/ee/vulnerability_spec.rb' - - 'ee/spec/models/geo_node_spec.rb' - - 'ee/spec/models/geo_node_status_spec.rb' - - 'ee/spec/models/group_wiki_repository_spec.rb' - - 'ee/spec/models/instance_security_dashboard_spec.rb' - - 'ee/spec/models/merge_request/blocking_spec.rb' - - 'ee/spec/models/merge_train_spec.rb' - - 'ee/spec/models/packages/package_file_spec.rb' - - 'ee/spec/models/project_feature_spec.rb' - - 'ee/spec/models/project_import_state_spec.rb' - - 'ee/spec/models/project_spec.rb' - - 'ee/spec/models/project_team_spec.rb' - - 'ee/spec/models/push_rule_spec.rb' - - 'ee/spec/models/requirements_management/requirement_spec.rb' - - 'ee/spec/models/security/scan_spec.rb' - - 'ee/spec/models/snippet_repository_spec.rb' - - 'ee/spec/models/vulnerabilities/feedback_spec.rb' - - 'ee/spec/models/vulnerabilities/stat_diff_spec.rb' - - 'ee/spec/policies/group_policy_spec.rb' - - 'ee/spec/policies/project_policy_spec.rb' - - 'ee/spec/requests/api/epic_issues_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/issues/promote_to_epic_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/issues/set_epic_spec.rb' - - 'ee/spec/requests/api/groups_spec.rb' - - 'ee/spec/requests/api/internal/app_sec/dast/site_validations_spec.rb' - - 'ee/spec/requests/api/internal/kubernetes_spec.rb' - - 'ee/spec/requests/api/namespaces_spec.rb' - - 'ee/spec/requests/api/project_approvals_spec.rb' - - 'ee/spec/requests/git_http_geo_spec.rb' - - 'ee/spec/requests/projects/merge_requests_controller_spec.rb' - - 'ee/spec/serializers/merge_request_widget_entity_spec.rb' - - 'ee/spec/services/ci/create_pipeline_service_spec.rb' - - 'ee/spec/services/ci/minutes/email_notification_service_spec.rb' - - 'ee/spec/services/ci/register_job_service_spec.rb' - - 'ee/spec/services/ci_cd/setup_project_spec.rb' - - 'ee/spec/services/ee/boards/issues/list_service_spec.rb' - - 'ee/spec/services/ee/notification_service_spec.rb' - - 'ee/spec/services/ee/releases/create_evidence_service_spec.rb' - - 'ee/spec/services/epic_issues/update_service_spec.rb' - - 'ee/spec/services/epics/issue_promote_service_spec.rb' - - 'ee/spec/services/epics/update_service_spec.rb' - - 'ee/spec/services/geo/file_registry_removal_service_spec.rb' - - 'ee/spec/services/geo/hashed_storage_migration_service_spec.rb' - - 'ee/spec/services/groups/create_service_spec.rb' - - 'ee/spec/services/groups/update_service_spec.rb' - - 'ee/spec/services/merge_trains/check_status_service_spec.rb' - - 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb' - - 'ee/spec/services/merge_trains/refresh_service_spec.rb' - - 'ee/spec/services/projects/setup_ci_cd_spec.rb' - - 'ee/spec/services/projects/update_mirror_service_spec.rb' - - 'ee/spec/services/security/ingestion/ingest_report_slice_service_spec.rb' - - 'ee/spec/services/security/security_orchestration_policies/create_pipeline_service_spec.rb' - - 'ee/spec/services/vulnerabilities/starboard_vulnerability_resolve_service_spec.rb' - - 'ee/spec/services/vulnerabilities/statistics/adjustment_service_spec.rb' - - 'ee/spec/services/vulnerabilities/statistics/update_service_spec.rb' - - 'ee/spec/support/helpers/ee/geo_helpers.rb' - - 'ee/spec/support/shared_examples/models/requirement_issues_examples.rb' - - 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb' - - 'ee/spec/workers/app_sec/dast/profile_schedule_worker_spec.rb' - - 'ee/spec/workers/ee/repository_check/batch_worker_spec.rb' - - 'ee/spec/workers/geo/repositories_clean_up_worker_spec.rb' - - 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb' - - 'ee/spec/workers/geo/repository_sync_worker_spec.rb' - - 'ee/spec/workers/geo/repository_verification/primary/batch_worker_spec.rb' - - 'ee/spec/workers/geo/repository_verification/primary/shard_worker_spec.rb' - - 'ee/spec/workers/geo/repository_verification/secondary/scheduler_worker_spec.rb' - - 'ee/spec/workers/geo/repository_verification/secondary/single_worker_spec.rb' - - 'ee/spec/workers/geo/verification_state_backfill_service_spec.rb' - - 'ee/spec/workers/import_software_licenses_worker_spec.rb' - - 'ee/spec/workers/iterations/roll_over_issues_worker_spec.rb' - - 'ee/spec/workers/iterations_update_status_worker_spec.rb' - - 'ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb' - - 'ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb' - - 'ee/spec/workers/update_all_mirrors_worker_spec.rb' - - 'lib/api/commit_statuses.rb' - - 'lib/api/usage_data.rb' - - 'lib/gitlab/background_migration/add_primary_email_to_emails_if_user_confirmed.rb' - - 'lib/gitlab/background_migration/backfill_ci_queuing_tables.rb' - - 'lib/gitlab/background_migration/backfill_draft_status_on_merge_requests.rb' - - 'lib/gitlab/background_migration/backfill_jira_tracker_deployment_type2.rb' - - 'lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb' - - 'lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb' - - 'lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots.rb' - - 'lib/gitlab/background_migration/backfill_projects_with_coverage.rb' - - 'lib/gitlab/background_migration/backfill_topics_title.rb' - - 'lib/gitlab/background_migration/backfill_user_namespace.rb' - - 'lib/gitlab/background_migration/backfill_work_item_type_id_for_issues.rb' - - 'lib/gitlab/background_migration/cleanup_draft_data_from_faulty_regex.rb' - - 'lib/gitlab/background_migration/copy_column_using_background_migration_job.rb' - - 'lib/gitlab/background_migration/legacy_upload_mover.rb' - - 'lib/gitlab/background_migration/merge_topics_with_same_name.rb' - - 'lib/gitlab/background_migration/migrate_merge_request_diff_commit_users.rb' - - 'lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb' - - 'lib/gitlab/background_migration/migrate_personal_namespace_project_maintainer_to_owner.rb' - - 'lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb' - - 'lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb' - - 'lib/gitlab/background_migration/migrate_u2f_webauthn.rb' - - 'lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds.rb' - - 'lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb' - - 'lib/gitlab/background_migration/remove_all_trace_expiration_dates.rb' - - 'lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects.rb' - - 'lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects.rb' - - 'lib/gitlab/bitbucket_import/importer.rb' - - 'lib/gitlab/bitbucket_server_import/importer.rb' - - 'lib/gitlab/ci/tags/bulk_insert.rb' - - 'lib/gitlab/ci/trace.rb' - - 'lib/gitlab/composer/cache.rb' - - 'lib/gitlab/database/background_migration_job.rb' - - 'lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin.rb' - - 'lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb' - - 'lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb' - - 'lib/gitlab/database/schema_migrations.rb' - - 'lib/gitlab/etag_caching/middleware.rb' - - 'lib/gitlab/fogbugz_import/importer.rb' - - 'lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb' - - 'lib/gitlab/github_import/importer/pull_request_review_importer.rb' - - 'lib/gitlab/import/set_async_jid.rb' - - 'lib/gitlab/jira_import/handle_labels_service.rb' - - 'lib/gitlab/job_waiter.rb' - - 'lib/gitlab/legacy_github_import/importer.rb' - - 'lib/gitlab/markdown_cache/active_record/extension.rb' - - 'lib/gitlab/otp_key_rotator.rb' - - 'lib/gitlab/seeder.rb' - - 'lib/tasks/ci/cleanup.rake' - - 'lib/tasks/gitlab/external_diffs.rake' - - 'lib/tasks/gitlab/ldap.rake' - - 'lib/tasks/gitlab/user_management.rake' - - 'lib/tasks/migrate/migrate_iids.rake' - - 'spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb' - - 'spec/controllers/groups_controller_spec.rb' - - 'spec/controllers/import/bitbucket_controller_spec.rb' - - 'spec/controllers/import/gitlab_controller_spec.rb' - - 'spec/controllers/omniauth_callbacks_controller_spec.rb' - - 'spec/controllers/projects/forks_controller_spec.rb' - - 'spec/controllers/projects/graphs_controller_spec.rb' - - 'spec/controllers/projects/jobs_controller_spec.rb' - - 'spec/controllers/projects/merge_requests/content_controller_spec.rb' - - 'spec/controllers/projects/merge_requests_controller_spec.rb' - - 'spec/controllers/projects/notes_controller_spec.rb' - - 'spec/controllers/projects/pipelines/tests_controller_spec.rb' - - 'spec/controllers/projects/repositories_controller_spec.rb' - - 'spec/controllers/projects/settings/ci_cd_controller_spec.rb' - - 'spec/controllers/projects/starrers_controller_spec.rb' - - 'spec/controllers/projects_controller_spec.rb' - - 'spec/controllers/uploads_controller_spec.rb' - - 'spec/factories/alert_management/alerts.rb' - - 'spec/factories/container_expiration_policies.rb' - - 'spec/factories/design_management/versions.rb' - - 'spec/factories/environments.rb' - - 'spec/factories/import_states.rb' - - 'spec/factories/projects.rb' - - 'spec/factories/usage_data.rb' - - 'spec/features/admin/admin_settings_spec.rb' - - 'spec/features/admin/admin_uses_repository_checks_spec.rb' - - 'spec/features/dashboard/projects_spec.rb' - - 'spec/features/groups_spec.rb' - - 'spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb' - - 'spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb' - - 'spec/features/issues/discussion_lock_spec.rb' - - 'spec/features/merge_request/merge_request_discussion_lock_spec.rb' - - 'spec/features/merge_request/user_creates_image_diff_notes_spec.rb' - - 'spec/features/merge_request/user_locks_discussion_spec.rb' - - 'spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb' - - 'spec/features/merge_request/user_sees_diff_spec.rb' - - 'spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb' - - 'spec/features/merge_request/user_sees_merge_widget_spec.rb' - - 'spec/features/merge_request/user_sees_pipelines_spec.rb' - - 'spec/features/merge_request/user_views_merge_request_from_deleted_fork_spec.rb' - - 'spec/features/monitor_sidebar_link_spec.rb' - - 'spec/features/password_reset_spec.rb' - - 'spec/features/profiles/emails_spec.rb' - - 'spec/features/projects/blobs/blob_show_spec.rb' - - 'spec/features/projects/diffs/diff_show_spec.rb' - - 'spec/features/projects/features_visibility_spec.rb' - - 'spec/features/projects/fork_spec.rb' - - 'spec/features/projects/jobs_spec.rb' - - 'spec/features/projects/milestones/milestone_spec.rb' - - 'spec/features/projects/pipeline_schedules_spec.rb' - - 'spec/features/projects/pipelines/pipeline_spec.rb' - - 'spec/features/projects/settings/service_desk_setting_spec.rb' - - 'spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb' - - 'spec/features/projects/user_sees_sidebar_spec.rb' - - 'spec/features/projects_spec.rb' - - 'spec/features/u2f_spec.rb' - - 'spec/features/users/show_spec.rb' - - 'spec/features/webauthn_spec.rb' - - 'spec/finders/groups_finder_spec.rb' - - 'spec/finders/notes_finder_spec.rb' - - 'spec/finders/packages/go/package_finder_spec.rb' - - 'spec/finders/packages/maven/package_finder_spec.rb' - - 'spec/finders/packages/npm/package_finder_spec.rb' - - 'spec/finders/packages/nuget/package_finder_spec.rb' - - 'spec/finders/packages/package_finder_spec.rb' - - 'spec/finders/projects_finder_spec.rb' - - 'spec/finders/releases/group_releases_finder_spec.rb' - - 'spec/finders/releases_finder_spec.rb' - - 'spec/finders/user_group_notification_settings_finder_spec.rb' - - 'spec/graphql/mutations/custom_emoji/destroy_spec.rb' - - 'spec/graphql/mutations/issues/set_escalation_status_spec.rb' - - 'spec/graphql/mutations/issues/update_spec.rb' - - 'spec/graphql/resolvers/ci/test_suite_resolver_spec.rb' - - 'spec/graphql/types/project_type_spec.rb' - - 'spec/helpers/auth_helper_spec.rb' - - 'spec/helpers/events_helper_spec.rb' - - 'spec/helpers/groups_helper_spec.rb' - - 'spec/helpers/import_helper_spec.rb' - - 'spec/helpers/members_helper_spec.rb' - - 'spec/helpers/projects_helper_spec.rb' - - 'spec/initializers/active_record_locking_spec.rb' - - 'spec/lib/api/helpers_spec.rb' - - 'spec/lib/backup/repositories_spec.rb' - - 'spec/lib/banzai/filter/references/issue_reference_filter_spec.rb' - - 'spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb' - - 'spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb' - - 'spec/lib/banzai/reference_parser/merge_request_parser_spec.rb' - - 'spec/lib/banzai/reference_parser/snippet_parser_spec.rb' - - 'spec/lib/gitlab/asciidoc_spec.rb' - - 'spec/lib/gitlab/auth/saml/user_spec.rb' - - 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb' - - 'spec/lib/gitlab/background_migration/batched_migration_job_spec.rb' - - 'spec/lib/gitlab/background_migration/update_timelogs_null_spent_at_spec.rb' - - 'spec/lib/gitlab/ci/variables/builder/group_spec.rb' - - 'spec/lib/gitlab/ci/variables/builder/project_spec.rb' - - 'spec/lib/gitlab/contributions_calendar_spec.rb' - - 'spec/lib/gitlab/cycle_analytics/permissions_spec.rb' - - 'spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb' - - 'spec/lib/gitlab/database/batch_count_spec.rb' - - 'spec/lib/gitlab/database/consistency_checker_spec.rb' - - 'spec/lib/gitlab/database/load_balancing/connection_proxy_spec.rb' - - 'spec/lib/gitlab/database/load_balancing_spec.rb' - - 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb' - - 'spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb' - - 'spec/lib/gitlab/database/schema_migrations/migrations_spec.rb' - - 'spec/lib/gitlab/discussions_diff/file_collection_spec.rb' - - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb' - - 'spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb' - - 'spec/lib/gitlab/etag_caching/store_spec.rb' - - 'spec/lib/gitlab/git_access_spec.rb' - - 'spec/lib/gitlab/git_access_wiki_spec.rb' - - 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb' - - 'spec/lib/gitlab/middleware/go_spec.rb' - - 'spec/lib/gitlab/middleware/query_analyzer_spec.rb' - - 'spec/lib/gitlab/object_hierarchy_spec.rb' - - 'spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb' - - 'spec/lib/sidebars/projects/menus/project_information_menu_spec.rb' - - 'spec/mailers/notify_spec.rb' - - 'spec/migrations/20211126115449_encrypt_static_objects_external_storage_auth_token_spec.rb' - - 'spec/migrations/remove_duplicate_dast_site_tokens_spec.rb' - - 'spec/migrations/schedule_update_timelogs_null_spent_at_spec.rb' - - 'spec/models/application_setting_spec.rb' - - 'spec/models/ci/build_dependencies_spec.rb' - - 'spec/models/ci/build_spec.rb' - - 'spec/models/ci/group_spec.rb' - - 'spec/models/ci/pipeline_schedule_spec.rb' - - 'spec/models/ci/pipeline_spec.rb' - - 'spec/models/ci/processable_spec.rb' - - 'spec/models/ci/resource_group_spec.rb' - - 'spec/models/ci/runner_spec.rb' - - 'spec/models/ci/stage_spec.rb' - - 'spec/models/commit_signatures/gpg_signature_spec.rb' - - 'spec/models/commit_status_spec.rb' - - 'spec/models/concerns/cache_markdown_field_spec.rb' - - 'spec/models/concerns/deployment_platform_spec.rb' - - 'spec/models/concerns/deprecated_assignee_spec.rb' - - 'spec/models/concerns/each_batch_spec.rb' - - 'spec/models/concerns/pg_full_text_searchable_spec.rb' - - 'spec/models/concerns/project_features_compatibility_spec.rb' - - 'spec/models/concerns/spammable_spec.rb' - - 'spec/models/container_repository_spec.rb' - - 'spec/models/deploy_keys_project_spec.rb' - - 'spec/models/deploy_token_spec.rb' - - 'spec/models/diff_discussion_spec.rb' - - 'spec/models/diff_note_spec.rb' - - 'spec/models/environment_spec.rb' - - 'spec/models/group_spec.rb' - - 'spec/models/guest_spec.rb' - - 'spec/models/integration_spec.rb' - - 'spec/models/issue_spec.rb' - - 'spec/models/loose_foreign_keys/deleted_record_spec.rb' - - 'spec/models/member_spec.rb' - - 'spec/models/members/group_member_spec.rb' - - 'spec/models/members/project_member_spec.rb' - - 'spec/models/merge_request_diff_spec.rb' - - 'spec/models/merge_request_spec.rb' - - 'spec/models/namespace/traversal_hierarchy_spec.rb' - - 'spec/models/namespace_spec.rb' - - 'spec/models/note_spec.rb' - - 'spec/models/project_authorization_spec.rb' - - 'spec/models/project_feature_spec.rb' - - 'spec/models/project_spec.rb' - - 'spec/models/project_statistics_spec.rb' - - 'spec/models/projects/build_artifacts_size_refresh_spec.rb' - - 'spec/models/projects/topic_spec.rb' - - 'spec/models/remote_mirror_spec.rb' - - 'spec/models/repository_spec.rb' - - 'spec/models/route_spec.rb' - - 'spec/models/snippet_repository_spec.rb' - - 'spec/models/user_spec.rb' - - 'spec/policies/ci/build_policy_spec.rb' - - 'spec/policies/custom_emoji_policy_spec.rb' - - 'spec/policies/note_policy_spec.rb' - - 'spec/policies/project_policy_spec.rb' - - 'spec/presenters/ci/build_presenter_spec.rb' - - 'spec/presenters/project_presenter_spec.rb' - - 'spec/requests/api/ci/job_artifacts_spec.rb' - - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb' - - 'spec/requests/api/container_repositories_spec.rb' - - 'spec/requests/api/graphql/container_repository/container_repository_details_spec.rb' - - 'spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb' - - 'spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb' - - 'spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb' - - 'spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb' - - 'spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb' - - 'spec/requests/api/graphql/mutations/snippets/create_spec.rb' - - 'spec/requests/api/graphql/mutations/snippets/destroy_spec.rb' - - 'spec/requests/api/graphql/mutations/snippets/update_spec.rb' - - 'spec/requests/api/helm_packages_spec.rb' - - 'spec/requests/api/issues/get_group_issues_spec.rb' - - 'spec/requests/api/issues/get_project_issues_spec.rb' - - 'spec/requests/api/issues/issues_spec.rb' - - 'spec/requests/api/merge_requests_spec.rb' - - 'spec/requests/api/notes_spec.rb' - - 'spec/requests/api/nuget_group_packages_spec.rb' - - 'spec/requests/api/projects_spec.rb' - - 'spec/requests/api/pypi_packages_spec.rb' - - 'spec/requests/api/releases_spec.rb' - - 'spec/requests/api/rubygem_packages_spec.rb' - - 'spec/requests/api/snippets_spec.rb' - - 'spec/requests/api/tags_spec.rb' - - 'spec/requests/git_http_spec.rb' - - 'spec/requests/groups/settings/access_tokens_controller_spec.rb' - - 'spec/requests/jwt_controller_spec.rb' - - 'spec/requests/lfs_http_spec.rb' - - 'spec/requests/projects/merge_requests_spec.rb' - - 'spec/requests/projects/settings/access_tokens_controller_spec.rb' - - 'spec/services/alert_management/create_alert_issue_service_spec.rb' - - 'spec/services/ci/compare_reports_base_service_spec.rb' - - 'spec/services/ci/compare_test_reports_service_spec.rb' - - 'spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb' - - 'spec/services/ci/register_job_service_spec.rb' - - 'spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb' - - 'spec/services/ci/retry_job_service_spec.rb' - - 'spec/services/ci/retry_pipeline_service_spec.rb' - - 'spec/services/ci/test_failure_history_service_spec.rb' - - 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb' - - 'spec/services/container_expiration_policies/cleanup_service_spec.rb' - - 'spec/services/dependency_proxy/find_cached_manifest_service_spec.rb' - - 'spec/services/deployments/update_environment_service_spec.rb' - - 'spec/services/groups/create_service_spec.rb' - - 'spec/services/groups/transfer_service_spec.rb' - - 'spec/services/groups/update_service_spec.rb' - - 'spec/services/incident_management/pager_duty/process_webhook_service_spec.rb' - - 'spec/services/issuable/common_system_notes_service_spec.rb' - - 'spec/services/issues/clone_service_spec.rb' - - 'spec/services/issues/close_service_spec.rb' - - 'spec/services/issues/update_service_spec.rb' - - 'spec/services/members/destroy_service_spec.rb' - - 'spec/services/merge_requests/get_urls_service_spec.rb' - - 'spec/services/merge_requests/merge_service_spec.rb' - - 'spec/services/merge_requests/refresh_service_spec.rb' - - 'spec/services/merge_requests/reopen_service_spec.rb' - - 'spec/services/merge_requests/update_service_spec.rb' - - 'spec/services/notes/update_service_spec.rb' - - 'spec/services/notification_service_spec.rb' - - 'spec/services/packages/maven/metadata/sync_service_spec.rb' - - 'spec/services/packages/nuget/search_service_spec.rb' - - 'spec/services/projects/container_repository/delete_tags_service_spec.rb' - - 'spec/services/projects/create_service_spec.rb' - - 'spec/services/projects/destroy_service_spec.rb' - - 'spec/services/projects/fork_service_spec.rb' - - 'spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb' - - 'spec/services/repositories/destroy_service_spec.rb' - - 'spec/services/spam/ham_service_spec.rb' - - 'spec/services/system_notes/design_management_service_spec.rb' - - 'spec/services/system_notes/issuables_service_spec.rb' - - 'spec/services/system_notes/time_tracking_service_spec.rb' - - 'spec/services/users/repair_ldap_blocked_service_spec.rb' - - 'spec/services/work_items/task_list_reference_replacement_service_spec.rb' - - 'spec/support/helpers/access_matchers_helpers.rb' - - 'spec/support/matchers/access_matchers_for_controller.rb' - - 'spec/support/shared_contexts/email_shared_context.rb' - - 'spec/support/shared_contexts/finders/packages/npm/package_finder_shared_context.rb' - - 'spec/support/shared_contexts/mailers/notify_shared_context.rb' - - 'spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb' - - 'spec/support/shared_examples/ci/stuck_builds_shared_examples.rb' - - 'spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb' - - 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb' - - 'spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb' - - 'spec/support/shared_examples/features/2fa_shared_examples.rb' - - 'spec/support/shared_examples/features/access_tokens_shared_examples.rb' - - 'spec/support/shared_examples/features/sidebar_shared_examples.rb' - - 'spec/support/shared_examples/lib/banzai/reference_parser_shared_examples.rb' - - 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb' - - 'spec/support/shared_examples/models/concerns/featurable_shared_examples.rb' - - 'spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb' - - 'spec/support/shared_examples/models/members_notifications_shared_example.rb' - - 'spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb' - - 'spec/support/shared_examples/models/throttled_touch_shared_examples.rb' - - 'spec/support/shared_examples/policies/resource_access_token_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/members_shared_examples.rb' - - 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb' - - 'spec/support/shared_examples/requests/user_activity_shared_examples.rb' - - 'spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb' - - 'spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb' - - 'spec/support/shared_examples/services/notification_service_shared_examples.rb' - - 'spec/support/shared_examples/views/pipeline_status_changes_email.rb' - - 'spec/support/trace/trace_helpers.rb' - - 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb' - - 'spec/tasks/gitlab/artifacts/check_rake_spec.rb' - - 'spec/tasks/gitlab/external_diffs_rake_spec.rb' - - 'spec/tasks/gitlab/uploads/check_rake_spec.rb' - - 'spec/uploaders/job_artifact_uploader_spec.rb' - - 'spec/views/groups/edit.html.haml_spec.rb' - - 'spec/views/projects/environments/terminal.html.haml_spec.rb' - - 'spec/workers/auto_devops/disable_worker_spec.rb' - - 'spec/workers/build_finished_worker_spec.rb' - - 'spec/workers/ci/merge_requests/add_todo_when_build_fails_worker_spec.rb' - - 'spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb' - - 'spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb' - - 'spec/workers/container_expiration_policy_worker_spec.rb' - - 'spec/workers/container_registry/migration/guard_worker_spec.rb' - - 'spec/workers/gitlab/github_import/advance_stage_worker_spec.rb' - - 'spec/workers/packages/cleanup_package_file_worker_spec.rb' - - 'spec/workers/packages/cleanup_package_registry_worker_spec.rb' - - 'spec/workers/packages/composer/cache_cleanup_worker_spec.rb' - - 'spec/workers/pipeline_schedule_worker_spec.rb' - - 'spec/workers/remote_mirror_notification_worker_spec.rb' - - 'spec/workers/repository_check/batch_worker_spec.rb' - - 'spec/workers/repository_check/clear_worker_spec.rb' diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 7214a5f31cf..ae7cf562ed8 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -39,9 +39,12 @@ import { TOKEN_TITLE_RELEASE, TOKEN_TITLE_TYPE, FILTERED_SEARCH_TERM, + OPERATOR_IS_NOT_OR, + OPERATOR_IS_AND_IS_NOT, } from '~/vue_shared/components/filtered_search_bar/constants'; import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue'; import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { CREATED_DESC, defaultTypeTokenOptions, @@ -123,6 +126,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + mixins: [glFeatureFlagMixin()], inject: [ 'autocompleteAwardEmojisPath', 'calendarPath', @@ -137,7 +141,6 @@ export default { 'hasAnyProjects', 'hasBlockedIssuesFeature', 'hasIssueWeightsFeature', - 'hasMultipleIssueAssigneesFeature', 'hasScopedLabelsFeature', 'initialEmail', 'initialSort', @@ -245,6 +248,9 @@ export default { typeTokenOptions() { return defaultTypeTokenOptions.concat(TYPE_TOKEN_TASK_OPTION); }, + hasOrFeature() { + return this.glFeatures.orIssuableQueries; + }, hasSearch() { return ( this.searchQuery || @@ -311,8 +317,8 @@ export default { icon: 'user', token: AuthorToken, dataType: 'user', - unique: !this.hasMultipleIssueAssigneesFeature, defaultAuthors: DEFAULT_NONE_ANY, + operators: this.hasOrFeature ? OPERATOR_IS_NOT_OR : OPERATOR_IS_AND_IS_NOT, fetchAuthors: this.fetchUsers, recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-assignee`, preloadedAuthors, @@ -781,6 +787,7 @@ export default { :show-page-size-change-controls="showPageSizeControls" :has-next-page="pageInfo.hasNextPage" :has-previous-page="pageInfo.hasPreviousPage" + :show-filtered-search-friendly-text="hasOrFeature" show-work-item-type-icon @click-tab="handleClickTab" @dismiss-alert="handleDismissAlert" diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index d2325d03837..100a0448e00 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -7,6 +7,7 @@ import { FILTER_UPCOMING, OPERATOR_IS, OPERATOR_IS_NOT, + OPERATOR_OR, TOKEN_TYPE_HEALTH, } from '~/vue_shared/components/filtered_search_bar/constants'; import { @@ -193,6 +194,9 @@ export const filters = { [OPERATOR_IS_NOT]: { [NORMAL_FILTER]: 'not[assignee_username][]', }, + [OPERATOR_OR]: { + [NORMAL_FILTER]: 'or[assignee_username][]', + }, }, }, [TOKEN_TYPE_MILESTONE]: { diff --git a/app/assets/javascripts/issues/list/index.js b/app/assets/javascripts/issues/list/index.js index 569ca006af5..5e04dd1971c 100644 --- a/app/assets/javascripts/issues/list/index.js +++ b/app/assets/javascripts/issues/list/index.js @@ -76,7 +76,6 @@ export function mountIssuesListApp() { hasIssuableHealthStatusFeature, hasIssueWeightsFeature, hasIterationsFeature, - hasMultipleIssueAssigneesFeature, hasScopedLabelsFeature, importCsvIssuesPath, initialEmail, @@ -127,7 +126,6 @@ export function mountIssuesListApp() { hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature), hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), hasIterationsFeature: parseBoolean(hasIterationsFeature), - hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature), hasScopedLabelsFeature: parseBoolean(hasScopedLabelsFeature), initialSort, isAnonymousSearchDisabled: parseBoolean(isAnonymousSearchDisabled), diff --git a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql index df7016aeb74..b447289b425 100644 --- a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql +++ b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql @@ -24,6 +24,7 @@ query getIssues( $crmContactId: String $crmOrganizationId: String $not: NegatedIssueFilterInput + $or: UnionedIssueFilterInput $beforeCursor: String $afterCursor: String $firstPageSize: Int @@ -49,6 +50,7 @@ query getIssues( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or before: $beforeCursor after: $afterCursor first: $firstPageSize @@ -84,6 +86,7 @@ query getIssues( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or before: $beforeCursor after: $afterCursor first: $firstPageSize diff --git a/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql index c1aee772167..fdb0eeb5970 100644 --- a/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql +++ b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql @@ -17,6 +17,7 @@ query getIssuesCount( $crmContactId: String $crmOrganizationId: String $not: NegatedIssueFilterInput + $or: UnionedIssueFilterInput ) { group(fullPath: $fullPath) @skip(if: $isProject) { id @@ -37,6 +38,7 @@ query getIssuesCount( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or ) { count } @@ -57,6 +59,7 @@ query getIssuesCount( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or ) { count } @@ -77,6 +80,7 @@ query getIssuesCount( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or ) { count } @@ -101,6 +105,7 @@ query getIssuesCount( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or ) { count } @@ -122,6 +127,7 @@ query getIssuesCount( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or ) { count } @@ -143,6 +149,7 @@ query getIssuesCount( crmContactId: $crmContactId crmOrganizationId: $crmOrganizationId not: $not + or: $or ) { count } diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index f02c7a23f51..681ddab7c25 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -5,6 +5,7 @@ import { __ } from '~/locale'; import { FILTERED_SEARCH_TERM, OPERATOR_IS_NOT, + OPERATOR_OR, } from '~/vue_shared/components/filtered_search_bar/constants'; import { API_PARAM, @@ -252,20 +253,36 @@ const formatData = (token) => { export const convertToApiParams = (filterTokens) => { const params = {}; const not = {}; + const or = {}; filterTokens .filter((token) => token.type !== FILTERED_SEARCH_TERM) .forEach((token) => { const filterType = getFilterType(token.value.data, token.type); const field = filters[token.type][API_PARAM][filterType]; - const obj = token.value.operator === OPERATOR_IS_NOT ? not : params; + let obj; + if (token.value.operator === OPERATOR_IS_NOT) { + obj = not; + } else if (token.value.operator === OPERATOR_OR) { + obj = or; + } else { + obj = params; + } const data = formatData(token); Object.assign(obj, { [field]: obj[field] ? [obj[field], data].flat() : data, }); }); - return Object.keys(not).length ? Object.assign(params, { not }) : params; + if (Object.keys(not).length) { + Object.assign(params, { not }); + } + + if (Object.keys(or).length) { + Object.assign(params, { or }); + } + + return params; }; export const convertToUrlParams = (filterTokens) => diff --git a/app/assets/javascripts/issues/show/components/incidents/constants.js b/app/assets/javascripts/issues/show/components/incidents/constants.js index aa7b9805b5f..db846009409 100644 --- a/app/assets/javascripts/issues/show/components/incidents/constants.js +++ b/app/assets/javascripts/issues/show/components/incidents/constants.js @@ -1,4 +1,4 @@ -import { __, s__ } from '~/locale'; +import { __, n__, s__ } from '~/locale'; export const timelineTabI18n = Object.freeze({ title: s__('Incident|Timeline'), @@ -15,6 +15,8 @@ export const timelineFormI18n = Object.freeze({ save: __('Save'), cancel: __('Cancel'), description: __('Description'), + hint: __('You can enter up to 280 characters'), + textRemaining: (count) => n__('%d character remaining', '%d characters remaining', count), saveAndAdd: s__('Incident|Save and add another event'), areaLabel: s__('Incident|Timeline text'), }); @@ -38,3 +40,5 @@ export const timelineItemI18n = Object.freeze({ moreActions: __('More actions'), timeUTC: __('%{time} UTC'), }); + +export const MAX_TEXT_LENGTH = 280; diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue index 55cd8b5f606..72dfccca467 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue @@ -2,7 +2,7 @@ import { GlDatepicker, GlFormInput, GlFormGroup, GlButton } from '@gitlab/ui'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; -import { timelineFormI18n } from './constants'; +import { MAX_TEXT_LENGTH, timelineFormI18n } from './constants'; import { getUtcShiftedDate } from './utils'; export default { @@ -26,6 +26,7 @@ export default { GlButton, }, i18n: timelineFormI18n, + MAX_TEXT_LENGTH, directives: { autofocusonshow, }, @@ -63,6 +64,9 @@ export default { }; }, computed: { + isTimelineTextValid() { + return this.timelineTextCount > 0 && this.timelineTextRemainingCount >= 0; + }, occurredAtString() { const year = this.datePickerInput.getFullYear(); const month = this.datePickerInput.getMonth(); @@ -74,8 +78,11 @@ export default { return utcDate.toISOString(); }, - hasTimelineText() { - return this.timelineText.length > 0; + timelineTextRemainingCount() { + return MAX_TEXT_LENGTH - this.timelineTextCount; + }, + timelineTextCount() { + return this.timelineText.length; }, }, mounted() { @@ -158,9 +165,21 @@ export default { dir="auto" data-supports-quick-actions="false" :aria-label="$options.i18n.description" + aria-describedby="timeline-form-hint" :placeholder="$options.i18n.areaPlaceholder" + :maxlength="$options.MAX_TEXT_LENGTH" > </textarea> + <div id="timeline-form-hint" class="gl-sr-only">{{ $options.i18n.hint }}</div> + <div + aria-hidden="true" + class="gl-absolute gl-text-gray-500 gl-font-sm gl-line-height-14 gl-right-4 gl-bottom-3" + > + {{ timelineTextRemainingCount }} + </div> + <div role="status" class="gl-sr-only"> + {{ $options.i18n.textRemaining(timelineTextRemainingCount) }} + </div> </template> </markdown-field> </gl-form-group> @@ -171,7 +190,7 @@ export default { category="primary" class="gl-mr-3" data-testid="save-button" - :disabled="!hasTimelineText" + :disabled="!isTimelineTextValid" :loading="isEventProcessed" @click="handleSave(false)" > @@ -183,7 +202,7 @@ export default { category="secondary" class="gl-mr-3 gl-ml-n2" data-testid="save-and-add-button" - :disabled="!hasTimelineText" + :disabled="!isTimelineTextValid" :loading="isEventProcessed" @click="handleSave(true)" > diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue index 63c2b329c27..0043d778c60 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue @@ -1,9 +1,10 @@ <script> -import { GlAlert, GlKeysetPagination } from '@gitlab/ui'; +import { GlAlert } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue'; import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; +import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; import { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, @@ -14,12 +15,13 @@ import { packageTypeToTrackCategory } from '~/packages_and_registries/package_re import Tracking from '~/tracking'; export default { + name: 'PackagesList', components: { GlAlert, - GlKeysetPagination, DeletePackageModal, PackagesListLoader, PackagesListRow, + RegistryList, }, mixins: [Tracking.mixin()], props: { @@ -57,9 +59,6 @@ export default { category, }; }, - showPagination() { - return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage; - }, errorTitleAlert() { return sprintf( s__('PackageRegistry|There was an error publishing a %{packageName} package'), @@ -123,24 +122,19 @@ export default { @primaryAction="showConfirmationModal" >{{ $options.i18n.errorMessageBodyAlert }}</gl-alert > - <div data-testid="packages-table"> - <packages-list-row - v-for="packageEntity in list" - :key="packageEntity.id" - :package-entity="packageEntity" - @packageToDelete="setItemToBeDeleted" - /> - </div> - - <div class="gl-display-flex gl-justify-content-center"> - <gl-keyset-pagination - v-if="showPagination" - v-bind="pageInfo" - class="gl-mt-3" - @prev="$emit('prev-page')" - @next="$emit('next-page')" - /> - </div> + <registry-list + data-testid="packages-table" + :hidden-delete="true" + :is-loading="isLoading" + :items="list" + :pagination="pageInfo" + @prev-page="$emit('prev-page')" + @next-page="$emit('next-page')" + > + <template #default="{ item }"> + <packages-list-row :package-entity="item" @packageToDelete="setItemToBeDeleted(item)" /> + </template> + </registry-list> <delete-package-modal :item-to-be-deleted="itemToBeDeleted" diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js index c24fb686bee..2491b79599b 100644 --- a/app/assets/javascripts/repository/constants.js +++ b/app/assets/javascripts/repository/constants.js @@ -93,7 +93,6 @@ export const LFS_STORAGE = 'lfs'; * These are file types that we want the legacy (backend) syntax highlighter to highlight. */ export const LEGACY_FILE_TYPES = [ - 'gemfile', 'composer_json', 'podfile', 'podspec', diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue index d1ade2886f4..4d66c75719b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue @@ -58,6 +58,7 @@ export default { :status-icon-name="statusIcon" :widget-name="widgetName" :header="data.header" + :help-popover="data.helpPopover" > <template #body> <div class="gl-display-flex gl-flex-direction-column"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue index 7b0230ff438..cea7fb8260a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue @@ -1,9 +1,16 @@ <script> -import { GlButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; +import { + GlButton, + GlLink, + GlTooltipDirective, + GlLoadingIcon, + GlSafeHtmlDirective, +} from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import { normalizeHeaders } from '~/lib/utils/common_utils'; import { sprintf, __ } from '~/locale'; import Poll from '~/lib/utils/poll'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; import ActionButtons from '../action_buttons.vue'; import { EXTENSION_ICONS } from '../../constants'; import { createTelemetryHub } from '../extensions/telemetry'; @@ -19,13 +26,16 @@ export default { components: { ActionButtons, StatusIcon, + GlLink, GlButton, GlLoadingIcon, ContentRow, DynamicContent, + HelpPopover, }, directives: { GlTooltip: GlTooltipDirective, + SafeHtml: GlSafeHtmlDirective, }, props: { /** @@ -74,8 +84,8 @@ export default { }, statusIconName: { type: String, - default: 'neutral', required: false, + default: 'neutral', validator: (value) => Object.keys(EXTENSION_ICONS).indexOf(value) > -1, }, isCollapsible: { @@ -98,6 +108,19 @@ export default { required: false, default: true, }, + /** + * @typedef {Object} helpPopover + * @property {Object} options + * @property {String} options.title + * @property {Object} content + * @property {String} content.text + * @property {String} content.learnMorePath + */ + helpPopover: { + type: Object, + required: false, + default: null, + }, }, data() { return { @@ -209,6 +232,9 @@ export default { }, }, failedStatusIcon: EXTENSION_ICONS.failed, + i18n: { + learnMore: __('Learn more'), + }, }; </script> @@ -229,12 +255,34 @@ export default { <span v-if="summaryError">{{ summaryError }}</span> <slot v-else name="summary">{{ isLoading ? loadingText : summary }}</slot> </div> - <action-buttons - v-if="actionButtons.length > 0" - :widget="widgetName" - :tertiary-buttons="actionButtons" - @clickedAction="onActionClick" - /> + <div class="gl-display-flex"> + <help-popover + v-if="helpPopover" + :options="helpPopover.options" + :class="{ 'gl-mr-3': actionButtons.length > 0 }" + > + <template v-if="helpPopover.content"> + <p + v-if="helpPopover.content.text" + v-safe-html="helpPopover.content.text" + class="gl-mb-0" + ></p> + <gl-link + v-if="helpPopover.content.learnMorePath" + :href="helpPopover.content.learnMorePath" + target="_blank" + class="gl-font-sm" + >{{ $options.i18n.learnMore }}</gl-link + > + </template> + </help-popover> + <action-buttons + v-if="actionButtons.length > 0" + :widget="widgetName" + :tertiary-buttons="actionButtons" + @clickedAction="onActionClick" + /> + </div> <div v-if="isCollapsible" class="gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue index 81cd3ef3ae1..b0d7e8dc006 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue @@ -1,5 +1,7 @@ <script> -import { GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlSafeHtmlDirective, GlLink } from '@gitlab/ui'; +import { __ } from '~/locale'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; import { EXTENSION_ICONS } from '../../constants'; import { generateText } from '../extensions/utils'; import StatusIcon from './status_icon.vue'; @@ -7,6 +9,8 @@ import StatusIcon from './status_icon.vue'; export default { components: { StatusIcon, + HelpPopover, + GlLink, }, directives: { SafeHtml: GlSafeHtmlDirective, @@ -19,8 +23,8 @@ export default { }, statusIconName: { type: String, - default: '', required: false, + default: '', validator: (value) => value === '' || Object.keys(EXTENSION_ICONS).includes(value), }, widgetName: { @@ -29,8 +33,21 @@ export default { }, header: { type: [String, Array], + required: false, default: '', + }, + /** + * @typedef {Object} helpPopover + * @property {Object} options + * @property {String} options.title + * @property {Object} content + * @property {String} content.text + * @property {String} content.learnMorePath + */ + helpPopover: { + type: Object, required: false, + default: null, }, }, computed: { @@ -40,6 +57,12 @@ export default { generatedSubheader() { return Array.isArray(this.header) && this.header[1] ? generateText(this.header[1]) : ''; }, + shouldShowHeaderActions() { + return Boolean(this.helpPopover); + }, + }, + i18n: { + learnMore: __('Learn more'), }, }; </script> @@ -61,8 +84,23 @@ export default { ></span> </div> </slot> - <div v-if="$scopedSlots['header-actions']" class="gl-ml-auto"> - <slot name="header-actions"></slot> + <div v-if="shouldShowHeaderActions" class="gl-ml-auto"> + <help-popover :options="helpPopover.options"> + <template v-if="helpPopover.content"> + <p + v-if="helpPopover.content.text" + v-safe-html="helpPopover.content.text" + class="gl-mb-0" + ></p> + <gl-link + v-if="helpPopover.content.learnMorePath" + :href="helpPopover.content.learnMorePath" + target="_blank" + class="gl-font-sm" + >{{ $options.i18n.learnMore }}</gl-link + > + </template> + </help-popover> </div> </div> <div class="gl-display-flex gl-align-items-baseline gl-w-full"> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js index e23a2dc91f1..c01251d4918 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js @@ -13,11 +13,19 @@ export const FILTER_NONE_ANY = [FILTER_NONE, FILTER_ANY]; export const OPERATOR_IS = '='; export const OPERATOR_IS_TEXT = __('is'); export const OPERATOR_IS_NOT = '!='; -export const OPERATOR_IS_NOT_TEXT = __('is not'); +export const OPERATOR_IS_NOT_TEXT = __('is not one of'); +export const OPERATOR_OR = '||'; +export const OPERATOR_OR_TEXT = __('is one of'); export const OPERATOR_IS_ONLY = [{ value: OPERATOR_IS, description: OPERATOR_IS_TEXT }]; export const OPERATOR_IS_NOT_ONLY = [{ value: OPERATOR_IS_NOT, description: OPERATOR_IS_NOT_TEXT }]; +export const OPERATOR_OR_ONLY = [{ value: OPERATOR_OR, description: OPERATOR_OR_TEXT }]; export const OPERATOR_IS_AND_IS_NOT = [...OPERATOR_IS_ONLY, ...OPERATOR_IS_NOT_ONLY]; +export const OPERATOR_IS_NOT_OR = [ + ...OPERATOR_IS_ONLY, + ...OPERATOR_IS_NOT_ONLY, + ...OPERATOR_OR_ONLY, +]; export const DEFAULT_LABEL_NONE = { value: FILTER_NONE, text: __('None'), title: __('None') }; export const DEFAULT_LABEL_ANY = { value: FILTER_ANY, text: __('Any'), title: __('Any') }; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue index 39e3e9e9c34..0d0787e7033 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue @@ -89,6 +89,11 @@ export default { required: false, default: () => ({}), }, + showFriendlyText: { + type: Boolean, + required: false, + default: false, + }, syncFilterAndSort: { type: Boolean, required: false, @@ -351,6 +356,7 @@ export default { :close-button-title="__('Close')" :clear-recent-searches-text="__('Clear recent searches')" :no-recent-searches-text="__(`You don't have any recent searches`)" + :show-friendly-text="showFriendlyText" class="flex-grow-1" @history-item-selected="handleHistoryItemSelected" @clear="onClear" diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js index 597fc00a004..62036056700 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/link_dependencies.js @@ -1,11 +1,13 @@ import packageJsonLinker from './utils/package_json_linker'; import gemspecLinker from './utils/gemspec_linker'; import godepsJsonLinker from './utils/godeps_json_linker'; +import gemfileLinker from './utils/gemfile_linker'; const DEPENDENCY_LINKERS = { package_json: packageJsonLinker, gemspec: gemspecLinker, godeps_json: godepsJsonLinker, + gemfile: gemfileLinker, }; /** diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js index 49704421d6e..299059f62f7 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js @@ -1,7 +1,9 @@ import { escape } from 'lodash'; export const createLink = (href, innerText) => - `<a href="${escape(href)}" rel="nofollow noreferrer noopener">${escape(innerText)}</a>`; + `<a href="${escape(href)}" target="_blank" rel="nofollow noreferrer noopener">${escape( + innerText, + )}</a>`; export const generateHLJSOpenTag = (type, delimiter = '"') => `<span class="hljs-${escape(type)}">${delimiter}`; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemfile_linker.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemfile_linker.js new file mode 100644 index 00000000000..81389763f49 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemfile_linker.js @@ -0,0 +1,25 @@ +import { createLink, generateHLJSOpenTag } from './dependency_linker_util'; + +const GEM_URL = 'https://rubygems.org/gems/'; +const GEM_STRING = 'gem </span>'; +const delimiter = '''; +const stringOpenTag = generateHLJSOpenTag('string', delimiter); + +const DEPENDENCY_REGEX = new RegExp( + /* + * Detects dependencies inside of content that is highlighted by Highlight.js + * Example: 'gem </span><span class="hljs-string">'paranoia'' + * Group 1 (packageName) : 'paranoia' + */ + `${GEM_STRING}${stringOpenTag}(.+?(?=${delimiter}))`, + 'gm', +); + +const handleReplace = (packageName) => { + const href = `${GEM_URL}${packageName}`; + const packageLink = createLink(href, packageName); + return `${GEM_STRING}${stringOpenTag}${packageLink}`; +}; +export default (result) => { + return result.value.replace(DEPENDENCY_REGEX, (_, packageName) => handleReplace(packageName)); +}; diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue index 0318dd22bfa..dd3d7c8f4d6 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue @@ -178,6 +178,11 @@ export default { required: false, default: false, }, + showFilteredSearchFriendlyText: { + type: Boolean, + required: false, + default: false, + }, showPageSizeChangeControls: { type: Boolean, required: false, @@ -310,6 +315,7 @@ export default { :sync-filter-and-sort="syncFilterAndSort" :show-checkbox="showBulkEditSidebar" :checkbox-checked="allIssuablesChecked" + :show-friendly-text="showFilteredSearchFriendlyText" class="gl-flex-grow-1 gl-border-t-none row-content-block" data-qa-selector="issuable_search_container" @checked-input="handleAllIssuablesCheckedInput" diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index 7fb2bf9a875..3438a73eff6 100644 --- a/app/assets/stylesheets/highlight/themes/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -125,6 +125,14 @@ $dark-il: #de935f; } .code.dark { + // Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167) + // We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251) + @include hljs-override('title\\.class', $dark-nc); + @include hljs-override('title\\.class\\.inherited', $dark-no); + @include hljs-override('variable\\.constant', $dark-no); + @include hljs-override('title\\.function', $dark-nf); + + // Line numbers .file-line-num { @include line-link($white, 'link'); diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index 66cada9181c..75dd342393d 100644 --- a/app/assets/stylesheets/highlight/themes/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -104,7 +104,7 @@ $monokai-gh: #75715e; @include hljs-override('selector-tag', $monokai-nt); @include hljs-override('keyword', $monokai-k); @include hljs-override('variable', $monokai-nv); - @include hljs-override('variable.language_', $monokai-k); + @include hljs-override('variable\\.language_', $monokai-k); @include hljs-override('title', $monokai-nf); @include hljs-override('name', $monokai-k); @include hljs-override('tag', $monokai-nt); @@ -116,7 +116,13 @@ $monokai-gh: #75715e; @include hljs-override('bullet', $monokai-n); @include hljs-override('subst', $monokai-p); @include hljs-override('symbol', $monokai-ss); - @include hljs-override('title.class_.inherited__', $monokai-no); + @include hljs-override('title\\.class_\\.inherited__', $monokai-no); + @include hljs-override('title\\.class\\.inherited', $monokai-no); + @include hljs-override('title\\.class', $monokai-nc); + @include hljs-override('title\\.function', $monokai-nf); + @include hljs-override('variable\\.constant', $monokai-no); + @include hljs-override('variable\\.language', $monokai-nb); + @include hljs-override('params', $monokai-nb); // Line numbers .file-line-num { diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index a1bba8720a2..c0b2fb90aa0 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -107,7 +107,9 @@ $solarized-dark-il: #2aa198; @include hljs-override('selector-tag', $solarized-dark-nt); @include hljs-override('keyword', $solarized-dark-k); @include hljs-override('variable', $solarized-dark-nv); - @include hljs-override('variable.language_', $solarized-dark-k); + @include hljs-override('variable\\.language_', $solarized-dark-k); + @include hljs-override('variable\\.language', $solarized-dark-k); + @include hljs-override('variable\\.constant', $solarized-dark-no); @include hljs-override('title', $solarized-dark-nf); @include hljs-override('name', $solarized-dark-k); @include hljs-override('tag', $solarized-dark-nt); @@ -119,7 +121,11 @@ $solarized-dark-il: #2aa198; @include hljs-override('bullet', $solarized-dark-n); @include hljs-override('subst', $solarized-dark-p); @include hljs-override('symbol', $solarized-dark-ni); - @include hljs-override('title.class_.inherited__', $solarized-dark-no); + @include hljs-override('title\\.class_\\.inherited__', $solarized-dark-no); + @include hljs-override('title\\.class', $solarized-dark-nc); + @include hljs-override('title\\.class\\.inherited', $solarized-dark-no); + @include hljs-override('title\\.function', $solarized-dark-nf); + @include hljs-override('params', $solarized-dark-nb); // Line numbers .file-line-num { diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index 33945f7cda9..921b36dd610 100644 --- a/app/assets/stylesheets/highlight/themes/solarized-light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -106,7 +106,17 @@ $solarized-light-il: #2aa198; } .code.solarized-light { - @include hljs-override('title.class_.inherited__', $solarized-light-no); + // Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167) + // We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251) + @include hljs-override('keyword', $solarized-light-k); + @include hljs-override('title\\.class_\\.inherited__', $solarized-light-no); + @include hljs-override('title\\.class\\.inherited', $solarized-light-no); + @include hljs-override('title\\.class', $solarized-light-nc); + @include hljs-override('title\\.function', $solarized-light-nf); + @include hljs-override('variable\\.constant', $solarized-light-no); + @include hljs-override('variable\\.language', $solarized-light-nb); + @include hljs-override('params', $solarized-light-nb); + // Line numbers .file-line-num { @include line-link($black, 'link'); diff --git a/app/assets/stylesheets/highlight/themes/white.scss b/app/assets/stylesheets/highlight/themes/white.scss index b0f6595feff..f6cce25671f 100644 --- a/app/assets/stylesheets/highlight/themes/white.scss +++ b/app/assets/stylesheets/highlight/themes/white.scss @@ -2,9 +2,18 @@ @import '../white_base'; @include conflict-colors('white'); + + // Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167) + // We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251) @include hljs-override('variable', $white-nv); @include hljs-override('symbol', $white-ss); - @include hljs-override('title.class_.inherited__', $white-no); + @include hljs-override('title\\.class_\\.inherited__', $white-no); + @include hljs-override('title\\.class\\.inherited', $white-no); + @include hljs-override('title\\.class', $white-nc); + @include hljs-override('variable\\.constant', $white-no); + @include hljs-override('variable\\.language', $white-nb); + @include hljs-override('title\\.function', $white-nf); + @include hljs-override('params', $white-nb); } :root { diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index 687b466ab76..3a81eec34e3 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -1794,6 +1794,7 @@ body.gl-dark { --border-color: #4f4f4f; --white: #333; --black: #fff; + --gray-light: #303030; --svg-status-bg: #333; } .nav-sidebar, @@ -2037,6 +2038,7 @@ body.gl-dark { --border-color: #4f4f4f; --white: #333; --black: #fff; + --gray-light: #303030; --svg-status-bg: #333; } .tab-width-8 { diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index ae33e9ef7d4..7126c99988c 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -203,6 +203,7 @@ body.gl-dark { --white: #{$white}; --black: #{$black}; + --gray-light: #{$gray-50}; --svg-status-bg: #{$white}; diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 269342a6c22..86557ff6640 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -35,6 +35,7 @@ class GroupsController < Groups::ApplicationController before_action :track_experiment_event, only: [:new] before_action only: :issues do + push_frontend_feature_flag(:or_issuable_queries, group) push_force_frontend_feature_flag(:work_items, group.work_items_feature_flag_enabled?) end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c2575629f0e..fd44eab42d8 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -49,6 +49,10 @@ class Projects::IssuesController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?) end + before_action only: :index do + push_frontend_feature_flag(:or_issuable_queries, project) + end + before_action only: :show do push_frontend_feature_flag(:issue_assignees_widget, project) push_frontend_feature_flag(:work_items_mvc, project&.group) diff --git a/app/models/event.rb b/app/models/event.rb index 4c1793d3f13..a1417db3410 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -358,7 +358,7 @@ class Event < ApplicationRecord # hence we add the extra WHERE clause for last_activity_at. Project.unscoped.where(id: project_id) .where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago) - .touch_all(:last_activity_at, time: created_at) # rubocop: disable Rails/SkipsModelValidations + .touch_all(:last_activity_at, time: created_at) Gitlab::InactiveProjectsDeletionWarningTracker.new(project.id).reset end @@ -441,7 +441,7 @@ class Event < ApplicationRecord def set_last_repository_updated_at Project.unscoped.where(id: project_id) .where("last_repository_updated_at < ? OR last_repository_updated_at IS NULL", REPOSITORY_UPDATED_AT_INTERVAL.ago) - .touch_all(:last_repository_updated_at, time: created_at) # rubocop: disable Rails/SkipsModelValidations + .touch_all(:last_repository_updated_at, time: created_at) end def design_action_names diff --git a/app/models/user.rb b/app/models/user.rb index 9b7ae453e3e..1858c134484 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1206,6 +1206,10 @@ class User < ApplicationRecord authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled end + def preloaded_member_roles_for_projects(projects) + # overridden in EE + end + # rubocop: disable CodeReuse/ServiceClass def require_ssh_key? count = Users::KeysCountService.new(self).count diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index ad4d609a1dc..7cef87ba19f 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -56,12 +56,12 @@ - if @user.blocked? || !@user.confirmed? .user-info - .cover-title + %h1.cover-title = user_display_name(@user) = render "users/profile_basic_info" - else .user-info - .cover-title{ itemprop: 'name' } + %h1.cover-title{ itemprop: 'name' } = @user.name - if @user.pronouns.present? %span.gl-font-base.gl-text-gray-500.gl-vertical-align-middle diff --git a/doc/api/milestones.md b/doc/api/milestones.md index df09e374395..22746d4ceed 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -88,8 +88,8 @@ Parameters: | `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user | | `title` | string | yes | The title of a milestone | | `description` | string | no | The description of the milestone | -| `due_date` | string | no | The due date of the milestone | -| `start_date` | string | no | The start date of the milestone | +| `due_date` | string | no | The due date of the milestone (`YYYYMMDD`) | +| `start_date` | string | no | The start date of the milestone (`YYYYMMDD`) | ## Edit milestone @@ -107,8 +107,8 @@ Parameters: | `milestone_id` | integer | yes | The ID of the project's milestone | | `title` | string | no | The title of a milestone | | `description` | string | no | The description of the milestone | -| `due_date` | string | no | The due date of the milestone | -| `start_date` | string | no | The start date of the milestone | +| `due_date` | string | no | The due date of the milestone (`YYYYMMDD`) | +| `start_date` | string | no | The start date of the milestone (`YYYYMMDD`) | | `state_event` | string | no | The state event of the milestone (close or activate) | ## Delete project milestone diff --git a/doc/install/installation.md b/doc/install/installation.md index 97bde5056e4..c00a959e037 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -864,12 +864,6 @@ Check if GitLab and its environment are configured correctly: sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production ``` -### Compile GetText PO files - -```shell -sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production -``` - ### Compile Assets ```shell diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 6e153d5132d..e0c0cdf31f9 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -60,11 +60,6 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -# Compile GetText PO files -# Internationalization was added in `v9.2.0` so this command is only -# required for versions equal or major to it. -sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production - # Clean up assets and cache sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096" ``` diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md index 9bb88755686..b99bb3d7992 100644 --- a/doc/update/upgrading_from_ce_to_ee.md +++ b/doc/update/upgrading_from_ce_to_ee.md @@ -75,9 +75,6 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -# Compile GetText PO files -sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production - # Update node dependencies and recompile assets sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096" diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index 4dff469ed6e..f5b85330f3b 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -310,9 +310,6 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -# Compile GetText PO files -sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production - # Update node dependencies and recompile assets sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096" diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index 25fcd376b4f..8cd211a51c7 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -611,7 +611,7 @@ To filter the list of issues: 1. Select or type the operator to use for filtering the attribute. The following operators are available: - `=`: Is - - `!=`: Is not ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18059) in GitLab 12.7) + - `!=`: Is not one of 1. Enter the text to filter the attribute by. You can filter some attributes by **None** or **Any**. 1. Repeat this process to filter by multiple attributes. Multiple attributes are joined by a logical @@ -620,6 +620,21 @@ To filter the list of issues: GitLab displays the results on-screen, but you can also [retrieve them as an RSS feed](../../search/index.md#retrieve-search-results-as-feed). +### Filter with the OR operator + +> OR filtering for assignees was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23532) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `or_issuable_queries`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. +To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `or_issuable_queries`. +The feature is not ready for production use. + +When this feature is enabled, you can use the OR operator (**is one of: `||`**) +when you [filter the list of issues](#filter-the-list-of-issues). + +`is one of` represents an inclusive OR. For example, if you filter by `Assignee is one of Sidney Jones` and +`Assignee is one of Zhang Wei`, GitLab shows issues where either Sidney, Zhang, or both of them are assignees. + ### Filter issues by ID > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/39908) in GitLab 12.1. diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ffe4b15f941..fc898c30a71 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -151,7 +151,6 @@ module API project_params = project_finder_params support_order_by_similarity!(project_params) verify_project_filters!(project_params) - ProjectsFinder.new(current_user: current_user, params: project_params).execute end diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index df501c06137..bb1420534f1 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -15,6 +15,8 @@ module API preload_repository_cache(projects_relation) Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user] + + options[:current_user].preloaded_member_roles_for_projects(projects_relation) if options[:current_user] Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy] preload_groups(projects_relation) if options[:with] == Entities::Project diff --git a/lib/banzai/reference_parser/commit_parser.rb b/lib/banzai/reference_parser/commit_parser.rb index 88896970bc6..c51f4976c28 100644 --- a/lib/banzai/reference_parser/commit_parser.rb +++ b/lib/banzai/reference_parser/commit_parser.rb @@ -32,6 +32,13 @@ module Banzai commits end + def nodes_visible_to_user(user, nodes) + projects = lazy { projects_for_nodes(nodes) } + user.preloaded_member_roles_for_projects(projects.values) if user + + super + end + private def can_read_reference?(user, ref_project, node) diff --git a/lib/banzai/reference_parser/commit_range_parser.rb b/lib/banzai/reference_parser/commit_range_parser.rb index fb4a392105f..3d09bc83151 100644 --- a/lib/banzai/reference_parser/commit_range_parser.rb +++ b/lib/banzai/reference_parser/commit_range_parser.rb @@ -38,6 +38,13 @@ module Banzai range.valid_commits? ? range : nil end + def nodes_visible_to_user(user, nodes) + projects = lazy { projects_for_nodes(nodes) } + user.preloaded_member_roles_for_projects(projects.values) if user + + super + end + private def can_read_reference?(user, ref_project, node) diff --git a/lib/gitlab/github_import/importer/pull_requests_importer.rb b/lib/gitlab/github_import/importer/pull_requests_importer.rb index 16541c90002..62863ba67fd 100644 --- a/lib/gitlab/github_import/importer/pull_requests_importer.rb +++ b/lib/gitlab/github_import/importer/pull_requests_importer.rb @@ -38,7 +38,7 @@ module Gitlab # deliberate. If we were to update this column after the fetch we may # miss out on changes pushed during the fetch or between the fetch and # updating the timestamp. - project.touch(:last_repository_updated_at) # rubocop: disable Rails/SkipsModelValidations + project.touch(:last_repository_updated_at) project.repository.fetch_remote(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: true) diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb index 708768a60cf..d7fe01e90f8 100644 --- a/lib/gitlab/github_import/importer/repository_importer.rb +++ b/lib/gitlab/github_import/importer/repository_importer.rb @@ -80,7 +80,7 @@ module Gitlab end def update_clone_time - project.touch(:last_repository_updated_at) # rubocop: disable Rails/SkipsModelValidations + project.touch(:last_repository_updated_at) end private diff --git a/lib/gitlab/usage/metrics/aggregates/aggregate.rb b/lib/gitlab/usage/metrics/aggregates/aggregate.rb index 6f9187eb929..78f1ddc8a29 100644 --- a/lib/gitlab/usage/metrics/aggregates/aggregate.rb +++ b/lib/gitlab/usage/metrics/aggregates/aggregate.rb @@ -29,15 +29,6 @@ module Gitlab attr_accessor :recorded_at - def aggregated_metrics_data(time_frame) - aggregated_metrics.each_with_object({}) do |aggregation, data| - next if aggregation[:feature_flag] && Feature.disabled?(aggregation[:feature_flag], type: :development) - next unless aggregation[:time_frame].include?(time_frame) - - data[aggregation[:name]] = calculate_count_for_aggregation(aggregation: aggregation, time_frame: time_frame) - end - end - def with_validate_configuration(aggregation, time_frame) source = aggregation[:source] diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c8855f605e7..a5ba7a9f9b4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -46561,6 +46561,9 @@ msgstr "" msgid "You can enable project access token creation in %{link_start}group settings%{link_end}." msgstr "" +msgid "You can enter up to 280 characters" +msgstr "" + msgid "You can filter by 'days to merge' by clicking on the columns in the chart." msgstr "" @@ -47935,6 +47938,9 @@ msgstr "" msgid "ciReport|New" msgstr "" +msgid "ciReport|New vulnerabilities are vulnerabilities that the security scan detects in the merge request that are different to existing vulnerabilities in the default branch." +msgstr "" + msgid "ciReport|No changes to Code Quality." msgstr "" @@ -47965,6 +47971,9 @@ msgstr "" msgid "ciReport|Security reports failed loading results" msgstr "" +msgid "ciReport|Security scan results" +msgstr "" + msgid "ciReport|Security scanning" msgstr "" @@ -48438,12 +48447,18 @@ msgstr "" msgid "is not in the member group" msgstr "" +msgid "is not one of" +msgstr "" + msgid "is not the member project" msgstr "" msgid "is not valid. The iteration group has to match the iteration cadence group." msgstr "" +msgid "is one of" +msgstr "" + msgid "is read-only" msgstr "" diff --git a/qa/qa/resource/approval_configuration.rb b/qa/qa/resource/approval_configuration.rb new file mode 100644 index 00000000000..89b8201d7d2 --- /dev/null +++ b/qa/qa/resource/approval_configuration.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module QA + module Resource + # Helper for approval configuration which exists on project and mr level + module ApprovalConfiguration + include ApiFabricator + + def api_approval_configuration_path + "#{api_get_path}/approvals" + end + + def api_approval_rules_path + "#{api_get_path}/approval_rules" + end + + # Approval configuration + # + # @return [Hash] + def approval_configuration + parse_body(api_get_from(api_approval_configuration_path)) + end + + # Update approvals configuration + # MR: https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-approval-configuration + # Project: https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration + # + # @param [Hash] configuration + # @return [Hash] + def update_approval_configuration(configuration) + api_post_to(api_approval_configuration_path, configuration) + end + + # Approval rules + # + # @return [Array<Hash>] + def fetch_approval_rules + parse_body(api_get_from(api_approval_rules_path)) + end + + # Create approval rules + # + # @return [Hash] + def create_approval_rules + raise("Trying to create approval rules but no rules set!") unless approval_rules + + rule = { approvals_required: 1, name: "Approval rule for mr #{title}" } + rule[:user_ids] = approval_rules[:users].map(&:id) if approval_rules[:users] + rule[:group_ids] = approval_rules[:group].map(&:full_path) if approval_rules[:groups] + + api_post_to(api_approvals_path, rule) + end + end + end +end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 39d95c3bba5..fcfda106523 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -3,6 +3,8 @@ module QA module Resource class MergeRequest < Issuable + include ApprovalConfiguration + attr_accessor :approval_rules, :source_branch, :target_new_branch, @@ -127,6 +129,10 @@ module QA "#{api_get_path}/reviewers" end + def api_approve_path + "#{api_get_path}/approve" + end + def api_post_body { description: description, @@ -159,6 +165,17 @@ module QA end end + # Approve merge request + # + # Due to internal implementation of api client, project needs to have + # setting 'Prevent approval by author' set to false since we use same user that created merge request which + # is set through approval configuration + # + # @return [void] + def approve + api_post_to(api_approve_path, {}) + end + def fabricate_large_merge_request @project = Resource::ImportProject.fabricate_via_browser_ui! # Setting the name here, since otherwise some tests will look for an existing file in diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 873a0e16117..3cbc002fa86 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -7,6 +7,7 @@ module QA include Integrations::Project include Members include Visibility + include ApprovalConfiguration attr_accessor :initialize_with_readme, :auto_devops_enabled, diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb index c1f11b15068..887eeda51e3 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'gitlab_project_migration_common' - module QA RSpec.describe 'Manage' do describe 'Gitlab migration', product_group: :import do diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb index aa4d3becbe7..07e54ead9c8 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'gitlab_project_migration_common' - module QA RSpec.describe 'Manage' do describe 'Gitlab migration', product_group: :import do diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb index 1b24ac85307..f44786939dc 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'gitlab_project_migration_common' - module QA RSpec.describe 'Manage' do describe 'Gitlab migration', product_group: :import do @@ -9,6 +7,7 @@ module QA let!(:source_project_with_readme) { true } + # We create additional user so that object being migrated is not owned by the user doing migration let!(:other_user) do Resource::User .fabricate_via_api! { |usr| usr.api_client = admin_api_client } @@ -40,8 +39,22 @@ module QA end end - let(:imported_reviewers) { imported_mr.reviewers.map { |r| r.slice(:id, :username) } } - let(:source_mr_reviewers) { [{ id: other_user.id, username: other_user.username }] } + let(:imported_mr_reviewers) { imported_mr.reviewers.map { |r| r.slice(:name, :username) } } + let(:source_mr_reviewers) { [{ name: other_user.name, username: other_user.username }] } + + let(:imported_mr_approvers) do + imported_mr.approval_configuration[:approved_by].map do |usr| + { username: usr.dig(:user, :username), name: usr.dig(:user, :name) } + end + end + + before do + source_project.update_approval_configuration( + merge_requests_author_approval: true, + approvals_before_merge: 1 + ) + source_mr.approve + end after do other_user.remove_via_api! @@ -59,7 +72,8 @@ module QA expect(imported_mr).to eq(source_mr.reload!) expect(imported_mr_comments).to match_array(source_mr_comments) - expect(imported_reviewers).to eq(source_mr_reviewers) + expect(imported_mr_reviewers).to eq(source_mr_reviewers) + expect(imported_mr_approvers).to eq([{ username: other_user.username, name: other_user.name }]) end end end diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb index 3db4ff4351e..7b79e6967c7 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'gitlab_project_migration_common' - module QA RSpec.describe 'Manage' do describe 'Gitlab migration', product_group: :import do diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb index 3e0df3d1e13..2b7818d1ed2 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'gitlab_project_migration_common' - module QA RSpec.describe 'Manage' do describe 'Gitlab migration', product_group: :import do diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb index 91dcfe6a1a3..36036a2321e 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'gitlab_project_migration_common' - module QA RSpec.describe 'Manage' do describe 'Gitlab migration', product_group: :import do diff --git a/qa/qa/specs/features/shared_contexts/github_import_shared_context.rb b/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb index c32f5d8bf4b..c32f5d8bf4b 100644 --- a/qa/qa/specs/features/shared_contexts/github_import_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_project_migration_common.rb index 9c80c088917..9c80c088917 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb +++ b/qa/qa/specs/features/shared_contexts/import/gitlab_project_migration_common.rb diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb index 97ac7d41311..e3754c13ee3 100644 --- a/qa/qa/specs/spec_helper.rb +++ b/qa/qa/specs/spec_helper.rb @@ -147,5 +147,5 @@ RSpec.configure do |config| end end -Dir[::File.join(__dir__, "features/shared_examples/*.rb")].sort.each { |f| require f } -Dir[::File.join(__dir__, "features/shared_contexts/*.rb")].sort.each { |f| require f } +Dir[::File.join(__dir__, "features/shared_examples/**/*.rb")].sort.each { |f| require f } +Dir[::File.join(__dir__, "features/shared_contexts/**/*.rb")].sort.each { |f| require f } diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index ed7b6d1e507..a75ac4d1b8c 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -374,6 +374,8 @@ function verify_deploy() { return 0 else echoerr "Review app is not available at ${CI_ENVIRONMENT_URL}: see the logs from cURL above for more details" + echoerr "State of the pods:" + kubectl get pods return 1 fi } diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 05eb656461e..40b0bfd9aa4 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -9,6 +9,10 @@ RSpec.describe 'Dropdown assignee', :js do let_it_be(:user) { create(:user) } let_it_be(:issue) { create(:issue, project: project) } + before do + stub_feature_flags(or_issuable_queries: false) + end + describe 'behavior' do before do project.add_maintainer(user) diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 36a8f1f3902..a67d114c6d1 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -10,6 +10,7 @@ RSpec.describe 'Dropdown author', :js do let_it_be(:issue) { create(:issue, project: project) } before do + stub_feature_flags(or_issuable_queries: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index dcbab308efa..cbe917931aa 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -10,6 +10,7 @@ RSpec.describe 'Dropdown hint', :js do let_it_be(:issue) { create(:issue, project: project) } before do + stub_feature_flags(or_issuable_queries: false) project.add_maintainer(user) end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 8d96bbc38cb..e48df1b1c53 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -19,6 +19,7 @@ RSpec.describe 'Filter issues', :js do end before do + stub_feature_flags(or_issuable_queries: false) project.add_maintainer(user) create(:issue, project: project, author: user2, title: "Bug report 1") diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index c44181a60e4..854b88c3f81 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -15,6 +15,7 @@ RSpec.describe 'Visual tokens', :js do let_it_be(:issue) { create(:issue, project: project) } before do + stub_feature_flags(or_issuable_queries: false) project.add_member(user, :maintainer) project.add_member(user_rock, :maintainer) sign_in(user) diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index c99ae118db7..a18b79d00a0 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -2,6 +2,8 @@ import { GlFilteredSearchToken } from '@gitlab/ui'; import { keyBy } from 'lodash'; import { ListType } from '~/boards/constants'; import { + OPERATOR_IS_AND_IS_NOT, + OPERATOR_IS_ONLY, TOKEN_TITLE_ASSIGNEE, TOKEN_TITLE_AUTHOR, TOKEN_TITLE_LABEL, @@ -739,7 +741,7 @@ export const mockConfidentialToken = { title: 'Confidential', unique: true, token: GlFilteredSearchToken, - operators: [{ value: '=', description: 'is' }], + operators: OPERATOR_IS_ONLY, options: [ { icon: 'eye-slash', value: 'yes', title: 'Yes' }, { icon: 'eye', value: 'no', title: 'No' }, @@ -751,10 +753,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI icon: 'user', title: TOKEN_TITLE_ASSIGNEE, type: 'assignee', - operators: [ - { value: '=', description: 'is' }, - { value: '!=', description: 'is not' }, - ], + operators: OPERATOR_IS_AND_IS_NOT, token: AuthorToken, unique: true, fetchAuthors, @@ -764,10 +763,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI icon: 'pencil', title: TOKEN_TITLE_AUTHOR, type: 'author', - operators: [ - { value: '=', description: 'is' }, - { value: '!=', description: 'is not' }, - ], + operators: OPERATOR_IS_AND_IS_NOT, symbol: '@', token: AuthorToken, unique: true, @@ -778,10 +774,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI icon: 'labels', title: TOKEN_TITLE_LABEL, type: 'label', - operators: [ - { value: '=', description: 'is' }, - { value: '!=', description: 'is not' }, - ], + operators: OPERATOR_IS_AND_IS_NOT, token: LabelToken, unique: false, symbol: '~', diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js index fa0195ed765..a5488941791 100644 --- a/spec/frontend/issues/list/components/issues_list_app_spec.js +++ b/spec/frontend/issues/list/components/issues_list_app_spec.js @@ -89,7 +89,6 @@ describe('CE IssuesListApp component', () => { hasIssuableHealthStatusFeature: true, hasIssueWeightsFeature: true, hasIterationsFeature: true, - hasMultipleIssueAssigneesFeature: true, hasScopedLabelsFeature: true, initialEmail: 'email@example.com', initialSort: CREATED_DESC, diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js index 42e9d348b16..e80191a95c8 100644 --- a/spec/frontend/issues/list/mock_data.js +++ b/spec/frontend/issues/list/mock_data.js @@ -1,6 +1,7 @@ import { OPERATOR_IS, OPERATOR_IS_NOT, + OPERATOR_OR, } from '~/vue_shared/components/filtered_search_bar/constants'; export const getIssuesQueryResponse = { @@ -122,6 +123,8 @@ export const locationSearch = [ 'assignee_username[]=5', 'not[assignee_username][]=patty', 'not[assignee_username][]=selma', + 'or[assignee_username][]=carl', + 'or[assignee_username][]=lenny', 'milestone_title=season+3', 'milestone_title=season+4', 'not[milestone_title]=season+20', @@ -173,6 +176,8 @@ export const filteredTokens = [ { type: 'assignee_username', value: { data: '5', operator: OPERATOR_IS } }, { type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } }, { type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } }, + { type: 'assignee_username', value: { data: 'carl', operator: OPERATOR_OR } }, + { type: 'assignee_username', value: { data: 'lenny', operator: OPERATOR_OR } }, { type: 'milestone', value: { data: 'season 3', operator: OPERATOR_IS } }, { type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } }, { type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } }, @@ -244,6 +249,9 @@ export const apiParams = { epicId: '34', weight: '3', }, + or: { + assigneeUsernames: ['carl', 'lenny'], + }, }; export const apiParamsWithSpecialValues = { @@ -263,6 +271,7 @@ export const urlParams = { 'not[author_username]': 'marge', 'assignee_username[]': ['bart', 'lisa', '5'], 'not[assignee_username][]': ['patty', 'selma'], + 'or[assignee_username][]': ['carl', 'lenny'], milestone_title: ['season 3', 'season 4'], 'not[milestone_title]': ['season 20', 'season 30'], 'label_name[]': ['cartoon', 'tv'], diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js index 2e7449974e5..0ce3f75f576 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js @@ -52,6 +52,9 @@ describe('Timeline events form', () => { findMinuteInput().setValue(45); }; const findTextarea = () => wrapper.findByTestId('input-note'); + const findCountNumeric = (count) => wrapper.findByText(count); + const findCountVerbose = (count) => wrapper.findByText(`${count} characters remaining`); + const findCountHint = () => wrapper.findByText(timelineFormI18n.hint); const submitForm = async () => { findSubmitButton().vm.$emit('click'); @@ -135,4 +138,31 @@ describe('Timeline events form', () => { expect(findSubmitAndAddButton().props('disabled')).toBe(false); }); }); + + describe('form character limit', () => { + beforeEach(() => { + mountComponent({ mountMethod: mountExtended }); + }); + + it('sets a character limit hint', () => { + expect(findCountHint().exists()).toBe(true); + }); + + it('sets a character limit when text is entered', async () => { + await findTextarea().setValue('hello'); + + expect(findCountNumeric('275').text()).toBe('275'); + expect(findCountVerbose('275').text()).toBe('275 characters remaining'); + }); + + it('prevents form submission when text is beyond maximum length', async () => { + // 281 characters long + const longText = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in volupte'; + await findTextarea().setValue(longText); + + expect(findSubmitButton().props('disabled')).toBe(true); + expect(findSubmitAndAddButton().props('disabled')).toBe(true); + }); + }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js index e086ee84073..c5b6b7da65b 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js @@ -1,9 +1,10 @@ -import { GlAlert, GlKeysetPagination, GlSprintf } from '@gitlab/ui'; +import { GlAlert, GlSprintf } from '@gitlab/ui'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue'; +import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; import { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, @@ -38,9 +39,9 @@ describe('packages_list', () => { const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' }; const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader); - const findPackageListPagination = () => wrapper.findComponent(GlKeysetPagination); const findPackageListDeleteModal = () => wrapper.findComponent(DeletePackageModal); const findEmptySlot = () => wrapper.findComponent(EmptySlotStub); + const findRegistryList = () => wrapper.findComponent(RegistryList); const findPackagesListRow = () => wrapper.findComponent(PackagesListRow); const findErrorPackageAlert = () => wrapper.findComponent(GlAlert); @@ -53,6 +54,7 @@ describe('packages_list', () => { stubs: { DeletePackageModal, GlSprintf, + RegistryList, }, slots: { 'empty-state': EmptySlotStub, @@ -73,12 +75,12 @@ describe('packages_list', () => { expect(findPackagesListLoader().exists()).toBe(true); }); - it('does not show the rows', () => { - expect(findPackagesListRow().exists()).toBe(false); + it('does not show the registry list', () => { + expect(findRegistryList().exists()).toBe(false); }); - it('does not show the pagination', () => { - expect(findPackageListPagination().exists()).toBe(false); + it('does not show the rows', () => { + expect(findPackagesListRow().exists()).toBe(false); }); }); @@ -91,18 +93,25 @@ describe('packages_list', () => { expect(findPackagesListLoader().exists()).toBe(false); }); + it('shows the registry list', () => { + expect(findRegistryList().exists()).toBe(true); + }); + + it('shows the registry list with the right props', () => { + expect(findRegistryList().props()).toMatchObject({ + items: defaultProps.list, + pagination: defaultProps.pageInfo, + isLoading: false, + hiddenDelete: true, + }); + }); + it('shows the rows', () => { expect(findPackagesListRow().exists()).toBe(true); }); }); describe('layout', () => { - it('contains a pagination component', () => { - mountComponent({ pageInfo: { hasPreviousPage: true } }); - - expect(findPackageListPagination().exists()).toBe(true); - }); - it("doesn't contain a visible modal component", () => { mountComponent(); @@ -122,7 +131,7 @@ describe('packages_list', () => { await findPackagesListRow().vm.$emit('packageToDelete', firstPackage); }); - it('passes itemToBeDeleted to the modla', () => { + it('passes itemToBeDeleted to the modal', () => { expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(firstPackage); }); @@ -182,15 +191,15 @@ describe('packages_list', () => { }); it('emits prev-page events when the prev event is fired', () => { - findPackageListPagination().vm.$emit('prev'); + findRegistryList().vm.$emit('prev-page'); - expect(wrapper.emitted('prev-page')).toEqual([[]]); + expect(wrapper.emitted('prev-page')).toHaveLength(1); }); it('emits next-page events when the next event is fired', () => { - findPackageListPagination().vm.$emit('next'); + findRegistryList().vm.$emit('next-page'); - expect(wrapper.emitted('next-page')).toEqual([[]]); + expect(wrapper.emitted('next-page')).toHaveLength(1); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap index 41220aaf306..bf50ae42794 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap +++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue renders given data 1`] = ` -"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\"> +"<content-row-stub level=\\"2\\" statusiconname=\\"success\\" widgetname=\\"MyWidget\\" header=\\"This is a header,This is a subheader\\" helppopover=\\"[object Object]\\"> <div class=\\"gl-display-flex gl-flex-direction-column\\"> <div> <p class=\\"gl-mb-0\\">Main text for the row</p> diff --git a/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js index b7753a58747..527e800ddcf 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/dynamic_content_spec.js @@ -25,6 +25,10 @@ describe('~/vue_merge_request_widget/components/widget/dynamic_content.vue', () header: ['This is a header', 'This is a subheader'], text: 'Main text for the row', subtext: 'Optional: Smaller sub-text to be displayed below the main text', + helpPopover: { + options: { title: 'Widget help popover title' }, + content: { text: 'Widget help popover content' }, + }, icon: { name: EXTENSION_ICONS.success, }, diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js index f885db86ea1..606f7696694 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js @@ -1,11 +1,13 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue'; import StatusIcon from '~/vue_merge_request_widget/components/widget/status_icon.vue'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', () => { let wrapper; const findStatusIcon = () => wrapper.findComponent(StatusIcon); + const findHelpPopover = () => wrapper.findComponent(HelpPopover); const createComponent = ({ propsData, slots } = {}) => { wrapper = shallowMountExtended(WidgetContentRow, { @@ -36,14 +38,12 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', }, slots: { header: '<span>this is a header</span>', - 'header-actions': '<span>this is a header action</span>', body: '<span>this is a body</span>', }, }); expect(wrapper.findByText('this is a body').exists()).toBe(true); expect(wrapper.findByText('this is a header').exists()).toBe(true); - expect(wrapper.findByText('this is a header action').exists()).toBe(true); }); }); @@ -63,5 +63,26 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue', createComponent({ propsData: { header: '<b role="header">this is a header</b>' } }); expect(wrapper.findByText('<b role="header">this is a header</b>').exists()).toBe(true); }); + + it('renders a help popover', () => { + createComponent({ + propsData: { + helpPopover: { + options: { title: 'Help popover title' }, + content: { text: 'Help popover content', learnMorePath: '/path/to/docs' }, + }, + }, + }); + + expect(findHelpPopover().props('options')).toEqual({ title: 'Help popover title' }); + expect(wrapper.findByText('Help popover content').exists()).toBe(true); + expect(wrapper.findByText('Learn more').attributes('href')).toBe('/path/to/docs'); + expect(wrapper.findByText('Learn more').attributes('target')).toBe('_blank'); + }); + + it('does not render help popover when it is not provided', () => { + createComponent({}); + expect(findHelpPopover().exists()).toBe(false); + }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js index 814a1e68142..f68cb21f2cf 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js @@ -1,6 +1,7 @@ import { nextTick } from 'vue'; import * as Sentry from '@sentry/browser'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; import waitForPromises from 'helpers/wait_for_promises'; import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue'; import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue'; @@ -22,6 +23,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section'); const findActionButtons = () => wrapper.findComponent(ActionButtons); const findToggleButton = () => wrapper.findByTestId('toggle-button'); + const findHelpPopover = () => wrapper.findComponent(HelpPopover); const createComponent = ({ propsData, slots } = {}) => { wrapper = shallowMountExtended(Widget, { @@ -201,6 +203,30 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }); }); + describe('help popover', () => { + it('renders a help popover', () => { + createComponent({ + propsData: { + fetchCollapsedData: jest.fn(), + helpPopover: { + options: { title: 'My help popover title' }, + content: { text: 'Help popover content', learnMorePath: '/path/to/docs' }, + }, + }, + }); + + expect(findHelpPopover().props('options')).toEqual({ title: 'My help popover title' }); + expect(wrapper.findByText('Help popover content').exists()).toBe(true); + expect(wrapper.findByText('Learn more').attributes('href')).toBe('/path/to/docs'); + expect(wrapper.findByText('Learn more').attributes('target')).toBe('_blank'); + }); + + it('does not render help popover when it is not provided', () => { + createComponent({ propsData: { fetchCollapsedData: jest.fn() } }); + expect(findHelpPopover().exists()).toBe(false); + }); + }); + describe('handle collapse toggle', () => { it('displays the toggle button correctly', () => { createComponent({ diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js index 032f2985d35..cfd493663b7 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js @@ -1,17 +1,20 @@ import packageJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/package_json_linker'; import godepsJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker'; import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker'; +import gemfileLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker'; import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies'; import { PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT, GEMSPEC_FILE_TYPE, GODEPS_JSON_FILE_TYPE, + GEMFILE_FILE_TYPE, } from './mock_data'; jest.mock('~/vue_shared/components/source_viewer/plugins/utils/package_json_linker'); jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker'); jest.mock('~/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker'); +jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker'); describe('Highlight.js plugin for linking dependencies', () => { const hljsResultMock = { value: 'test' }; @@ -30,4 +33,9 @@ describe('Highlight.js plugin for linking dependencies', () => { linkDependencies(hljsResultMock, GODEPS_JSON_FILE_TYPE); expect(godepsJsonLinker).toHaveBeenCalled(); }); + + it('calls gemfileLinker for gemfile file types', () => { + linkDependencies(hljsResultMock, GEMFILE_FILE_TYPE); + expect(gemfileLinker).toHaveBeenCalled(); + }); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js index 3146600e491..4f390cebd37 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/mock_data.js @@ -4,3 +4,5 @@ export const PACKAGE_JSON_CONTENT = '{ "dependencies": { "@babel/core": "^7.18.5 export const GEMSPEC_FILE_TYPE = 'gemspec'; export const GODEPS_JSON_FILE_TYPE = 'godeps_json'; + +export const GEMFILE_FILE_TYPE = 'gemfile'; diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js index e4ce07ec668..e1dbdf8a87d 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js @@ -7,7 +7,7 @@ describe('createLink', () => { it('generates a link with the correct attributes', () => { const href = 'http://test.com'; const innerText = 'testing'; - const result = `<a href="${href}" rel="nofollow noreferrer noopener">${innerText}</a>`; + const result = `<a href="${href}" target="_blank" rel="nofollow noreferrer noopener">${innerText}</a>`; expect(createLink(href, innerText)).toBe(result); }); @@ -18,7 +18,7 @@ describe('createLink', () => { const escapedHref = '<script>XSS</script>'; const href = `http://test.com/${unescapedXSS}`; const innerText = `testing${unescapedXSS}`; - const result = `<a href="http://test.com/${escapedHref}" rel="nofollow noreferrer noopener">testing${escapedPackageName}</a>`; + const result = `<a href="http://test.com/${escapedHref}" target="_blank" rel="nofollow noreferrer noopener">testing${escapedPackageName}</a>`; expect(createLink(href, innerText)).toBe(result); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js new file mode 100644 index 00000000000..4e188c9af7e --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemfile_linker_spec.js @@ -0,0 +1,13 @@ +import gemfileLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemfile_linker'; + +describe('Highlight.js plugin for linking gemfile dependencies', () => { + it('mutates the input value by wrapping dependency names in anchors', () => { + const inputValue = 'gem </span><span class="hljs-string">'paranoia''; + const outputValue = + 'gem </span><span class="hljs-string">'<a href="https://rubygems.org/gems/paranoia" target="_blank" rel="nofollow noreferrer noopener">paranoia</a>''; + const hljsResultMock = { value: inputValue }; + + const output = gemfileLinker(hljsResultMock); + expect(output).toBe(outputValue); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js index 3f74bfa117f..4b104b0bf43 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/gemspec_linker_spec.js @@ -5,7 +5,7 @@ describe('Highlight.js plugin for linking gemspec dependencies', () => { const inputValue = 's.add_dependency(<span class="hljs-string">'rugged'</span>, <span class="hljs-string">'~> 0.24.0'</span>)'; const outputValue = - 's.add_dependency(<span class="hljs-string linked">'<a href="https://rubygems.org/gems/rugged" rel="nofollow noreferrer noopener">rugged</a>'</span>, <span class="hljs-string">'~> 0.24.0'</span>)'; + 's.add_dependency(<span class="hljs-string linked">'<a href="https://rubygems.org/gems/rugged" target="_blank" rel="nofollow noreferrer noopener">rugged</a>'</span>, <span class="hljs-string">'~> 0.24.0'</span>)'; const hljsResultMock = { value: inputValue }; const output = gemspecLinker(hljsResultMock); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js index 1d8b11cf707..ea7e3936846 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/godeps_json_linker_spec.js @@ -3,7 +3,7 @@ import godepsJsonLinker from '~/vue_shared/components/source_viewer/plugins/util const getInputValue = (dependencyString) => `<span class="hljs-attr">"ImportPath"</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-string">"${dependencyString}"</span>`; const getOutputValue = (dependencyString, expectedHref) => - `<span class="hljs-attr">"ImportPath"</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-attr">"<a href="${expectedHref}" rel="nofollow noreferrer noopener">${dependencyString}</a>"</span>`; + `<span class="hljs-attr">"ImportPath"</span><span class="hljs-punctuation">:</span><span class=""> </span><span class="hljs-attr">"<a href="${expectedHref}" target="_blank" rel="nofollow noreferrer noopener">${dependencyString}</a>"</span>`; describe('Highlight.js plugin for linking Godeps.json dependencies', () => { it.each` diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js index e83c129818c..170a44f8ee2 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/package_json_linker_spec.js @@ -6,7 +6,7 @@ describe('Highlight.js plugin for linking package.json dependencies', () => { const inputValue = '<span class="hljs-attr">"@babel/core"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"^7.18.5"</span>'; const outputValue = - '<span class="hljs-attr">"<a href="https://npmjs.com/package/@babel/core" rel="nofollow noreferrer noopener">@babel/core</a>"</span>: <span class="hljs-attr">"<a href="https://npmjs.com/package/@babel/core" rel="nofollow noreferrer noopener">^7.18.5</a>"</span>'; + '<span class="hljs-attr">"<a href="https://npmjs.com/package/@babel/core" target="_blank" rel="nofollow noreferrer noopener">@babel/core</a>"</span>: <span class="hljs-attr">"<a href="https://npmjs.com/package/@babel/core" target="_blank" rel="nofollow noreferrer noopener">^7.18.5</a>"</span>'; const hljsResultMock = { value: inputValue }; const output = packageJsonLinker(hljsResultMock, PACKAGE_JSON_CONTENT); diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js index 0c53f599d55..371844e66f4 100644 --- a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js +++ b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js @@ -322,6 +322,18 @@ describe('IssuableListRoot', () => { }); }); + describe('showFilteredSearchFriendlyText prop', () => { + describe.each([true, false])('when %s', (showFilteredSearchFriendlyText) => { + it('passes its value to FilteredSearchBar', () => { + wrapper = createComponent({ props: { showFilteredSearchFriendlyText } }); + + expect(findFilteredSearchBar().props('showFriendlyText')).toBe( + showFilteredSearchFriendlyText, + ); + }); + }); + }); + describe('alert', () => { const error = 'oopsie!'; diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index d31ccccd6c3..9e77137795a 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -63,7 +63,7 @@ RSpec.describe Banzai::ReferenceParser::BaseParser do context 'when the link does not have a data-project attribute' do it 'returns the nodes' do - expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) + expect(subject.nodes_visible_to_user(user, [link])).to match_array([link]) end end end diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index 8e962c0252e..3569a1019f0 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -31,6 +31,12 @@ RSpec.describe Banzai::ReferenceParser::CommitParser do expect(subject.nodes_visible_to_user(user, [link])).to be_empty end end + + context 'when the link does not have a data-project attribute' do + it 'returns the nodes' do + expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) + end + end end describe '#referenced_by' do @@ -141,7 +147,7 @@ RSpec.describe Banzai::ReferenceParser::CommitParser do end end - context 'when checking commits on another projects' do + context 'when checking commits on another projects', :request_store do let!(:control_links) do [commit_link] end @@ -153,7 +159,7 @@ RSpec.describe Banzai::ReferenceParser::CommitParser do def commit_link project = create(:project, :repository, :public) - Nokogiri::HTML.fragment(%Q{<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>}).children[0] + Nokogiri::HTML.fragment(%(<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>)).children[0] end it_behaves_like 'no project N+1 queries' diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb index 1b8214ebcbb..172347fc421 100644 --- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb @@ -31,6 +31,12 @@ RSpec.describe Banzai::ReferenceParser::CommitRangeParser do expect(subject.nodes_visible_to_user(user, [link])).to be_empty end end + + context 'when the link does not have a data-project attribute' do + it 'returns the nodes' do + expect(subject.nodes_visible_to_user(user, [link])).to match_array([link]) + end + end end describe '#referenced_by' do @@ -149,7 +155,7 @@ RSpec.describe Banzai::ReferenceParser::CommitRangeParser do end end - context 'when checking commits ranges on another project' do + context 'when checking commits ranges on another projects', :request_store do let!(:control_links) do [commit_range_link] end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index bc72d28ae77..9579c4c2d27 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Event do describe 'after_create :set_last_repository_updated_at' do context 'with a push event' do it 'updates the project last_repository_updated_at and updated_at' do - project.touch(:last_repository_updated_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations + project.touch(:last_repository_updated_at, time: 1.year.ago) event = create_push_event(project, project.first_owner) @@ -864,7 +864,7 @@ RSpec.describe Event do end it 'updates the project' do - project.touch(:last_activity_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations + project.touch(:last_activity_at, time: 1.year.ago) event = create_push_event(project, project.first_owner) @@ -880,7 +880,7 @@ RSpec.describe Event do "project:#{project.id}") end - project.touch(:last_activity_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations + project.touch(:last_activity_at, time: 1.year.ago) create_push_event(project, project.first_owner) end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 4a989b4d839..ce6140d8da8 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -1227,10 +1227,7 @@ RSpec.describe API::Groups do group1.reload end - it "only looks up root ancestor once and returns projects including those in subgroups" do - expect(Namespace).to receive(:find_by).with(id: group1.id.to_s).once.and_call_original # For the group sent in the API call - expect(Namespace).to receive(:joins).with(start_with('INNER JOIN (SELECT id, traversal_ids[1]')).once.and_call_original # All-in-one root_ancestor query - + it "returns projects including those in subgroups" do get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true } expect(response).to have_gitlab_http_status(:ok) @@ -1238,6 +1235,18 @@ RSpec.describe API::Groups do expect(json_response).to be_an(Array) expect(json_response.length).to eq(6) end + + it 'avoids N+1 queries', :use_sql_query_cache do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true } + end + + create_list(:project, 2, :public, namespace: group1) + + expect do + get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true } + end.not_to exceed_all_query_limit(control.count) + end end context 'when include_ancestor_groups is true' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 29713d11f87..3831e8e1dfe 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1065,7 +1065,7 @@ RSpec.describe API::Projects do let_it_be(:admin) { create(:admin) } - it 'avoids N+1 queries' do + it 'avoids N+1 queries', :use_sql_query_cache do get api('/projects', admin) base_project = create(:project, :public, namespace: admin.namespace) @@ -1073,7 +1073,7 @@ RSpec.describe API::Projects do fork_project1 = fork_project(base_project, admin, namespace: create(:user).namespace) fork_project2 = fork_project(fork_project1, admin, namespace: create(:user).namespace) - control = ActiveRecord::QueryRecorder.new do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do get api('/projects', admin) end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 4d9bdc4bb17..cd6b8c77291 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -163,10 +163,8 @@ RSpec.describe PipelineSerializer do context 'with different refs' do before do - # rubocop:disable Rails/SkipsModelValidations Ci::Pipeline.update_all(%(ref = 'feature-' || id)) Ci::Build.update_all(%(ref = 'feature-' || stage_id)) - # rubocop:enable Rails/SkipsModelValidations end it 'verifies number of queries', :request_store do diff --git a/spec/services/ci/runners/bulk_delete_runners_service_spec.rb b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb index 5bab54c75b6..fa8af1100df 100644 --- a/spec/services/ci/runners/bulk_delete_runners_service_spec.rb +++ b/spec/services/ci/runners/bulk_delete_runners_service_spec.rb @@ -121,6 +121,13 @@ RSpec.describe ::Ci::Runners::BulkDeleteRunnersService, '#execute' do service = described_class.new(runners: runners_arg, current_user: user) + # Base cost per runner is: + # - 1 `SELECT * FROM "taggings"` query + # - 1 `SAVEPOINT` query + # - 1 `DELETE FROM "ci_runners"` query + # - 1 `RELEASE SAVEPOINT` query + # Project runners have an additional query: + # - 1 `DELETE FROM "ci_runner_projects"` query, given the call to `destroy_all` instance_runner_cost = 4 group_runner_cost = 4 project_runner_cost = 5 diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 93122ca3d0c..677cea7b804 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -254,6 +254,10 @@ module FilteredSearchHelpers expect(page).to have_css '.gl-filtered-search-token', text: "Assignee = #{value}" end + def expect_unioned_assignee_token(value) + expect(page).to have_css '.gl-filtered-search-token', text: "Assignee is one of #{value}" + end + def expect_author_token(value) expect(page).to have_css '.gl-filtered-search-token', text: "Author = #{value}" end diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index 9aee9025b12..370dedabd9b 100644 --- a/spec/support/helpers/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -12,14 +12,14 @@ module ReferenceParserHelpers end RSpec.shared_examples 'no project N+1 queries' do - it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do + it 'avoids N+1 queries in #nodes_visible_to_user', :use_sql_query_cache do context = Banzai::RenderContext.new(project, user) request = lambda do |links| described_class.new(context).nodes_visible_to_user(user, links) end - control = ActiveRecord::QueryRecorder.new { request.call(control_links) } + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { request.call(control_links) } create(:group_member, group: project.group) if project.group create(:project_member, project: project) diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb index 7aad5e2de80..f09cf0613a1 100644 --- a/spec/support/shared_examples/features/packages_shared_examples.rb +++ b/spec/support/shared_examples/features/packages_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false| end def package_table_row(index) - page.all("#{packages_table_selector} > [data-testid=\"package-row\"]")[index].text + page.all("#{packages_table_selector} [data-testid=\"package-row\"]")[index].text end end |