summaryrefslogtreecommitdiff
path: root/app/services/ci/find_exposed_artifacts_service.rb
blob: abbeb101be2db37057156037bee3d352393bf56c (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
# frozen_string_literal: true

module Ci
  # This class loops through all builds with exposed artifacts and returns
  # basic information about exposed artifacts for given jobs for the frontend
  # to display them as custom links in the merge request.
  #
  # This service must be used with care.
  # Looking for exposed artifacts is very slow and should be done asynchronously.
  class FindExposedArtifactsService < ::BaseService
    include Gitlab::Routing

    MAX_EXPOSED_ARTIFACTS = 10

    def for_pipeline(pipeline, limit: MAX_EXPOSED_ARTIFACTS)
      results = []

      pipeline.builds.latest.with_exposed_artifacts.find_each do |job|
        if job_exposed_artifacts = for_job(job)
          results << job_exposed_artifacts
        end

        break if results.size >= limit
      end

      results
    end

    def for_job(job)
      return unless job.has_exposed_artifacts?

      metadata_entries = first_2_metadata_entries_for_artifacts_paths(job)
      return if metadata_entries.empty?

      {
        text: job.artifacts_expose_as,
        url: path_for_entries(metadata_entries, job),
        job_path: project_job_path(job.project, job),
        job_name: job.name
      }
    end

    private

    # we don't need to fetch all artifacts entries for a job because
    # it could contain many. We only need to know whether it has 1 or more
    # artifacts, so fetching the first 2 would be sufficient.
    def first_2_metadata_entries_for_artifacts_paths(job)
      return [] unless job.artifacts_metadata

      job.artifacts_paths
        .lazy
        .map { |path| job.artifacts_metadata_entry(path, recursive: true) }
        .select { |entry| entry.exists? }
        .first(2)
    end

    def path_for_entries(entries, job)
      return if entries.empty?

      if single_artifact?(entries)
        file_project_job_artifacts_path(job.project, job, entries.first.path)
      else
        browse_project_job_artifacts_path(job.project, job)
      end
    end

    def single_artifact?(entries)
      entries.size == 1 && entries.first.file?
    end
  end
end