summaryrefslogtreecommitdiff
path: root/app/graphql/resolvers/base_resolver.rb
blob: 5db618254cbbfd513ae498090920395e07d80fc7 (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
146
147
148
149
150
151
152
153
154
155
# frozen_string_literal: true

module Resolvers
  class BaseResolver < GraphQL::Schema::Resolver
    extend ::Gitlab::Utils::Override
    include ::Gitlab::Utils::StrongMemoize
    include ::Gitlab::Graphql::GlobalIDCompatibility

    argument_class ::Types::BaseArgument

    def self.requires_argument!
      @requires_argument = true
    end

    def self.field_options
      super.merge(requires_argument: @requires_argument)
    end

    def self.singular_type
      return unless type

      unwrapped = type.unwrap

      %i[node_type relay_node_type of_type itself].reduce(nil) do |t, m|
        t || unwrapped.try(m)
      end
    end

    def self.when_single(&block)
      as_single << block

      # Have we been called after defining the single version of this resolver?
      if @single.present?
        @single.instance_exec(&block)
      end
    end

    def self.as_single
      @as_single ||= []
    end

    def self.single_definition_blocks
      ancestors.flat_map { |klass| klass.try(:as_single) || [] }
    end

    def self.single
      @single ||= begin
        parent = self
        klass = Class.new(self) do
          type parent.singular_type, null: true

          def ready?(**args)
            ready, early_return = super
            [ready, select_result(early_return)]
          end

          def resolve(**args)
            select_result(super)
          end

          def single?
            true
          end

          def select_result(results)
            results&.first
          end

          define_singleton_method :to_s do
            "#{parent}.single"
          end
        end

        single_definition_blocks.each do |definition|
          klass.instance_exec(&definition)
        end

        klass
      end
    end

    def self.last
      parent = self
      @last ||= Class.new(self.single) do
        type parent.singular_type, null: true

        def select_result(results)
          results&.last
        end

        define_singleton_method :to_s do
          "#{parent}.last"
        end
      end
    end

    def self.complexity
      0
    end

    def self.resolver_complexity(args, child_complexity:)
      complexity = 1
      complexity += 1 if args[:sort]
      complexity += 5 if args[:search]

      complexity
    end

    def self.complexity_multiplier(args)
      # When fetching many items, additional complexity is added to the field
      # depending on how many items is fetched. For each item we add 1% of the
      # original complexity - this means that loading 100 items (our default
      # maxp_age_size limit) doubles the original complexity.
      #
      # Complexity is not increased when searching by specific ID(s), because
      # complexity difference is minimal in this case.
      [args[:iid], args[:iids]].any? ? 0 : 0.01
    end

    def offset_pagination(relation)
      ::Gitlab::Graphql::Pagination::OffsetPaginatedRelation.new(relation)
    end

    override :object
    def object
      super.tap do |obj|
        # If the field this resolver is used in is wrapped in a presenter, unwrap its subject
        break obj.subject if obj.is_a?(Gitlab::View::Presenter::Base)
      end
    end

    # TODO: remove! This should never be necessary
    # Remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/13984,
    # since once we use that authorization approach, the object is guaranteed to
    # be synchronized before any field.
    def synchronized_object
      strong_memoize(:synchronized_object) do
        ::Gitlab::Graphql::Lazy.force(object)
      end
    end

    def single?
      false
    end

    def current_user
      context[:current_user]
    end

    # Overridden in sub-classes (see .single, .last)
    def select_result(results)
      results
    end
  end
end