Welcome to mirror list, hosted at ThFree Co, Russian Federation.

composite.rb « status « ci « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: fe4f6db95490834db57c050a203d2e695e69c4bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# frozen_string_literal: true

module Gitlab
  module Ci
    module Status
      class Composite
        include Gitlab::Utils::StrongMemoize

        # This class accepts an array of arrays/hashes/or objects
        # `with_allow_failure` will be removed when deleting ci_remove_ensure_stage_service
        def initialize(all_jobs, with_allow_failure: true, dag: false, project: nil)
          unless all_jobs.respond_to?(:pluck)
            raise ArgumentError, "all_jobs needs to respond to `.pluck`"
          end

          @status_set = Set.new
          @status_key = 0
          @allow_failure_key = 1 if with_allow_failure
          @dag = dag
          @project = project

          consume_all_jobs(all_jobs)
        end

        # The status calculation is order dependent,
        # 1. In some cases we assume that that status is exact
        #    if the we only have given statues,
        # 2. In other cases we assume that status is of that type
        #    based on what statuses are no longer valid based on the
        #    data set that we have
        #
        # This method is used for three cases:
        # 1. When it is called for a stage or a pipeline (with `all_jobs` from all jobs in a stage or a pipeline),
        #    then, the returned status is assigned to the stage or pipeline.
        # 2. When it is called for a job (with `all_jobs` from all previous jobs or all needed jobs),
        #    then, the returned status is used to determine if the job is processed or not.
        # 3. When it is called for a group (of jobs that are related),
        #    then, the returned status is used to show the overall status of the group.
        # rubocop: disable Metrics/CyclomaticComplexity
        # rubocop: disable Metrics/PerceivedComplexity
        def status
          return if none?

          strong_memoize(:status) do
            if @dag && any_skipped_or_ignored?
              # The DAG job is skipped if one of the needs does not run at all.
              'skipped'
            elsif only_of?(:skipped, :ignored)
              'skipped'
            elsif only_of?(:success, :skipped, :success_with_warnings, :ignored)
              'success'
            elsif only_of?(:created, :success_with_warnings, :ignored)
              'created'
            elsif only_of?(:preparing, :success_with_warnings, :ignored)
              'preparing'
            elsif only_of?(:canceled, :success, :skipped, :success_with_warnings, :ignored)
              'canceled'
            elsif only_of?(:pending, :created, :skipped, :success_with_warnings, :ignored)
              'pending'
            elsif any_of?(:running, :pending)
              'running'
            elsif any_of?(:waiting_for_resource)
              'waiting_for_resource'
            elsif any_of?(:waiting_for_callback)
              'waiting_for_callback'
            elsif any_of?(:manual)
              'manual'
            elsif any_of?(:scheduled)
              'scheduled'
            elsif any_of?(:preparing)
              'preparing'
            elsif any_of?(:created)
              'running'
            else
              'failed'
            end
          end
        end
        # rubocop: enable Metrics/CyclomaticComplexity
        # rubocop: enable Metrics/PerceivedComplexity

        def warnings?
          @status_set.include?(:success_with_warnings)
        end

        private

        def none?
          @status_set.empty?
        end

        def any_of?(*names)
          names.any? { |name| @status_set.include?(name) }
        end

        def only_of?(*names)
          matching = names.count { |name| @status_set.include?(name) }
          matching > 0 &&
            matching == @status_set.size
        end

        def any_skipped_or_ignored?
          any_of?(:skipped) || any_of?(:ignored)
        end

        def consume_all_jobs(all_jobs)
          columns = []
          columns[@status_key] = :status
          columns[@allow_failure_key] = :allow_failure if @allow_failure_key

          all_jobs
            .pluck(*columns) # rubocop: disable CodeReuse/ActiveRecord
            .each do |job_attrs|
              consume_job_status(Array.wrap(job_attrs))
            end
        end

        def consume_job_status(job_attrs)
          status_result =
            if success_with_warnings?(job_attrs)
              :success_with_warnings
            elsif ignored_status?(job_attrs)
              :ignored
            else
              job_attrs[@status_key].to_sym
            end

          @status_set.add(status_result)
        end

        def success_with_warnings?(job_attrs)
          @allow_failure_key &&
            job_attrs[@allow_failure_key] &&
            ::Ci::HasStatus::PASSED_WITH_WARNINGS_STATUSES.include?(job_attrs[@status_key])
        end

        def ignored_status?(job_attrs)
          @allow_failure_key &&
            job_attrs[@allow_failure_key] &&
            ::Ci::HasStatus::IGNORED_STATUSES.include?(job_attrs[@status_key])
        end
      end
    end
  end
end