From 882ad7490a83e5278fafd0f2c79df063d5292341 Mon Sep 17 00:00:00 2001
From: Jennifer Li <jli@gitlab.com>
Date: Tue, 30 Jul 2024 11:10:16 -0700
Subject: [PATCH] Remove CI dependency validation danger

Remove spec
---
 danger/pipeline/Dangerfile                    |   7 +-
 .../plugins/ci_jobs_dependency_validation.rb  |   9 -
 .../ci_jobs_dependency_validation_spec.rb     | 607 ------------------
 .../danger/ci_jobs_dependency_validation.rb   | 297 ---------
 4 files changed, 1 insertion(+), 919 deletions(-)
 delete mode 100644 danger/plugins/ci_jobs_dependency_validation.rb
 delete mode 100644 spec/tooling/danger/ci_jobs_dependency_validation_spec.rb
 delete mode 100644 tooling/danger/ci_jobs_dependency_validation.rb

diff --git a/danger/pipeline/Dangerfile b/danger/pipeline/Dangerfile
index 090faf620ee12..0f936d827dc54 100644
--- a/danger/pipeline/Dangerfile
+++ b/danger/pipeline/Dangerfile
@@ -17,9 +17,4 @@ Please consider the effect of the changes in this merge request on the following
 Please consider communicating these changes to the broader team following the [communication guideline for pipeline changes](https://about.gitlab.com/handbook/engineering/quality/engineering-productivity/#pipeline-changes)
 MSG
 
-if helper.has_ci_changes?
-  markdown(PIPELINE_CHANGES_MESSAGE)
-
-  dependency_validation_message = ci_jobs_dependency_validation.output_message
-  markdown(dependency_validation_message) unless dependency_validation_message.empty?
-end
+markdown(PIPELINE_CHANGES_MESSAGE) if helper.has_ci_changes?
diff --git a/danger/plugins/ci_jobs_dependency_validation.rb b/danger/plugins/ci_jobs_dependency_validation.rb
deleted file mode 100644
index 0d35eb62ed285..0000000000000
--- a/danger/plugins/ci_jobs_dependency_validation.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../tooling/danger/ci_jobs_dependency_validation'
-
-module Danger
-  class CiJobsDependencyValidation < ::Danger::Plugin
-    include Tooling::Danger::CiJobsDependencyValidation
-  end
-end
diff --git a/spec/tooling/danger/ci_jobs_dependency_validation_spec.rb b/spec/tooling/danger/ci_jobs_dependency_validation_spec.rb
deleted file mode 100644
index c5e645ffee31a..0000000000000
--- a/spec/tooling/danger/ci_jobs_dependency_validation_spec.rb
+++ /dev/null
@@ -1,607 +0,0 @@
-# frozen_string_literal: true
-
-require 'gitlab/dangerfiles/spec_helper'
-require 'fast_spec_helper'
-require 'webmock/rspec'
-
-require_relative '../../../tooling/danger/ci_jobs_dependency_validation'
-
-RSpec.describe Tooling::Danger::CiJobsDependencyValidation, feature_category: :tooling do
-  include_context 'with dangerfile'
-
-  let(:ci) { true }
-  let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
-
-  let(:rules_base) do
-    [
-      {
-        'if' => '$CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached"',
-        'changes' => ["doc/index.md"]
-      }
-    ]
-  end
-
-  let(:job_config_base) { { 'rules' => rules_base, 'needs' => [] } }
-  let(:new_condition) { { 'if' => '$NEW_VAR == "true"' } }
-
-  let(:rules_with_new_condition) { [*rules_base, new_condition] }
-
-  let(:validated_jobs_base) do
-    described_class::VALIDATED_JOB_NAMES.index_with { job_config_base }
-  end
-
-  let(:master_merged_yaml) do
-    YAML.dump({
-      'job1' => job_config_base
-    })
-  end
-
-  let(:query) do
-    {
-      content_ref: 'merged_result_commit_sha',
-      dry_run_ref: 'feature_branch',
-      include_jobs: true,
-      dry_run: true
-    }
-  end
-
-  subject(:ci_jobs_dependency_validation) { fake_danger.new(helper: fake_helper) }
-
-  before do
-    stub_env('CI_COMMIT_SHA', 'merged_result_commit_sha')
-
-    allow(ci_jobs_dependency_validation).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint",
-      query: {}) do
-      { 'merged_yaml' => master_merged_yaml }
-    end
-
-    allow(ci_jobs_dependency_validation).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint",
-      query: query) do
-      { 'merged_yaml' => source_branch_merged_yaml }
-    end
-
-    allow(ci_jobs_dependency_validation.helper).to receive(:ci?).and_return(ci)
-    allow(ci_jobs_dependency_validation.helper).to receive(:has_ci_changes?).and_return(true)
-    allow(ci_jobs_dependency_validation.helper).to receive(:mr_source_branch).and_return('feature_branch')
-    allow(ci_jobs_dependency_validation.helper).to receive(:mr_target_branch).and_return('master')
-    allow(ci_jobs_dependency_validation.helper).to receive(:mr_source_project_id).and_return('1')
-    allow(ci_jobs_dependency_validation.helper).to receive(:mr_target_project_id).and_return('1')
-    allow($stdout).to receive(:puts)
-  end
-
-  describe '#output_message' do
-    shared_examples 'output message' do |warning|
-      it 'outputs messages' do
-        if warning
-          expect(ci_jobs_dependency_validation).to receive(:warn).with(described_class::FAILED_VALIDATION_WARNING)
-        else
-          expect(ci_jobs_dependency_validation).not_to receive(:warn)
-        end
-
-        expect(ci_jobs_dependency_validation.output_message).to eq(expected_message)
-      end
-    end
-
-    context 'when not in ci environment' do
-      let(:ci) { false }
-      let(:expected_message) { '' }
-
-      it_behaves_like 'output message'
-    end
-
-    context 'when in ci environment' do
-      context 'with no ci changes' do
-        let(:expected_message) { '' }
-
-        before do
-          allow(ci_jobs_dependency_validation.helper).to receive(:has_ci_changes?).and_return(false)
-        end
-
-        it_behaves_like 'output message'
-      end
-
-      context 'with api fails to retrieve jobs from target branch' do
-        let(:error_msg) { '404 not found' }
-
-        before do
-          allow(
-            ci_jobs_dependency_validation
-          ).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: {}).and_raise(error_msg)
-        end
-
-        it 'warns validation is skipped and outputs empty message' do
-          expect(ci_jobs_dependency_validation).to receive(:warn).with(
-            "#{described_class::SKIPPED_VALIDATION_WARNING}: #{error_msg}"
-          )
-          expect { expect(ci_jobs_dependency_validation.output_message).to eq('') }.tap do |expectation|
-            expectation
-              .to output(<<~MSG).to_stdout
-                Initializing 0 jobs from master ci config...
-              MSG
-          end
-        end
-      end
-
-      context 'with api fails to retrieve jobs from source branch' do
-        let(:error_msg) { '404 not found' }
-
-        before do
-          allow(
-            ci_jobs_dependency_validation
-          ).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: query).and_raise(error_msg)
-        end
-
-        it 'warns validation is skipped and outputs empty message' do
-          expect(ci_jobs_dependency_validation).to receive(:warn).with(
-            "#{described_class::SKIPPED_VALIDATION_WARNING}: #{error_msg}"
-          )
-
-          expect { expect(ci_jobs_dependency_validation.output_message).to eq('') }.tap do |expectation|
-            expectation
-              .to output(<<~MSG).to_stdout
-                Initializing 1 jobs from master ci config...
-                Initializing 0 jobs from feature_branch ci config...
-              MSG
-          end
-        end
-      end
-
-      context 'with api returns nil for merged yaml' do
-        let(:source_branch_merged_yaml) { nil }
-
-        it 'warns validation is skipped and outputs empty message' do
-          expect(ci_jobs_dependency_validation).to receive(:warn).with(
-            "#{described_class::SKIPPED_VALIDATION_WARNING}: no implicit conversion of nil into String"
-          )
-
-          expect(ci_jobs_dependency_validation.output_message).to eq('')
-        end
-      end
-
-      context 'when target branch jobs is empty' do
-        let(:source_branch_merged_yaml) { YAML.dump({}) }
-        let(:expected_message) { '' }
-
-        it_behaves_like 'output message'
-      end
-
-      context 'when source branch jobs is empty' do
-        let(:master_merged_yaml) { YAML.dump({}) }
-        let(:expected_message) { '' }
-
-        it_behaves_like 'output message'
-      end
-
-      context 'when jobs do not have dependencies' do
-        let(:source_branch_merged_yaml) { YAML.dump(validated_jobs_base) }
-
-        let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-        it_behaves_like 'output message'
-      end
-
-      context 'when needed jobs are missing is source branch' do
-        let(:source_branch_merged_yaml) do
-          YAML.dump({ 'job1' => job_config_base.merge({ 'rules' => rules_with_new_condition }) })
-        end
-
-        let(:expected_message) do
-          warning_details = described_class::VALIDATED_JOB_NAMES.map do |job_name|
-            <<~MARKDOWN
-              - :warning: Unable to find job `#{job_name}` in branch `feature_branch`.
-                If this job has been removed, please delete it from `Tooling::Danger::CiJobsDependencyValidation::VALIDATED_JOB_NAMES`.
-                Validation skipped.
-            MARKDOWN
-          end.join("\n")
-
-          <<~MARKDOWN.chomp
-           ### CI Jobs Dependency Validation
-
-           | name | validation status |
-           | ------ | --------------- |
-           | `setup-test-env` | :warning: Skipped |
-           | `compile-test-assets` | :warning: Skipped |
-           | `retrieve-tests-metadata` | :warning: Skipped |
-           | `build-gdk-image` | :warning: Skipped |
-           | `build-assets-image` | :warning: Skipped |
-           | `build-qa-image` | :warning: Skipped |
-           | `e2e-test-pipeline-generate` | :warning: Skipped |
-
-           #{warning_details}
-          MARKDOWN
-        end
-
-        it_behaves_like 'output message', true
-      end
-
-      context 'when job1 in branch needs one other job to run' do
-        let(:job_name)          { 'job1' }
-        let(:needed_job_name)   { 'setup-test-env' }
-
-        let(:source_branch_merged_yaml) do
-          YAML.dump(validated_jobs_base.merge(
-            {
-              job_name => {
-                'rules' => rules_with_new_condition,
-                'needs' => [needed_job_name]
-              }
-            }
-          ))
-        end
-
-        context 'with a hidden job' do
-          let(:job_name) { '.job1' }
-          let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-          it_behaves_like 'output message'
-        end
-
-        context 'with a global keyword' do
-          let(:job_name) { 'default' }
-          let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-          it_behaves_like 'output message'
-        end
-
-        context 'when the dependent job config has not changed (identical in master and in branch)' do
-          let(:master_merged_yaml) { source_branch_merged_yaml }
-          let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-          it_behaves_like 'output message'
-        end
-
-        context 'when VALIDATED_JOB_NAMES does not contain the needed job' do
-          let(:needed_job_name) { 'not-recognized' }
-          let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-          it_behaves_like 'output message'
-        end
-
-        context 'when VALIDATED_JOB_NAMES contains the needed job and dependent job config changed' do
-          context 'when the added rule is also present in its needed job' do
-            let(:source_branch_merged_yaml) do
-              YAML.dump(validated_jobs_base.merge({
-                job_name => job_config_base.merge({
-                  'rules' => rules_with_new_condition,
-                  'needs' => [needed_job_name]
-                }),
-                needed_job_name => { 'rules' => rules_with_new_condition, 'needs' => [] }
-              }))
-            end
-
-            let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-            it_behaves_like 'output message'
-          end
-
-          context 'when an added rule is missing in its needed job' do
-            let(:expected_message) do
-              <<~MARKDOWN
-              ### CI Jobs Dependency Validation
-
-              | name | validation status |
-              | ------ | --------------- |
-              | `setup-test-env` | 🚨 Failed (1) |
-              | `compile-test-assets` | :white_check_mark: Passed |
-              | `retrieve-tests-metadata` | :white_check_mark: Passed |
-              | `build-gdk-image` | :white_check_mark: Passed |
-              | `build-assets-image` | :white_check_mark: Passed |
-              | `build-qa-image` | :white_check_mark: Passed |
-              | `e2e-test-pipeline-generate` | :white_check_mark: Passed |
-
-              - 🚨 **These rule changes do not match with rules for `setup-test-env`:**
-
-              <details><summary>Click to expand details</summary>
-
-              `job1`:
-
-              - Added rules:
-
-              ```yaml
-              - if: $NEW_VAR == "true"
-              ```
-
-              - Removed rules:
-
-              `N/A`
-
-              Here are the rules for `setup-test-env`:
-
-              ```yaml
-              - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE
-                  == "detached"
-                changes:
-                - doc/index.md
-              ```
-
-              </details>
-
-              To avoid CI config errors, please verify if the same rule addition/removal should be applied to `setup-test-env`.
-              If not, please add a comment to explain why.
-              MARKDOWN
-            end
-
-            it_behaves_like 'output message', true
-          end
-        end
-      end
-
-      context 'when job configs are malformatted' do
-        let(:source_branch_merged_yaml) do
-          YAML.dump(validated_jobs_base.merge(
-            {
-              'job1' => 'not a hash',
-              'job2' => ['array'],
-              'job3' => { 'key' => 'missing needs and rules' }
-            }
-          ))
-        end
-
-        let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-        it_behaves_like 'output message'
-      end
-
-      context 'when dependent job has a rule that is not a hash' do
-        let(:source_branch_merged_yaml) do
-          YAML.dump(
-            validated_jobs_base.merge({
-              'job1' => {
-                'rules' => ['this is a malformatted rule'],
-                'needs' => 'this is a malformatted needs'
-              }
-            })
-          )
-        end
-
-        let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-        it_behaves_like 'output message'
-      end
-
-      context 'when dependent job have an added rule but condition reads "when: never"' do
-        let(:new_condition) { { 'if' => "$NEW_VAR == true", 'when' => 'never' } }
-        let(:source_branch_merged_yaml) do
-          YAML.dump(
-            validated_jobs_base.merge({
-              'job1' => {
-                'rules' => [new_condition],
-                'needs' => ['setup-test-env']
-              }
-            })
-          )
-        end
-
-        let(:expected_message) { described_class::VALIDATION_PASSED_OUTPUT }
-
-        it_behaves_like 'output message'
-      end
-
-      context 'when dependent job has removed a rule with "when: never"' do
-        let(:needed_job_rules) { rules_base }
-        let(:master_merged_yaml) do
-          YAML.dump(
-            validated_jobs_base.merge({
-              'job1' => {
-                'rules' => [*rules_base, { 'if' => 'true', 'when' => 'never' }],
-                'needs' => ['setup-test-env']
-              }
-            })
-          )
-        end
-
-        let(:source_branch_merged_yaml) do
-          YAML.dump(
-            validated_jobs_base.merge({
-              'job1' => {
-                'rules' => rules_base,
-                'needs' => ['setup-test-env']
-              },
-              'setup-test-env' => {
-                'rules' => needed_job_rules
-              }
-            })
-          )
-        end
-
-        context 'when needed_job does not have this negative rule' do
-          let(:expected_message) { ':white_check_mark: No warnings found in ci job dependencies.' }
-
-          it_behaves_like 'output message'
-        end
-
-        context 'when needed_job still has this negative rule' do
-          let(:needed_job_rules) { [*rules_base, { 'if' => 'true', 'when' => 'never' }] }
-          let(:expected_message) do
-            <<~MARKDOWN
-              ### CI Jobs Dependency Validation
-
-              | name | validation status |
-              | ------ | --------------- |
-              | `setup-test-env` | 🚨 Failed (1) |
-              | `compile-test-assets` | :white_check_mark: Passed |
-              | `retrieve-tests-metadata` | :white_check_mark: Passed |
-              | `build-gdk-image` | :white_check_mark: Passed |
-              | `build-assets-image` | :white_check_mark: Passed |
-              | `build-qa-image` | :white_check_mark: Passed |
-              | `e2e-test-pipeline-generate` | :white_check_mark: Passed |
-
-              - 🚨 **These rule changes do not match with rules for `setup-test-env`:**
-
-              <details><summary>Click to expand details</summary>
-
-              `job1`:
-
-              - Added rules:
-
-              `N/A`
-
-              - Removed rules:
-
-              ```yaml
-              - if: 'true'
-                when: never
-              ```
-
-              Here are the rules for `setup-test-env`:
-
-              ```yaml
-              - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE
-                  == "detached"
-                changes:
-                - doc/index.md
-              - if: 'true'
-                when: never
-              ```
-
-              </details>
-
-              To avoid CI config errors, please verify if the same rule addition/removal should be applied to `setup-test-env`.
-              If not, please add a comment to explain why.
-            MARKDOWN
-          end
-
-          it_behaves_like 'output message', true
-        end
-      end
-
-      context 'when dependent job have modified rules, but its attributes have nested arrays' do
-        let(:source_branch_merged_yaml) do
-          YAML.dump(
-            validated_jobs_base.merge({
-              'job1' => {
-                'rules' => [{ 'if' => 'true', 'when' => 'always' }, [new_condition]],
-                'needs' => ['setup-test-env', %w[compile-test-assets retrieve-tests-metadata]]
-              }
-            })
-          )
-        end
-
-        let(:message_preview) do
-          <<~MARKDOWN
-            ### CI Jobs Dependency Validation
-
-            | name | validation status |
-            | ------ | --------------- |
-            | `setup-test-env` | 🚨 Failed (1) |
-            | `compile-test-assets` | 🚨 Failed (1) |
-            | `retrieve-tests-metadata` | 🚨 Failed (1) |
-            | `build-gdk-image` | :white_check_mark: Passed |
-            | `build-assets-image` | :white_check_mark: Passed |
-            | `build-qa-image` | :white_check_mark: Passed |
-            | `e2e-test-pipeline-generate` | :white_check_mark: Passed |
-
-          MARKDOWN
-        end
-
-        let(:expected_message) do
-          %w[setup-test-env compile-test-assets retrieve-tests-metadata].map do |job_name|
-            <<~MARKDOWN
-              - 🚨 **These rule changes do not match with rules for `#{job_name}`:**
-
-              <details><summary>Click to expand details</summary>
-
-              `job1`:
-
-              - Added rules:
-
-              ```yaml
-              - if: 'true'
-                when: always
-              - if: $NEW_VAR == "true"
-              ```
-
-              - Removed rules:
-
-              `N/A`
-
-              Here are the rules for `#{job_name}`:
-
-              ```yaml
-              - if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE
-                  == "detached"
-                changes:
-                - doc/index.md
-              ```
-
-              </details>
-
-              To avoid CI config errors, please verify if the same rule addition/removal should be applied to `#{job_name}`.
-              If not, please add a comment to explain why.
-
-            MARKDOWN
-          end.join('').prepend(message_preview).chomp
-        end
-
-        it_behaves_like 'output message', true
-      end
-    end
-  end
-
-  describe '#fetch_jobs_yaml' do
-    context 'with api returns error' do
-      before do
-        allow(
-          ci_jobs_dependency_validation
-        ).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: {}).and_raise('error')
-      end
-
-      it 'returns empty object' do
-        expect(ci_jobs_dependency_validation).to receive(:warn).with(
-          "#{described_class::SKIPPED_VALIDATION_WARNING}: error"
-        )
-        expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq({})
-      end
-    end
-
-    context 'with returned payload missing merged_yaml' do
-      before do
-        allow(
-          ci_jobs_dependency_validation
-        ).to receive_message_chain(:gitlab, :api, :get).with(
-          "/projects/1/ci/lint", query: {}
-        ).and_return({ 'errors' => ['error'] })
-      end
-
-      it 'returns empty object' do
-        expect(ci_jobs_dependency_validation).to receive(:warn).with(
-          "#{described_class::SKIPPED_VALIDATION_WARNING}: error"
-        )
-        expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq({})
-      end
-    end
-
-    context 'with returned payload has merged_yaml and also has errors' do
-      before do
-        allow(
-          ci_jobs_dependency_validation
-        ).to receive_message_chain(:gitlab, :api, :get).with(
-          "/projects/1/ci/lint", query: {}
-        ).and_return({ 'errors' => ['error'], 'merged_yaml' => master_merged_yaml })
-      end
-
-      it 'returns the yaml and disregard the errors' do
-        expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq(
-          YAML.load(master_merged_yaml)
-        )
-      end
-    end
-
-    context 'with returned merged_yaml cannot be parsed' do
-      before do
-        allow(
-          ci_jobs_dependency_validation
-        ).to receive_message_chain(:gitlab, :api, :get).with("/projects/1/ci/lint", query: {}).and_return(
-          { 'merged_yaml' => 'date: 2024-04-04' }
-        )
-      end
-
-      it 'returns empty object' do
-        expect(ci_jobs_dependency_validation).to receive(:warn).with(
-          "#{described_class::SKIPPED_VALIDATION_WARNING}: Tried to load unspecified class: Date"
-        )
-        expect(ci_jobs_dependency_validation.send(:fetch_jobs_yaml, '1', 'master')).to eq({})
-      end
-    end
-  end
-end
diff --git a/tooling/danger/ci_jobs_dependency_validation.rb b/tooling/danger/ci_jobs_dependency_validation.rb
deleted file mode 100644
index db5f2b04ba557..0000000000000
--- a/tooling/danger/ci_jobs_dependency_validation.rb
+++ /dev/null
@@ -1,297 +0,0 @@
-# frozen_string_literal: true
-
-module Tooling
-  module Danger
-    module CiJobsDependencyValidation
-      VALIDATED_JOB_NAMES = %w[
-        setup-test-env
-        compile-test-assets
-        retrieve-tests-metadata
-        build-gdk-image
-        build-assets-image
-        build-qa-image
-        e2e-test-pipeline-generate
-      ].freeze
-      GLOBAL_KEYWORDS = %w[workflow variables stages default].freeze
-      DEFAULT_BRANCH_NAME = 'master'
-      FAILED_VALIDATION_WARNING = 'Please review warnings in the *CI Jobs Dependency Validation* section below.'
-      SKIPPED_VALIDATION_WARNING = 'Job dependency validation is skipped due to error fetching merged CI yaml'
-      VALIDATION_PASSED_OUTPUT = ':white_check_mark: No warnings found in ci job dependencies.'
-
-      Job = Struct.new(:name, :rules, :needs, keyword_init: true) do
-        def self.parse_rules_from_yaml(name, jobs_yaml)
-          attribute_values(jobs_yaml, name, 'rules').filter_map do |rule|
-            rule.is_a?(Hash) ? rule.slice('if', 'changes', 'when') : rule
-          end
-        end
-
-        def self.parse_needs_from_yaml(name, jobs_yaml)
-          attribute_values(jobs_yaml, name, 'needs').map { |need| need.is_a?(Hash) ? need['job'] : need }
-        end
-
-        def self.attribute_values(jobs_yaml, name, attribute)
-          return [] if jobs_yaml.nil? || jobs_yaml.empty? || !jobs_yaml[name].is_a?(Hash)
-
-          values = jobs_yaml.dig(name, attribute)
-          values.nil? ? [] : Array(values).flatten
-        end
-
-        def self.ignore?(job_name)
-          GLOBAL_KEYWORDS.include?(job_name) || job_name.start_with?('.')
-        end
-
-        def dependent_jobs(jobs)
-          jobs.select do |job|
-            !Job.ignore?(job.name) && job.needs.include?(name)
-          end
-        end
-      end
-
-      def output_message
-        return '' if !helper.ci? || !helper.has_ci_changes? || target_branch_jobs.empty? || source_branch_jobs.empty?
-
-        validation_statuses = VALIDATED_JOB_NAMES.to_h do |job_name|
-          [job_name, { skipped: false, failed: 0 }]
-        end
-
-        output = VALIDATED_JOB_NAMES.filter_map do |needed_job_name|
-          validate(needed_job_name, validation_statuses)
-        end.join("\n").chomp
-
-        return VALIDATION_PASSED_OUTPUT if output == ''
-
-        warn FAILED_VALIDATION_WARNING
-
-        <<~MARKDOWN
-        ### CI Jobs Dependency Validation
-
-        | name | validation status |
-        | ------ | --------------- |
-        #{construct_summary_table(validation_statuses)}
-
-        #{output}
-        MARKDOWN
-      end
-
-      private
-
-      def target_branch_jobs
-        @target_branch_jobs ||= build_jobs_from_yaml(target_branch_jobs_yaml, target_branch)
-      end
-
-      def source_branch_jobs
-        @source_branch_jobs ||= build_jobs_from_yaml(source_branch_jobs_yaml, source_branch)
-      end
-
-      def target_branch_jobs_yaml
-        @target_branch_jobs_yaml ||= fetch_jobs_yaml(target_project_id, target_branch)
-      end
-
-      def source_branch_jobs_yaml
-        @source_branch_jobs_yaml ||= fetch_jobs_yaml(source_project_id, source_branch)
-      end
-
-      def fetch_jobs_yaml(project_id, ref)
-        api_response = gitlab.api.get(lint_path(project_id), query: query_params(ref))
-
-        raise api_response['errors'].first if api_response['merged_yaml'].nil? && api_response['errors']&.any?
-
-        YAML.load(api_response['merged_yaml'], aliases: true)
-      rescue StandardError => e
-        warn "#{SKIPPED_VALIDATION_WARNING}: #{e.message}"
-        {}
-      end
-
-      def build_jobs_from_yaml(jobs_yaml, ref)
-        puts "Initializing #{jobs_yaml.keys.count} jobs from #{ref} ci config..."
-
-        jobs_yaml.filter_map do |job_name, _job_data|
-          next if Job.ignore?(job_name)
-
-          Job.new(
-            name: job_name,
-            rules: Job.parse_rules_from_yaml(job_name, jobs_yaml),
-            needs: Job.parse_needs_from_yaml(job_name, jobs_yaml)
-          )
-        end
-      end
-
-      def query_params(ref)
-        ref_query_params = {
-          content_ref: merged_result_commit_sha,
-          dry_run_ref: ref,
-          include_jobs: true,
-          dry_run: true
-        }
-
-        ref == DEFAULT_BRANCH_NAME ? {} : ref_query_params
-      end
-
-      def validate(needed_job_name, validation_statuses)
-        needed_job_in_source_branch = source_branch_jobs.find { |job| job.name == needed_job_name }
-        needed_job_in_target_branch = target_branch_jobs.find { |job| job.name == needed_job_name }
-
-        if needed_job_in_source_branch.nil?
-          validation_statuses[needed_job_name][:skipped] = true
-
-          return <<~MARKDOWN
-          - :warning: Unable to find job `#{needed_job_name}` in branch `#{source_branch}`.
-            If this job has been removed, please delete it from `Tooling::Danger::CiJobsDependencyValidation::VALIDATED_JOB_NAMES`.
-            Validation skipped.
-          MARKDOWN
-        end
-
-        failures = validation_failures(
-          needed_job_in_source_branch: needed_job_in_source_branch,
-          needed_job_in_target_branch: needed_job_in_target_branch
-        )
-
-        failed_count = failures.count
-
-        return if failed_count == 0
-
-        validation_statuses[needed_job_name][:failed] = failed_count
-
-        <<~MSG
-          - 🚨 **These rule changes do not match with rules for `#{needed_job_name}`:**
-
-          <details><summary>Click to expand details</summary>
-
-          #{failures.join("\n")}
-          Here are the rules for `#{needed_job_name}`:
-
-          ```yaml
-          #{dump_yaml(needed_job_in_source_branch.rules)}
-          ```
-
-          </details>
-
-          To avoid CI config errors, please verify if the same rule addition/removal should be applied to `#{needed_job_name}`.
-          If not, please add a comment to explain why.
-        MSG
-      end
-
-      def construct_summary_table(validation_statuses)
-        validation_statuses.map do |job_name, statuses|
-          skipped, failed_count = statuses.values_at(:skipped, :failed)
-
-          summary = if skipped
-                      ":warning: Skipped"
-                    elsif failed_count == 0
-                      ":white_check_mark: Passed"
-                    else
-                      "🚨 Failed (#{failed_count})"
-                    end
-
-          <<~MARKDOWN.chomp
-          | `#{job_name}` | #{summary} |
-          MARKDOWN
-        end.join("\n")
-      end
-
-      def validation_failures(needed_job_in_source_branch:, needed_job_in_target_branch:)
-        dependent_jobs_new = needed_job_in_source_branch&.dependent_jobs(source_branch_jobs) || []
-        dependent_jobs_old = needed_job_in_target_branch&.dependent_jobs(target_branch_jobs) || []
-
-        (dependent_jobs_new - dependent_jobs_old).filter_map do |dependent_job_with_rule_change|
-          dependent_job_old = dependent_jobs_old.find do |target_branch_job|
-            target_branch_job.name == dependent_job_with_rule_change.name
-          end
-
-          new_rules = dependent_job_with_rule_change.rules
-          old_rules = dependent_job_old&.rules
-
-          added_rules_to_report = rules_missing_in_needed_job(
-            needed_job: needed_job_in_source_branch,
-            rules: dependent_job_old.nil? ? new_rules : new_rules - old_rules # added rules
-          )
-
-          removed_rules_to_report = removed_negative_rules_present_in_needed_job(
-            needed_job: needed_job_in_source_branch,
-            rules: dependent_job_old.nil? ? [] : old_rules - new_rules # removed rules
-          )
-
-          next if added_rules_to_report.empty? && removed_rules_to_report.empty?
-
-          <<~MARKDOWN
-          `#{dependent_job_with_rule_change.name}`:
-
-          - Added rules:
-
-          #{report_yaml_markdown(added_rules_to_report)}
-
-          - Removed rules:
-
-          #{report_yaml_markdown(removed_rules_to_report)}
-          MARKDOWN
-        end
-      end
-
-      def report_yaml_markdown(rules_to_report)
-        return '`N/A`' unless rules_to_report.any?
-
-        <<~MARKDOWN.chomp
-        ```yaml
-        #{dump_yaml(rules_to_report)}
-        ```
-        MARKDOWN
-      end
-
-      def dump_yaml(yaml)
-        YAML.dump(yaml).delete_prefix("---\n").chomp
-      end
-
-      # Limitation: missing rules in needed jobs does not always mean the config is invalid.
-      # needed_jobs can have very generic rules, for example
-      #   - rule-for-job1:
-      #   - <<: *if-merge-request-targeting-stable-branch
-      # - rule-for-needed_job:
-      #   - <<: *if-merge-request-targeting-all-branches
-      # The above config is still valid, but danger will still print a warning because the exact rule is missing.
-      # We will have to manually identify that this config is fine and the warning should be ignored.
-      def rules_missing_in_needed_job(rules:, needed_job:)
-        return [] if rules.empty?
-
-        rules.select do |rule|
-          !needed_job.rules.include?(rule) && !negative_rule?(rule)
-        end
-      end
-
-      def removed_negative_rules_present_in_needed_job(rules:, needed_job:)
-        return [] if rules.empty?
-
-        rules.select do |rule|
-          needed_job.rules.include?(rule) && negative_rule?(rule)
-        end
-      end
-
-      def negative_rule?(rule)
-        rule.is_a?(Hash) && rule['when'] == 'never'
-      end
-
-      def lint_path(project_id)
-        "/projects/#{project_id}/ci/lint"
-      end
-
-      def source_project_id
-        helper.mr_source_project_id
-      end
-
-      def target_project_id
-        helper.mr_target_project_id
-      end
-
-      def source_branch
-        helper.mr_source_branch
-      end
-
-      def merged_result_commit_sha
-        ENV['CI_COMMIT_SHA'] # so we validate the merged results commit
-      end
-
-      def target_branch
-        helper.mr_target_branch
-      end
-    end
-  end
-end
-- 
GitLab