diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-02 00:10:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-02 00:10:22 +0300 |
commit | 64667f74b1613f91fe7eb53cd6610f0693c3a45e (patch) | |
tree | 1fae98fab48c9e67185eab978af278fdb8238aca | |
parent | 4316e6895a9038cc21eeef847578e2918d5b5595 (diff) |
Add latest changes from gitlab-org/gitlab@master
57 files changed, 394 insertions, 252 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 1d0218f4bd7..4298482e03d 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -215,6 +215,14 @@ jest minimal: script: - run_timed_command "yarn jest:ci:minimal" +jest as-if-foss: + extends: + - .jest-base + - .frontend:rules:jest:as-if-foss + - .as-if-foss + needs: ["rspec-all frontend_fixture as-if-foss"] + parallel: 2 + jest minimal as-if-foss: extends: - .jest-base @@ -236,14 +244,6 @@ jest-integration: - job: "rspec-all frontend_fixture" - job: "graphql-schema-dump" -jest-as-if-foss: - extends: - - .jest-base - - .frontend:rules:default-frontend-jobs-as-if-foss - - .as-if-foss - needs: ["rspec-all frontend_fixture as-if-foss"] - parallel: 2 - coverage-frontend: extends: - .default-retry diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 37ccecc0562..fa4839fc094 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -102,7 +102,6 @@ review-deploy: - deploy || (display_deployment_debug && exit 1) - verify_deploy || exit 1 - disable_sign_ups || (delete_release && exit 1) - - create_sample_projects after_script: # Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan. # Set DAST_RUN to true when jobs are manually scheduled. @@ -113,6 +112,22 @@ review-deploy: expire_in: 7 days when: always +review-deploy-sample-projects: + extends: + - .review-workflow-base + - .review:rules:review-deploy + stage: deploy + needs: ["review-deploy"] + before_script: + - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION) + - export GITALY_VERSION=$(<GITALY_SERVER_VERSION) + - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION) + - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt + - *base-before_script + script: + - date + - create_sample_projects + .review-stop-base: extends: .review-workflow-base environment: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index f86b2d917f4..83590a2861c 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -806,30 +806,28 @@ - <<: *if-merge-request changes: *code-backstage-patterns -.frontend:rules:jest:minimal:as-if-foss: +.frontend:rules:jest:as-if-foss: rules: - !reference [".strict-ee-only-rules", rules] - - <<: *if-security-merge-request - changes: *code-backstage-patterns - when: never - <<: *if-merge-request-labels-as-if-foss - when: never - <<: *if-merge-request-labels-run-all-jest - when: never - - <<: *if-merge-request-labels-run-all-rspec - when: never - - <<: *if-merge-request - changes: *startup-css-patterns + - <<: *if-security-merge-request + changes: *code-backstage-patterns + - <<: *if-merge-request-not-approved when: never - <<: *if-merge-request - changes: *ci-patterns + changes: *frontend-patterns-for-as-if-foss + +.frontend:rules:jest:minimal:as-if-foss: + rules: + - !reference [".strict-ee-only-rules", rules] + - !reference [".rails:rules:minimal-default-rules", rules] + - <<: *if-merge-request-labels-run-all-jest when: never - <<: *if-fork-merge-request when: never - <<: *if-merge-request - changes: *core-frontend-patterns - - <<: *if-merge-request - changes: *code-backstage-patterns + changes: *frontend-patterns-for-as-if-foss .frontend:rules:eslint-as-if-foss: rules: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4965d44ab0d..b8d3d239aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 15.3.3 (2022-09-01) + +### Fixed (5 changes) + +- [Skip file removal if GitLab managed replication is disabled](gitlab-org/gitlab@dbec61270621df70775c98946d09deca913bd187) ([merge request](gitlab-org/gitlab!96556)) **GitLab Enterprise Edition** +- [Geo: Fix redirects of LFS transfer downloads](gitlab-org/gitlab@98092958c879d1dc9dda0ba2953ba548aa0b93c0) ([merge request](gitlab-org/gitlab!96654)) **GitLab Enterprise Edition** +- [Improve blame link feature](gitlab-org/gitlab@163cadb49f96951a0f747d61a8cd1cb92b7d4296) ([merge request](gitlab-org/gitlab!96654)) +- [Bypass earliest date validation in importing of iteration cadences](gitlab-org/gitlab@66f56eb2551a302d80ca0891ff0bddec1c84f025) ([merge request](gitlab-org/gitlab!96654)) **GitLab Enterprise Edition** +- [Fix user recent activity links for work item actions](gitlab-org/gitlab@9d9368545847cf558fad26a64b216a00b2db36b4) ([merge request](gitlab-org/gitlab!96654)) + ## 15.3.2 (2022-08-30) ### Security (17 changes) diff --git a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql index 98b51e8c2c4..75e1daaafbf 100644 --- a/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql +++ b/app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql @@ -12,6 +12,7 @@ query getJobs($fullPath: ID!, $after: String, $first: Int = 30, $statuses: [CiJo nodes { artifacts { nodes { + id downloadPath fileType } diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js index 9cea89f4990..7e331bdd91d 100644 --- a/app/assets/javascripts/persistent_user_callout.js +++ b/app/assets/javascripts/persistent_user_callout.js @@ -7,12 +7,13 @@ const DEFERRED_LINK_CLASS = 'deferred-link'; export default class PersistentUserCallout { constructor(container, options = container.dataset) { - const { dismissEndpoint, featureId, groupId, namespaceId, deferLinks } = options; + const { dismissEndpoint, featureId, groupId, namespaceId, projectId, deferLinks } = options; this.container = container; this.dismissEndpoint = dismissEndpoint; this.featureId = featureId; this.groupId = groupId; this.namespaceId = namespaceId; + this.projectId = projectId; this.deferLinks = parseBoolean(deferLinks); this.closeButtons = this.container.querySelectorAll('.js-close'); @@ -58,6 +59,7 @@ export default class PersistentUserCallout { feature_name: this.featureId, group_id: this.groupId, namespace_id: this.namespaceId, + project_id: this.projectId, }) .then(() => { this.container.remove(); diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js index ead512e3574..04f0caa1ca3 100644 --- a/app/assets/javascripts/persistent_user_callouts.js +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -17,6 +17,7 @@ const PERSISTENT_USER_CALLOUTS = [ '.js-submit-license-usage-data-banner', '.js-project-usage-limitations-callout', '.js-namespace-storage-alert', + '.js-web-hook-disabled-callout', ]; const initCallouts = () => { diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql index 641ec7a3cf6..0ed8f596d3d 100644 --- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql +++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_jobs.query.graphql @@ -12,6 +12,7 @@ query getPipelineJobs($fullPath: ID!, $iid: ID!, $after: String) { nodes { artifacts { nodes { + id downloadPath fileType } diff --git a/app/assets/javascripts/vue_shared/security_reports/graphql/fragments/job_artifacts.fragment.graphql b/app/assets/javascripts/vue_shared/security_reports/graphql/fragments/job_artifacts.fragment.graphql index 829b9d9f9d8..981d01cc81a 100644 --- a/app/assets/javascripts/vue_shared/security_reports/graphql/fragments/job_artifacts.fragment.graphql +++ b/app/assets/javascripts/vue_shared/security_reports/graphql/fragments/job_artifacts.fragment.graphql @@ -6,6 +6,7 @@ fragment JobArtifacts on Pipeline { name artifacts { nodes { + id downloadPath fileType } diff --git a/app/assets/javascripts/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql b/app/assets/javascripts/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql index 2e80db30e9a..9c5090cfc28 100644 --- a/app/assets/javascripts/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql +++ b/app/assets/javascripts/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql @@ -15,6 +15,7 @@ query securityReportDownloadPaths( name artifacts { nodes { + id downloadPath fileType } diff --git a/app/graphql/types/ci/job_artifact_type.rb b/app/graphql/types/ci/job_artifact_type.rb index a6ab445702c..6346d50de3a 100644 --- a/app/graphql/types/ci/job_artifact_type.rb +++ b/app/graphql/types/ci/job_artifact_type.rb @@ -6,6 +6,9 @@ module Types class JobArtifactType < BaseObject graphql_name 'CiJobArtifact' + field :id, Types::GlobalIDType[::Ci::JobArtifact], null: false, + description: 'ID of the artifact.' + field :download_path, GraphQL::Types::String, null: true, description: "URL for downloading the artifact's file." @@ -16,6 +19,12 @@ module Types description: 'File name of the artifact.', method: :filename + field :size, GraphQL::Types::Int, null: false, + description: 'Size of the artifact in bytes.' + + field :expire_at, Types::TimeType, null: true, + description: 'Expiry date of the artifact.' + def download_path ::Gitlab::Routing.url_helpers.download_project_job_artifacts_path( object.project, diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index a910d3d7c9d..62e66b9a3ea 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module DropdownsHelper + # rubocop:disable Metrics/CyclomaticComplexity def dropdown_tag(toggle_text, options: {}, &block) content_tag :div, class: "dropdown #{options[:wrapper_class] if options.key?(:wrapper_class)}" do data_attr = { toggle: "dropdown" } @@ -16,7 +17,8 @@ module DropdownsHelper end content_tag_options = { class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}" } - content_tag_options[:data] = { qa_selector: "#{options[:dropdown_qa_selector]}" } if options[:dropdown_qa_selector] + content_tag_options[:data] = options[:dropdown_qa_selector] ? { qa_selector: "#{options[:dropdown_qa_selector]}" } : {} + content_tag_options[:data][:testid] = "#{options[:dropdown_testid]}" if options[:dropdown_testid] dropdown_output << content_tag(:div, content_tag_options) do output = [] @@ -46,6 +48,7 @@ module DropdownsHelper dropdown_output.html_safe end end + # rubocop:enable Metrics/CyclomaticComplexity def dropdown_toggle(toggle_text, data_attr, options = {}) default_label = data_attr[:default_label] diff --git a/app/helpers/web_hooks/web_hooks_helper.rb b/app/helpers/web_hooks/web_hooks_helper.rb index 95122750c2f..e95b90c69ef 100644 --- a/app/helpers/web_hooks/web_hooks_helper.rb +++ b/app/helpers/web_hooks/web_hooks_helper.rb @@ -5,6 +5,7 @@ module WebHooks EXPIRY_TTL = 1.hour def show_project_hook_failed_callout?(project:) + return false if project_hook_page? return false unless current_user return false unless Feature.enabled?(:webhooks_failed_callout, project) return false unless Feature.enabled?(:web_hooks_disable_failed, project) @@ -23,5 +24,9 @@ module WebHooks ProjectHook.for_projects(project).disabled.exists? end end + + def project_hook_page? + current_controller?('projects/hooks') || current_controller?('projects/hook_logs') + end end end diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 6921c051361..eabb7e51227 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -23,119 +23,120 @@ = last_check_message.html_safe .row .col-md-6 - .card - .card-header + = render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }) do |c| + - c.header do = _('Project info:') - %ul.content-list - %li - %span.light - = _('Name:') - %strong - = link_to @project.name, project_path(@project) - %li - %span.light - = _('Namespace:') - %strong - - if @project.namespace - = link_to @project.namespace.human_name, [:admin, @project.personal? ? @project.namespace.owner : @project.group] - - else - = s_('ProjectSettings|Global') - %li - %span.light - = _('Owned by:') - %strong - - if @project.owners.any? - = safe_join(@project.owners.map { |owner| link_to(owner.name, [:admin, owner]) }, ", ".html_safe) - - else - = _('(deleted)') - - %li - %span.light - = _('Created by:') - %strong - = @project.creator.try(:name) || _('(deleted)') - - %li - %span.light - = _('Created on:') - %strong - = @project.created_at.to_s(:medium) - - %li - %span.light - = _('ID:') - %strong - = @project.id - - %li - %span.light - = _('http:') - %strong - = link_to @project.http_url_to_repo, project_path(@project) - %li - %span.light - = _('ssh:') - %strong - = link_to @project.ssh_url_to_repo, project_path(@project) - - if @project.repository.exists? - %li + - c.body do + %ul.content-list + %li{ class: 'gl-px-5!' } %span.light - = _('Gitaly storage name:') + = _('Name:') %strong - = @project.repository.storage - %li + = link_to @project.name, project_path(@project) + %li{ class: 'gl-px-5!' } %span.light - = _('Gitaly relative path:') + = _('Namespace:') %strong - = @project.repository.relative_path - - %li - = render 'shared/storage_counter_statistics', storage_size: @project.statistics&.storage_size, storage_details: @project.statistics - - %li + - if @project.namespace + = link_to @project.namespace.human_name, [:admin, @project.personal? ? @project.namespace.owner : @project.group] + - else + = s_('ProjectSettings|Global') + %li{ class: 'gl-px-5!' } %span.light - = _('last commit:') + = _('Owned by:') %strong - = last_commit(@project) + - if @project.owners.any? + = safe_join(@project.owners.map { |owner| link_to(owner.name, [:admin, owner]) }, ", ".html_safe) + - else + = _('(deleted)') - %li + %li{ class: 'gl-px-5!' } %span.light - = _('Git LFS status:') + = _('Created by:') %strong - = project_lfs_status(@project) - = link_to sprite_icon('question-o'), help_page_path('topics/git/lfs/index') - - else - %li - %span.light - = _('repository:') - %strong.cred - = _('does not exist') + = @project.creator.try(:name) || _('(deleted)') - - if @project.archived? - %li + %li{ class: 'gl-px-5!' } %span.light - = _('archived:') + = _('Created on:') %strong - = _('project is read-only') + = @project.created_at.to_s(:medium) - = render_if_exists "shared_runner_status", project: @project + %li{ class: 'gl-px-5!' } + %span.light + = _('ID:') + %strong + = @project.id - %li - %span.light - = _('access:') - %strong - %span{ class: visibility_level_color(@project.visibility_level) } - = visibility_level_icon(@project.visibility_level) - = visibility_level_label(@project.visibility_level) + %li{ class: 'gl-px-5!' } + %span.light + = _('http:') + %strong + = link_to @project.http_url_to_repo, project_path(@project) + %li{ class: 'gl-px-5!' } + %span.light + = _('ssh:') + %strong + = link_to @project.ssh_url_to_repo, project_path(@project) + - if @project.repository.exists? + %li{ class: 'gl-px-5!' } + %span.light + = _('Gitaly storage name:') + %strong + = @project.repository.storage + %li{ class: 'gl-px-5!' } + %span.light + = _('Gitaly relative path:') + %strong + = @project.repository.relative_path + + %li{ class: 'gl-px-5!' } + = render 'shared/storage_counter_statistics', storage_size: @project.statistics&.storage_size, storage_details: @project.statistics + + %li{ class: 'gl-px-5!' } + %span.light + = _('last commit:') + %strong + = last_commit(@project) + + %li{ class: 'gl-px-5!' } + %span.light + = _('Git LFS status:') + %strong + = project_lfs_status(@project) + = link_to sprite_icon('question-o'), help_page_path('topics/git/lfs/index') + - else + %li{ class: 'gl-px-5!' } + %span.light + = _('repository:') + %strong.cred + = _('does not exist') + + - if @project.archived? + %li{ class: 'gl-px-5!' } + %span.light + = _('archived:') + %strong + = _('project is read-only') + + = render_if_exists "admin/projects/shared_runner_status", project: @project + + %li{ class: 'gl-px-5!' } + %span.light + = _('access:') + %strong + %span{ class: visibility_level_color(@project.visibility_level) } + = visibility_level_icon(@project.visibility_level) + = visibility_level_label(@project.visibility_level) = render 'shared/custom_attributes', custom_attributes: @project.custom_attributes = render_if_exists 'admin/projects/geo_status_widget', locals: { project: @project } - .card - .card-header + = render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }) do |c| + - c.header do = s_('ProjectSettings|Transfer project') - .card-body + - c.body do = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| .form-group.row .col-sm-3.col-form-label @@ -150,10 +151,10 @@ .offset-sm-3.col-sm-9 = f.submit _('Transfer'), class: 'gl-button btn btn-confirm' - .card.repository-check - .card-header + = render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5 repository-check' }) do |c| + - c.header do = _("Repository check") - .card-body + - c.body do = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f| .form-group - if @project.last_repository_check_at.nil? @@ -172,34 +173,36 @@ .col-md-6 - if @group - .card - .card-header + = render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }, footer_options: { class: 'gl-p-4' }) do |c| + - c.header do %strong= @group.name = _('group members') = gl_badge_tag @group_members.size = render 'shared/members/manage_access_button', path: group_group_members_path(@group) - %ul.content-list.members-list - = render partial: 'shared/members/member', - collection: @group_members, as: :member, - locals: { membership_source: @project, - group: @group, - current_user_is_group_owner: current_user_is_group_owner } - .card-footer + - c.body do + %ul.content-list.members-list + = render partial: 'shared/members/member', + collection: @group_members, as: :member, + locals: { membership_source: @project, + group: @group, + current_user_is_group_owner: current_user_is_group_owner } + - c.footer do = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' = render 'shared/members/requests', membership_source: @project, group: @group, requesters: @requesters - .card - .card-header + = render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }, footer_options: { class: 'gl-p-4' }) do |c| + - c.header do %strong= @project.name = _('project members') = gl_badge_tag @project.users.size = render 'shared/members/manage_access_button', path: project_project_members_path(@project) - %ul.content-list.project_members.members-list - = render partial: 'shared/members/member', - collection: @project_members, as: :member, - locals: { membership_source: @project, - group: @group, - current_user_is_group_owner: current_user_is_group_owner } - .card-footer + - c.body do + %ul.content-list.project_members.members-list + = render partial: 'shared/members/member', + collection: @project_members, as: :member, + locals: { membership_source: @project, + group: @group, + current_user_is_group_owner: current_user_is_group_owner } + - c.footer do = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/sessions/_new_base.html.haml b/app/views/admin/sessions/_new_base.html.haml index 65eb1358b40..b755b4a442c 100644 --- a/app/views/admin/sessions/_new_base.html.haml +++ b/app/views/admin/sessions/_new_base.html.haml @@ -1,7 +1,7 @@ = form_tag(admin_session_path, method: :post, class: 'new_user gl-show-field-errors', 'aria-live': 'assertive') do .form-group = label_tag :user_password, _('Password'), class: 'label-bold' - = password_field_tag 'user[password]', nil, class: 'form-control', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' } + = password_field_tag 'user[password]', nil, class: 'form-control', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field', testid: 'password-field' } .submit-container.move-submit-down = submit_tag _('Enter Admin Mode'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'enter_admin_mode_button' } diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index ed6cecdcc3d..4edb0f324dc 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -2,7 +2,7 @@ = render 'shared/event_filter' .controls = link_to dashboard_projects_path(rss_url_options), class: 'btn gl-button btn-default btn-icon d-none d-sm-inline-flex has-tooltip', title: 'Subscribe' do - = sprite_icon('rss', css_class: 'qa-rss-icon gl-icon') + = sprite_icon('rss', css_class: 'gl-icon') .content_list .loading diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 5a322a8f89b..e0de8824625 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -4,7 +4,7 @@ = f.text_field :login, value: @invite_email, class: 'form-control gl-form-input top js-username-field', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field', testid: 'username-field' } .form-group.gl-px-5 = f.label :password, class: "label-bold #{'gl-mb-1' if Feature.enabled?(:restyle_login_page, @project)}" - = f.password_field :password, class: 'form-control gl-form-input bottom', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' } + = f.password_field :password, class: 'form-control gl-form-input bottom', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field', testid: 'password-field' } - if devise_mapping.rememberable? .gl-px-5 .gl-display-inline-block diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index 336954d00b0..b7ba8870df5 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,2 +1,2 @@ = gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do - = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { qa_selector: 'sign_in_tab' } } + = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { qa_selector: 'sign_in_tab', testid: 'sign-in-tab' } } diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index 0ef4a30d820..9843f6f6d05 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -8,7 +8,7 @@ = render_if_exists "devise/shared/kerberos_tab" - ldap_servers.each_with_index do |server, i| %li.nav-item - = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab' }, role: 'tab' + = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab', testid: 'ldap-tab' }, role: 'tab' = render_if_exists 'devise/shared/tab_smartcard' diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml index 757c0a836f3..0d6c3e74ce8 100644 --- a/app/views/groups/_activities.html.haml +++ b/app/views/groups/_activities.html.haml @@ -2,7 +2,7 @@ = render 'shared/event_filter', show_group_events: @group.supports_events? .controls = link_to group_path(@group, rss_url_options), class: 'btn gl-button btn-default btn-icon d-none d-sm-inline-flex has-tooltip' , title: _('Subscribe') do - = sprite_icon('rss', css_class: 'qa-rss-icon gl-icon') + = sprite_icon('rss', css_class: 'gl-icon') .content_list .loading diff --git a/app/views/layouts/_google_tag_manager_head.html.haml b/app/views/layouts/_google_tag_manager_head.html.haml index f5c823465be..97e118aba93 100644 --- a/app/views/layouts/_google_tag_manager_head.html.haml +++ b/app/views/layouts/_google_tag_manager_head.html.haml @@ -6,19 +6,20 @@ function gtag(){dataLayer.push(arguments);} gtag('consent', 'default', { - 'analytics_storage': 'denied', - 'ad_storage': 'denied', - 'functionality_storage': 'denied', - 'region': ['EU', 'UK', 'PE', 'RU'], - 'wait_for_update': 500 - }); - gtag('consent', 'default', { 'analytics_storage': 'granted', 'ad_storage': 'granted', 'functionality_storage': 'granted', 'wait_for_update': 500 }); + gtag('consent', 'default', { + 'analytics_storage': 'denied', + 'ad_storage': 'denied', + 'functionality_storage': 'denied', + 'region': ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO', 'GB', 'PE', 'RU'], + 'wait_for_update': 500 + }); + - if Feature.enabled?(:gtm_nonce, type: :ops) = javascript_tag nonce: content_security_policy_nonce do :plain diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 59d4c81358d..014e26c7613 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -27,6 +27,7 @@ %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %main.content{ id: "content-body", **page_itemtype } = render "layouts/flash", extra_flash_class: 'limit-container-width' + = yield :after_flash_content = yield :before_content = yield = yield :after_content diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c876d82f49c..836fd3b4c31 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -116,7 +116,7 @@ = render "layouts/nav/top_nav" - e.control {} - if header_link?(:user_dropdown) - %li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { track_label: "profile_dropdown", track_action: "click_dropdown", track_value: "", qa_selector: 'user_menu' }, class: ('mr-0' if has_impersonation_link) } + %li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { track_label: "profile_dropdown", track_action: "click_dropdown", track_value: "", qa_selector: 'user_menu', testid: 'user-menu' }, class: ('mr-0' if has_impersonation_link) } = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar", alt: current_user.name = render_if_exists 'layouts/header/user_notification_dot', project: project, namespace: group diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 9503e874fd0..9e5819c5189 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -18,4 +18,6 @@ :plain window.uploads_path = "#{project_uploads_path(project)}"; += dispensable_render_if_exists "shared/web_hooks/web_hook_disabled_alert" + = render template: "layouts/application" diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml index d031842be95..38c6dfae411 100644 --- a/app/views/notify/new_ssh_key_email.html.haml +++ b/app/views/notify/new_ssh_key_email.html.haml @@ -1,10 +1,4 @@ -%p - Hi #{sanitize_name(@user.name)}! -%p - A new public key was added to your account: -%p - title: - %code= @key.title -%p - If this key was added in error, you can remove it under - = link_to "SSH Keys", profile_keys_url +- name = sanitize_name(@user.name) +- key_title = html_escape(@key.title) += (s_("Notify|%{paragraph_start}Hi %{name}!%{paragraph_end} %{paragraph_start}A new public key was added to your account:%{paragraph_end} %{paragraph_start}title: %{key_title}%{paragraph_end} %{paragraph_start}If this key was added in error, you can remove it under %{removal_link}%{paragraph_end}") % { paragraph_start: '<p>'.html_safe, + paragraph_end: '</p>'.html_safe, name: name, key_title: content_tag(:code, key_title), removal_link: link_to(_("SSH Keys"), profile_keys_url) }).html_safe diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 402affc7b0e..118f6fb1296 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -4,7 +4,7 @@ = render 'shared/event_filter' .controls.gl-display-flex = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn gl-button btn-default btn-icon d-none d-sm-inline-flex has-tooltip' do - = sprite_icon('rss', css_class: 'qa-rss-icon gl-icon') + = sprite_icon('rss', css_class: 'gl-icon') - if is_project_overview && can?(current_user, :download_code, @project) .project-clone-holder.d-none.d-md-inline-flex.gl-ml-2 = render "projects/buttons/clone", dropdown_class: 'dropdown-menu-right' diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 4007b657403..6b06584ea25 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -28,7 +28,7 @@ = search_field_tag :search, params[:search], { placeholder: _('Search by message'), id: 'commits-search', class: 'form-control gl-form-input input-short gl-mt-3 gl-sm-mt-0 gl-min-w-full', spellcheck: false } .control.d-none.d-md-block = link_to project_commits_path(@project, @id, rss_url_options), title: _("Commits feed"), class: 'btn gl-button btn-default btn-icon' do - = sprite_icon('rss', css_class: 'qa-rss-icon') + = sprite_icon('rss') = render_if_exists 'projects/commits/mirror_status' diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index 20cd45be6da..34fe9a29068 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -1,14 +1,14 @@ - content_for :merge_access_levels do .merge_access_levels-container = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge-select wide', - dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown rspec-allowed-to-merge-dropdown capitalize-header', - data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) + options: { toggle_class: 'js-allowed-to-merge wide', + dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_qa_selector: 'allowed_to_merge_dropdown_content', dropdown_testid: 'allowed-to-merge-dropdown', + data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes', qa_selector: 'allowed_to_merge_dropdown' }}) - content_for :push_access_levels do .push_access_levels-container = dropdown_tag('Select', - options: { toggle_class: "js-allowed-to-push qa-allowed-to-push-select js-multiselect wide", - dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown rspec-allowed-to-push-dropdown capitalize-header', - data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) + options: { toggle_class: "js-allowed-to-push js-multiselect wide", + dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_qa_selector: 'allowed_to_push_dropdown_content' , dropdown_testid: 'allowed-to-push-dropdown', + data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes', qa_selector: 'allowed_to_push_dropdown' }}) = render 'projects/protected_branches/shared/create_protected_branch' diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml index 5964f2bfeda..64db51d5df2 100644 --- a/app/views/projects/protected_branches/shared/_branches_list.html.haml +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -1,4 +1,4 @@ -.protected-branches-list.js-protected-branches-list.qa-protected-branches-list +.protected-branches-list.js-protected-branches-list{ data: { testid: 'protected-branches-list' } } - if @protected_branches.empty? .card-header.bg-white = s_("ProtectedBranch|Protected branch (%{protected_branches_count})") % { protected_branches_count: 0 } diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml index 6dd3b2e8d5e..098bd4a7eeb 100644 --- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -1,6 +1,6 @@ - can_admin_project = can?(current_user, :admin_project, @project) -%tr.qa-protected-branch.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } +%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), testid: 'protected-branch' } } %td %span.ref-name= protected_branch.name diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 2721f94134c..fda797f3228 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -10,7 +10,7 @@ .nav-controls #js-tags-sort-dropdown{ data: { filter_tags_path: filter_tags_path(search: @search, sort: @sort), sort_options: tags_sort_options_hash.to_json } } = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn gl-button btn-default btn-icon has-tooltip gl-ml-auto' do - = sprite_icon('rss', css_class: 'gl-icon qa-rss-icon') + = sprite_icon('rss', css_class: 'gl-icon') - if can?(current_user, :admin_tag, @project) = link_to new_project_tag_path(@project), class: 'btn gl-button btn-confirm', data: { qa_selector: "new_tag_button" } do = s_('TagsPage|New tag') diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml index f0e4b915ac8..69ff477d415 100644 --- a/app/views/shared/issuable/_feed_buttons.html.haml +++ b/app/views/shared/issuable/_feed_buttons.html.haml @@ -1,7 +1,7 @@ - show_calendar_button = local_assigns.fetch(:show_calendar_button, true) = link_to safe_params.merge(rss_url_options), class: 'btn gl-button btn-default btn-icon has-tooltip', data: { container: 'body', testid: 'rss-feed-link' }, title: _('Subscribe to RSS feed') , 'aria-label': _('Subscribe to RSS feed') do - = sprite_icon('rss', css_class: 'qa-rss-icon') + = sprite_icon('rss') - if show_calendar_button = link_to safe_params.merge(calendar_url_options), class: 'btn gl-button btn-default btn-icon has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar'), 'aria-label': _('Subscribe to calendar') do diff --git a/app/views/shared/web_hooks/_index.html.haml b/app/views/shared/web_hooks/_index.html.haml index 5d07b0f95ab..5ec82ad6702 100644 --- a/app/views/shared/web_hooks/_index.html.haml +++ b/app/views/shared/web_hooks/_index.html.haml @@ -1,5 +1,5 @@ %hr -.card +.card#webhooks-index .card-header %h5 = hook_class.underscore.humanize.titleize.pluralize diff --git a/app/views/shared/web_hooks/_web_hook_disabled_alert.html.haml b/app/views/shared/web_hooks/_web_hook_disabled_alert.html.haml new file mode 100644 index 00000000000..d9155b397b8 --- /dev/null +++ b/app/views/shared/web_hooks/_web_hook_disabled_alert.html.haml @@ -0,0 +1,13 @@ +- return unless show_project_hook_failed_callout?(project: @project) + +- content_for :after_flash_content do + = render Pajamas::AlertComponent.new(variant: :danger, + title: s_('Webhooks|Webhook disabled'), + alert_options: { class: 'gl-my-4 js-web-hook-disabled-callout', + data: { feature_id: Users::CalloutsHelper::WEB_HOOK_DISABLED, dismiss_endpoint: project_callouts_path, project_id: @project.id, defer_links: 'true'} }) do |c| + = c.body do + = s_('Webhooks|A webhook in this project was automatically disabled after being retried multiple times.') + = succeed '.' do + = link_to _('Learn more'), help_page_path('user/project/integrations/webhooks', anchor: 'troubleshoot-webhooks'), target: '_blank', rel: 'noopener noreferrer' + = c.actions do + = link_to s_('Webhooks|Go to webhooks'), project_hooks_path(@project, anchor: 'webhooks-index'), class: 'btn gl-alert-action btn-confirm gl-button' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 25070138128..952023b3745 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -36,7 +36,7 @@ - if can?(current_user, :read_user_profile, @user) = link_to user_path(@user, rss_url_options), class: link_classes + 'btn gl-button btn-default btn-icon has-tooltip', title: s_('UserProfile|Subscribe'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do - = sprite_icon('rss', css_class: 'qa-rss-icon') + = sprite_icon('rss') - if current_user && current_user.admin? = link_to [:admin, @user], class: link_classes + 'btn gl-button btn-default btn-icon', title: s_('UserProfile|View user in admin area'), data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 99dc2b88e3e..f4ba752a6a1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10245,8 +10245,11 @@ CI/CD variables for a GitLab instance. | Name | Type | Description | | ---- | ---- | ----------- | | <a id="cijobartifactdownloadpath"></a>`downloadPath` | [`String`](#string) | URL for downloading the artifact's file. | +| <a id="cijobartifactexpireat"></a>`expireAt` | [`Time`](#time) | Expiry date of the artifact. | | <a id="cijobartifactfiletype"></a>`fileType` | [`JobArtifactFileType`](#jobartifactfiletype) | File type of the artifact. | +| <a id="cijobartifactid"></a>`id` | [`CiJobArtifactID!`](#cijobartifactid) | ID of the artifact. | | <a id="cijobartifactname"></a>`name` | [`String`](#string) | File name of the artifact. | +| <a id="cijobartifactsize"></a>`size` | [`Int!`](#int) | Size of the artifact in bytes. | ### `CiJobTokenScopeType` @@ -11824,6 +11827,7 @@ Relationship between an epic and an issue. | <a id="epicissueepicissueid"></a>`epicIssueId` | [`ID!`](#id) | ID of the epic-issue relation. | | <a id="epicissueescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | Escalation policy associated with the issue. Available for issues which support escalation. | | <a id="epicissueescalationstatus"></a>`escalationStatus` | [`IssueEscalationStatus`](#issueescalationstatus) | Escalation status of the issue. | +| <a id="epicissuehasepic"></a>`hasEpic` | [`Boolean!`](#boolean) | Indicates if the issue belongs to an epic. Can return true and not show an associated epic when the user has no access to the epic. | | <a id="epicissuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. | | <a id="epicissuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. | | <a id="epicissuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. | @@ -13329,6 +13333,7 @@ Describes an issuable resource link for incident issues. | <a id="issueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. | | <a id="issueescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | Escalation policy associated with the issue. Available for issues which support escalation. | | <a id="issueescalationstatus"></a>`escalationStatus` | [`IssueEscalationStatus`](#issueescalationstatus) | Escalation status of the issue. | +| <a id="issuehasepic"></a>`hasEpic` | [`Boolean!`](#boolean) | Indicates if the issue belongs to an epic. Can return true and not show an associated epic when the user has no access to the epic. | | <a id="issuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. | | <a id="issuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. | | <a id="issuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. | @@ -21246,6 +21251,12 @@ A `CiBuildID` is a global ID. It is encoded as a string. An example `CiBuildID` is: `"gid://gitlab/Ci::Build/1"`. +### `CiJobArtifactID` + +A `CiJobArtifactID` is a global ID. It is encoded as a string. + +An example `CiJobArtifactID` is: `"gid://gitlab/Ci::JobArtifact/1"`. + ### `CiPipelineID` A `CiPipelineID` is a global ID. It is encoded as a string. diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md index d68432e567e..7f795356c15 100644 --- a/doc/ci/yaml/artifacts_reports.md +++ b/doc/ci/yaml/artifacts_reports.md @@ -146,6 +146,30 @@ GitLab can display the results of one or more reports in: - The [Project Vulnerability report](../../user/application_security/vulnerability_report/index.md). - The [security dashboard](../../user/application_security/security_dashboard/index.md). +## `artifacts:reports:cyclonedx` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/360766) in GitLab 15.3 + +This report is a Software Bill of Materials describing the components of a project +following the [cyclonedx](https://cyclonedx.org/docs/1.4) protocol format. + +You can specify multiple cyclonedx reports per job. These can be either supplied +as a list of filenames, a filename pattern, or both: + +- List of filenames: `cyclonedx: [gl-sbom-npm-npm.cdx.json, gl-sbom-bundler-gem.cdx.json]`. +- A filename pattern: `cyclonedx: gl-sbom-*.json`. +- Combination of both of the above: `cyclonedx: [gl-sbom-*.json, my-cyclonedx.json]`. + +Below is an example of a job exposing cyclonedx artifacts: + +```yaml +artifacts: + reports: + cyclonedx: + - gl-sbom-npm-npm.cdx.json + - gl-sbom-bundler-gem.cdx.json +``` + ## `artifacts:reports:dast` **(ULTIMATE)** The `dast` report collects [DAST vulnerabilities](../../user/application_security/dast/index.md). The collected DAST @@ -315,27 +339,3 @@ GitLab can display the results of one or more reports in the merge request [terraform widget](../../user/infrastructure/iac/mr_integration.md#output-terraform-plan-information-into-a-merge-request). For more information, see [Output `terraform plan` information into a merge request](../../user/infrastructure/iac/mr_integration.md). - -## `artifacts:reports:cyclonedx` - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/360766) in GitLab 15.3 - -This report is a Software Bill of Materials describing the components of a project -following the [cyclonedx](https://cyclonedx.org/docs/1.4) protocol format. - -You can specify multiple cyclonedx reports per job. These can be either supplied -as a list of filenames, a filename pattern, or both: - -- List of filenames: `cyclonedx: [gl-sbom-npm-npm.cdx.json, gl-sbom-bundler-gem.cdx.json]`. -- A filename pattern: `cyclonedx: gl-sbom-*.json`. -- Combination of both of the above: `cyclonedx: [gl-sbom-*.json, my-cyclonedx.json]`. - -Below is an example of a job exposing cyclonedx artifacts: - -```yaml -artifacts: - reports: - cyclonedx: - - gl-sbom-npm-npm.cdx.json - - gl-sbom-bundler-gem.cdx.json -``` diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index 80b07949f8e..ff55d7404cd 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -257,7 +257,7 @@ Here is an example of when `let_it_be` cannot be used, but `let_it_be_with_reloa ```ruby let_it_be(:user) { create(:user) } -let_it_be_with_reload(:project) { create(:project) } # The test will fail if `let_it_be` is used +let_it_be_with_reload(:project) { create(:project) } # The test will fail if `let_it_be` is used context 'with a developer' do before do @@ -1107,12 +1107,11 @@ Snowplow performs **runtime type checks** by using the [contracts gem](https://r Because Snowplow is **by default disabled in tests and development**, it can be hard to **catch exceptions** when mocking `Gitlab::Tracking`. -To catch runtime errors due to type checks, you can enable Snowplow in tests. Mark the spec with -`:snowplow` and use the `expect_snowplow_event` helper, which checks for +To catch runtime errors due to type checks you can use `expect_snowplow_event`, which checks for calls to `Gitlab::Tracking#event`. ```ruby -describe '#show', :snowplow do +describe '#show' do it 'tracks snowplow events' do get :show @@ -1137,7 +1136,7 @@ end When you want to ensure that no event got called, you can use `expect_no_snowplow_event`. ```ruby - describe '#show', :snowplow do + describe '#show' do it 'does not track any snowplow events' do get :show diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 4ad4209865a..22a8792bac6 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -420,7 +420,11 @@ it('passes', () => { ### Waiting in tests Sometimes a test needs to wait for something to happen in the application before it continues. -Avoid using [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) because it makes the reason for waiting unclear. Instead use one of the following approaches. + +You should try to avoid: + +- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) because it makes the reason for waiting unclear. Additionally, it is faked in our tests so its usage is tricky. +- [`setImmediate`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate) because it is no longer supported in Jest 27 and later. See [this epic](https://gitlab.com/groups/gitlab-org/-/epics/7002) for details. #### Promises and Ajax calls @@ -448,14 +452,15 @@ it('waits for an Ajax call', async () => { }); ``` -If you are not able to register handlers to the `Promise`, for example because it is executed in a synchronous Vue life cycle hook, take a look at the [waitFor](#wait-until-axios-requests-finish) helpers or you can flush all pending `Promise`s: +If you cannot register handlers to the `Promise`, for example because it is executed in a synchronous Vue lifecycle hook, take a look at the [`waitFor`](#wait-until-axios-requests-finish) helpers or flush all pending `Promise`s with: **in Jest:** ```javascript -it('waits for an Ajax call', () => { +it('waits for an Ajax call', async () => { synchronousFunction(); - jest.runAllTicks(); + + await waitForPromises(); expect(something).toBe('done'); }); @@ -1073,7 +1078,7 @@ testAction( <!-- vale gitlab.Spelling = NO --> -The Axios Utils mock module located in `spec/frontend/mocks/ce/lib/utils/axios_utils.js` contains two helper methods for Jest tests that spawn HTTP requests. +The Axios Utils mock module located in `spec/frontend/__helpers__/mocks/axios_utils.js` contains two helper methods for Jest tests that spawn HTTP requests. These are very useful if you don't have a handle to the request's Promise, for example when a Vue component does a request as part of its life cycle. <!-- vale gitlab.Spelling = YES --> diff --git a/doc/integration/advanced_search/elasticsearch.md b/doc/integration/advanced_search/elasticsearch.md index 3adb2e974d5..bab7deb9fec 100644 --- a/doc/integration/advanced_search/elasticsearch.md +++ b/doc/integration/advanced_search/elasticsearch.md @@ -223,7 +223,7 @@ The following Elasticsearch settings are available: | `Number of Elasticsearch shards` | Elasticsearch indices are split into multiple shards for performance reasons. In general, you should use at least 5 shards, and indices with tens of millions of documents need to have more shards ([see below](#guidance-on-choosing-optimal-cluster-configuration)). Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). | | `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value increases total disk space required by the index. | | `Limit the number of namespaces and projects that can be indexed` | Enabling this allows you to select namespaces and projects to index. All other namespaces and projects use database search instead. If you enable this option but do not select any namespaces or projects, none are indexed. [Read more below](#limit-the-number-of-namespaces-and-projects-that-can-be-indexed).| -| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html) for details of AWS hosted OpenSearch domain access policy configuration. | +| `Using AWS OpenSearch Service with IAM credentials` | Sign your OpenSearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html) for details of AWS hosted OpenSearch domain access policy configuration. | | `AWS Region` | The AWS region in which your OpenSearch Service is located. | | `AWS Access Key` | The AWS access key. | | `AWS Secret Access Key` | The AWS secret access key. | diff --git a/doc/user/snippets.md b/doc/user/snippets.md index cab18b221c1..325c98355c2 100644 --- a/doc/user/snippets.md +++ b/doc/user/snippets.md @@ -25,7 +25,7 @@ GitLab provides two types of snippets: You can set a [visibility level](public_access.md) for your snippet: public, internal, or private. - **Project snippets**: Always related to a specific project. - Project snippets can be visible publicly or to only group members. + Project snippets can be visible publicly, or to only project members. ## Create snippets diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1eeeca97f69..47500bb567a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1851,6 +1851,9 @@ msgstr "" msgid "AWS Access Key" msgstr "" +msgid "AWS OpenSearch IAM credentials" +msgstr "" + msgid "AWS Secret Access Key" msgstr "" @@ -2910,7 +2913,7 @@ msgstr "" msgid "AdminSettings|Total number of jobs in currently active pipelines" msgstr "" -msgid "AdminSettings|Use AWS hosted Elasticsearch with IAM credentials" +msgid "AdminSettings|Use AWS OpenSearch Service with IAM credentials" msgstr "" msgid "AdminSettings|Users and groups must accept the invitation before they're added to a group or project." @@ -14292,9 +14295,6 @@ msgstr "" msgid "Elapsed time" msgstr "" -msgid "Elasticsearch AWS IAM credentials" -msgstr "" - msgid "Elasticsearch HTTP client timeout value in seconds." msgstr "" @@ -14319,9 +14319,6 @@ msgstr "" msgid "Elasticsearch zero-downtime reindexing" msgstr "" -msgid "Elasticsearch's region." -msgstr "" - msgid "Elastic|None. Select namespaces to index." msgstr "" @@ -26877,6 +26874,9 @@ msgstr "" msgid "Notify|%{mr_highlight}Merge request%{highlight_end} %{mr_link} %{approved_highlight}was approved by%{highlight_end} %{approver_avatar} %{approver_link}" msgstr "" +msgid "Notify|%{paragraph_start}Hi %{name}!%{paragraph_end} %{paragraph_start}A new public key was added to your account:%{paragraph_end} %{paragraph_start}title: %{key_title}%{paragraph_end} %{paragraph_start}If this key was added in error, you can remove it under %{removal_link}%{paragraph_end}" +msgstr "" + msgid "Notify|A new GPG key was added to your account:" msgstr "" @@ -27604,6 +27604,9 @@ msgstr "" msgid "OpenAPI Specification file path or URL" msgstr "" +msgid "OpenSearch's region." +msgstr "" + msgid "Opened" msgstr "" @@ -44038,6 +44041,9 @@ msgstr "" msgid "Webhooks|A subgroup is created or removed." msgstr "" +msgid "Webhooks|A webhook in this project was automatically disabled after being retried multiple times." +msgstr "" + msgid "Webhooks|A wiki page is created or updated." msgstr "" @@ -44080,6 +44086,9 @@ msgstr "" msgid "Webhooks|Feature flag events" msgstr "" +msgid "Webhooks|Go to webhooks" +msgstr "" + msgid "Webhooks|Issues events" msgstr "" @@ -44137,6 +44146,9 @@ msgstr "" msgid "Webhooks|Used to validate received payloads. Sent with the request in the %{code_start}X-Gitlab-Token HTTP%{code_end} header." msgstr "" +msgid "Webhooks|Webhook disabled" +msgstr "" + msgid "Webhooks|Webhook failed to connect" msgstr "" diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 35fc87f717c..a78d8a6ccf4 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -11,14 +11,10 @@ module QA end view 'app/views/projects/protected_branches/_create_protected_branch.html.haml' do - element :allowed_to_push_select element :allowed_to_push_dropdown - element :allowed_to_merge_select + element :allowed_to_push_dropdown_content element :allowed_to_merge_dropdown - end - - view 'app/views/projects/protected_branches/shared/_branches_list.html.haml' do - element :protected_branches_list + element :allowed_to_merge_dropdown_content end view 'app/views/projects/protected_branches/shared/_create_protected_branch.html.haml' do @@ -49,11 +45,11 @@ module QA private def select_allowed(action, allowed) - click_element :"allowed_to_#{action}_select" + click_element :"allowed_to_#{action}_dropdown" allowed[:roles] = Resource::ProtectedBranch::Roles::NO_ONE unless allowed.key?(:roles) - within_element(:"allowed_to_#{action}_dropdown") do + within_element(:"allowed_to_#{action}_dropdown_content") do click_on allowed[:roles][:description] allowed[:users].each { |user| click_on user.username } if allowed.key?(:users) allowed[:groups].each { |group| click_on group.name } if allowed.key?(:groups) diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index bc88b90a2dd..86acf5a05d4 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -205,7 +205,7 @@ RSpec.describe 'Admin::Users::User' do it 'logs in as the user when impersonate is clicked' do subject - find('[data-qa-selector="user_menu"]').click # rubocop:disable QA/SelectorUsage + find('[data-testid="user-menu"]').click expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eql(another_user.username) end @@ -241,7 +241,7 @@ RSpec.describe 'Admin::Users::User' do it 'logs out of impersonated user back to original user' do subject - find('[data-qa-selector="user_menu"]').click # rubocop:disable QA/SelectorUsage + find('[data-testid="user-menu"]').click expect(page.find(:css, '[data-testid="user-profile-link"]')['data-user']).to eq(current_user.username) end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index e29911e3263..b96490bd7e7 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -269,7 +269,7 @@ RSpec.describe "User creates issue" do end it 'hides the weight input' do - expect(page).not_to have_selector('.qa-issuable-weight-input') # rubocop:disable QA/SelectorUsage + expect(page).not_to have_selector('[data-testid="issuable-weight-input"]') end it 'shows the incident help text' do diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js index 30eddcee86a..4a339899473 100644 --- a/spec/frontend/design_management/components/design_presentation_spec.js +++ b/spec/frontend/design_management/components/design_presentation_spec.js @@ -525,7 +525,7 @@ describe('Design management design presentation component', () => { { clientX: 10, clientY: 10 }, { mouseup: true }, ).then(() => { - expect(wrapper.emitted('openCommentForm')).toBeFalsy(); + expect(wrapper.emitted('openCommentForm')).toBeUndefined(); }); }); diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js index 57d1511d859..24514d99078 100644 --- a/spec/frontend/pipelines/mock_data.js +++ b/spec/frontend/pipelines/mock_data.js @@ -528,6 +528,7 @@ export const mockPipelineJobsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/101', downloadPath: '/root/ci-project/-/jobs/620/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', @@ -580,6 +581,7 @@ export const mockPipelineJobsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/102', downloadPath: '/root/ci-project/-/jobs/619/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js index a9ad675e538..a0e31243365 100644 --- a/spec/frontend/vue_shared/security_reports/mock_data.js +++ b/spec/frontend/vue_shared/security_reports/mock_data.js @@ -356,12 +356,14 @@ export const securityReportMergeRequestDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/101', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/102', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=secret_detection', fileType: 'SECRET_DETECTION', @@ -378,12 +380,14 @@ export const securityReportMergeRequestDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/103', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/104', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=sast', fileType: 'SAST', @@ -400,12 +404,14 @@ export const securityReportMergeRequestDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/105', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/106', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=sast', fileType: 'SAST', @@ -422,18 +428,21 @@ export const securityReportMergeRequestDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/107', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=archive', fileType: 'ARCHIVE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/108', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/109', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=metadata', fileType: 'METADATA', @@ -468,12 +477,14 @@ export const securityReportPipelineDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/110', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/111', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1399/artifacts/download?file_type=secret_detection', fileType: 'SECRET_DETECTION', @@ -490,12 +501,14 @@ export const securityReportPipelineDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/112', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/113', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1400/artifacts/download?file_type=sast', fileType: 'SAST', @@ -512,12 +525,14 @@ export const securityReportPipelineDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/114', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/115', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1401/artifacts/download?file_type=sast', fileType: 'SAST', @@ -534,18 +549,21 @@ export const securityReportPipelineDownloadPathsQueryResponse = { artifacts: { nodes: [ { + id: 'gid://gitlab/Ci::JobArtifact/116', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=archive', fileType: 'ARCHIVE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/117', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=trace', fileType: 'TRACE', __typename: 'CiJobArtifact', }, { + id: 'gid://gitlab/Ci::JobArtifact/118', downloadPath: '/gitlab-org/secrets-detection-test/-/jobs/1402/artifacts/download?file_type=metadata', fileType: 'METADATA', diff --git a/spec/graphql/types/ci/job_artifact_type_spec.rb b/spec/graphql/types/ci/job_artifact_type_spec.rb index 58b5f9cfcb7..3e054faf0c9 100644 --- a/spec/graphql/types/ci/job_artifact_type_spec.rb +++ b/spec/graphql/types/ci/job_artifact_type_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CiJobArtifact'] do it 'has the correct fields' do - expected_fields = [:download_path, :file_type, :name] + expected_fields = [:id, :download_path, :file_type, :name, :size, :expire_at] expect(described_class).to have_graphql_fields(*expected_fields) end diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb index c8b194919ed..265e1c38b09 100644 --- a/spec/support/helpers/snowplow_helpers.rb +++ b/spec/support/helpers/snowplow_helpers.rb @@ -7,7 +7,7 @@ module SnowplowHelpers # # Examples: # - # describe '#show', :snowplow do + # describe '#show' do # it 'tracks snowplow events' do # get :show # @@ -15,7 +15,7 @@ module SnowplowHelpers # end # end # - # describe '#create', :snowplow do + # describe '#create' do # it 'tracks snowplow events' do # post :create # diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb index 8212f14d6be..81d548e000a 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.rspec-allowed-to-merge-dropdown') do + within('[data-testid="allowed-to-merge-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -35,13 +35,13 @@ RSpec.shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.rspec-allowed-to-merge-dropdown') do + within('[data-testid="allowed-to-merge-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.rspec-allowed-to-push-dropdown') do + within('[data-testid="allowed-to-push-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -83,7 +83,7 @@ RSpec.shared_examples "protected branches > access control > CE" do end find(".js-allowed-to-push").click - within('.rspec-allowed-to-push-dropdown') do + within('[data-testid="allowed-to-push-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -100,13 +100,13 @@ RSpec.shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.rspec-allowed-to-merge-dropdown') do + within('[data-testid="allowed-to-merge-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.rspec-allowed-to-push-dropdown') do + within('[data-testid="allowed-to-push-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end diff --git a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb index 14142793a0d..90b0e600228 100644 --- a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb @@ -23,7 +23,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do find(".js-allowed-to-push").click wait_for_requests - within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage + within('[data-testid="allowed-to-push-dropdown"]') do dropdown_headers = page.all('.dropdown-header').map(&:text) expect(dropdown_headers).to contain_exactly(*all_dropdown_sections) @@ -38,7 +38,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do find(".js-allowed-to-merge").click wait_for_requests - within('.qa-allowed-to-merge-dropdown') do # rubocop:disable QA/SelectorUsage + within('[data-testid="allowed-to-merge-dropdown"]') do dropdown_headers = page.all('.dropdown-header').map(&:text) expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) @@ -68,7 +68,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do find(".js-allowed-to-push").click wait_for_requests - within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage + within('[data-testid="allowed-to-push-dropdown"]') do dropdown_headers = page.all('.dropdown-header').map(&:text) expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb index 0991de21d8d..ad865b084e1 100644 --- a/spec/support/shared_examples/features/rss_shared_examples.rb +++ b/spec/support/shared_examples/features/rss_shared_examples.rb @@ -9,7 +9,7 @@ end RSpec.shared_examples "it has an RSS button with current_user's feed token" do it "shows the RSS button with current_user's feed token" do expect(page) - .to have_css("a:has(.qa-rss-icon)[href*='feed_token=#{user.feed_token}']") # rubocop:disable QA/SelectorUsage + .to have_css("a:has([data-testid='rss-icon'])[href*='feed_token=#{user.feed_token}']") end end @@ -22,7 +22,7 @@ end RSpec.shared_examples "it has an RSS button without a feed token" do it "shows the RSS button without a feed token" do expect(page) - .to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])") # rubocop:disable QA/SelectorUsage + .to have_css("a:has([data-testid='rss-icon']):not([href*='feed_token'])") end end diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb index 97528b6e782..ac35bbef5b4 100644 --- a/spec/views/admin/sessions/new.html.haml_spec.rb +++ b/spec/views/admin/sessions/new.html.haml_spec.rb @@ -19,9 +19,9 @@ RSpec.describe 'admin/sessions/new.html.haml' do it 'shows enter password form' do render - expect(rendered).to have_selector('[data-qa-selector="sign_in_tab"]') # rubocop:disable QA/SelectorUsage + expect(rendered).to have_selector('[data-testid="sign-in-tab"]') expect(rendered).to have_css('#login-pane.active') - expect(rendered).to have_selector('[data-qa-selector="password_field"]') # rubocop:disable QA/SelectorUsage + expect(rendered).to have_selector('[data-testid="password-field"]') end it 'warns authentication not possible if password not set' do @@ -60,7 +60,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do it 'is shown when enabled' do render - expect(rendered).to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage + expect(rendered).to have_selector('[data-testid="ldap-tab"]') expect(rendered).to have_css('.login-box#ldapmain') expect(rendered).to have_field('LDAP Username') expect(rendered).not_to have_content('No authentication methods configured') @@ -71,7 +71,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do render - expect(rendered).not_to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage + expect(rendered).not_to have_selector('[data-testid="ldap-tab"]') expect(rendered).not_to have_field('LDAP Username') expect(rendered).to have_content('No authentication methods configured') end diff --git a/spec/views/devise/sessions/new.html.haml_spec.rb b/spec/views/devise/sessions/new.html.haml_spec.rb index c8e9aa15287..798c891e75c 100644 --- a/spec/views/devise/sessions/new.html.haml_spec.rb +++ b/spec/views/devise/sessions/new.html.haml_spec.rb @@ -55,7 +55,7 @@ RSpec.describe 'devise/sessions/new' do render expect(rendered).to have_selector('.new-session-tabs') - expect(rendered).to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage + expect(rendered).to have_selector('[data-testid="ldap-tab"]') expect(rendered).to have_field('LDAP Username') end @@ -65,7 +65,7 @@ RSpec.describe 'devise/sessions/new' do render expect(rendered).to have_content('No authentication methods configured') - expect(rendered).not_to have_selector('[data-qa-selector="ldap_tab"]') # rubocop:disable QA/SelectorUsage + expect(rendered).not_to have_selector('[data-testid="ldap-tab"]') expect(rendered).not_to have_field('LDAP Username') end end diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb index 79c22871b44..17251049c57 100644 --- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb +++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb @@ -166,7 +166,7 @@ RSpec.describe 'layouts/header/_new_dropdown' do let(:user) { create(:user, :external) } it 'is nil' do - # We have to us `view.render` because `render` causes issues + # We have to use `view.render` because `render` causes issues # https://github.com/rails/rails/issues/41320 expect(view.render("layouts/header/new_dropdown")).to be_nil end diff --git a/spec/views/shared/web_hooks/_web_hook_disabled_alert.html.haml_spec.rb b/spec/views/shared/web_hooks/_web_hook_disabled_alert.html.haml_spec.rb new file mode 100644 index 00000000000..22ed8bb262c --- /dev/null +++ b/spec/views/shared/web_hooks/_web_hook_disabled_alert.html.haml_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'shared/web_hooks/_web_hook_disabled_alert' do + let_it_be(:project) { create(:project) } + + let(:show_project_hook_failed_callout?) { false } + + def after_flash_content + view.content_for(:after_flash_content) + end + + before do + assign(:project, project) + allow(view).to receive(:show_project_hook_failed_callout?).and_return(show_project_hook_failed_callout?) + end + + context 'when show_project_hook_failed_callout? is true' do + let(:show_project_hook_failed_callout?) { true } + + it 'adds alert to `:after_flash_content`' do + render + + expect(after_flash_content).to have_content('Webhook disabled') + end + end + + context 'when show_project_hook_failed_callout? is false' do + it 'does not add alert to `:after_flash_content`' do + # We have to use `view.render` because `render` causes issues + # https://github.com/rails/rails/issues/41320 + view.render('shared/web_hooks/web_hook_disabled_alert') + + expect(after_flash_content).to be_nil + end + end +end |