Class: Gitlab::Triage::Sources::WorkItemsRestSource
- Inherits:
-
Object
- Object
- Gitlab::Triage::Sources::WorkItemsRestSource
- Defined in:
- lib/gitlab/triage/sources/work_items_rest_source.rb
Overview
Fetches and updates work items through the GitLab Work Items REST API.
Unlike the legacy issues REST API, the Work Items REST list endpoint
applies no default base-type filter, so work items of any type -
including custom, namespace-defined types - are returned and can be
matched by name via the work_item_type_names filter.
The source is a self-contained peer to the engine's inline issues /
merge_requests / branches fetch: it owns list URL building, filter
push-down, normalization, and the PATCH update path. Comment creation
is not yet available on the Work Items REST API, so the comment
action is rejected at load time by Validators::PolicyValidator.
Constant Summary collapse
Class.new(StandardError)
- ScopeError =
Class.new(StandardError)
- LIST_FIELDS =
Base fields opted into on the list endpoint. The endpoint is sparse by default (id, iid, global_id, title only); every field a filter or template reads must be requested here or it comes back nil.
%w[ state confidential created_at updated_at web_url author namespace work_item_type ].freeze
- LIST_FEATURES =
Widget payloads opted into via
features. %w[ labels assignees milestone status start_and_due_date ].freeze
Instance Method Summary collapse
- #apply_date_filter(params, date_condition) ⇒ Object private
-
#apply_filters(params, conditions) ⇒ Object
private
Push-down: map work_items conditions to Work Items REST query params.
- #build_list_url(conditions) ⇒ Object private
- #date_boundary(date_condition) ⇒ Object private
-
#initialize(network:, options:, per_page: 100, normalizer: Normalizers::WorkItemNormalizer.new) ⇒ WorkItemsRestSource
constructor
A new instance of WorkItemsRestSource.
- #list(conditions) ⇒ Object
- #list_params(conditions) ⇒ Object private
- #project_id_by_full_path(full_path) ⇒ Object private
- #project_url(full_path) ⇒ Object private
-
#raise_if_unavailable!(raw) ⇒ Object
private
The Work Items REST API is behind the :work_item_rest_api feature flag.
-
#resolve_project_id!(resource) ⇒ Object
private
The normalizer surfaces
group_iddirectly for group-namespace items (namespace.id == legacy group id) but cannot supply aproject_idfor project-namespace items, because the list payload only carries the project-namespace id and full path, not the legacy project id that the gem's project-scoped resource helpers need. -
#scope_for(resource) ⇒ Object
Returns [source, source_id] for the namespace the resource lives in.
- #update(resource, features) ⇒ Object
- #work_item_url(resource) ⇒ Object private
Constructor Details
#initialize(network:, options:, per_page: 100, normalizer: Normalizers::WorkItemNormalizer.new) ⇒ WorkItemsRestSource
Returns a new instance of WorkItemsRestSource.
52 53 54 55 56 57 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 52 def initialize(network:, options:, per_page: 100, normalizer: Normalizers::WorkItemNormalizer.new) @network = network @options = @per_page = per_page @normalizer = normalizer end |
Instance Method Details
#apply_date_filter(params, date_condition) ⇒ Object (private)
199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 199 def apply_date_filter(params, date_condition) attribute = date_condition[:attribute] condition = date_condition[:condition] return unless %w[created_at updated_at].include?(attribute) boundary = date_boundary(date_condition) return unless boundary direction = condition == 'older_than' ? 'before' : 'after' params["#{attribute.delete_suffix('_at')}_#{direction}"] = boundary.iso8601 end |
#apply_filters(params, conditions) ⇒ Object (private)
Push-down: map work_items conditions to Work Items REST query params.
type uses work_item_type_names, which resolves names (including
custom types) case-insensitively server-side - no id resolution
needed.
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 187 def apply_filters(params, conditions) params[:iids] = Array(conditions[:iids]).join(',') if conditions[:iids].present? params[:work_item_type_names] = Array(conditions[:type]).join(',') if conditions[:type] params[:state] = conditions[:state] if conditions[:state] params[:label_name] = Array(conditions[:labels]).join(',') if conditions[:labels] params['not[label_name]'] = Array(conditions[:forbidden_labels]).join(',') if conditions[:forbidden_labels] params[:assignee_usernames] = Array(conditions[:assignees]).join(',') if conditions[:assignees] params['status[name]'] = conditions[:status] if conditions[:status] apply_date_filter(params, conditions[:date]) if conditions[:date] end |
#build_list_url(conditions) ⇒ Object (private)
149 150 151 152 153 154 155 156 157 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 149 def build_list_url(conditions) UrlBuilders::UrlBuilder.new( network_options: @options, source: @options.source, source_id: @options.source_id, resource_type: '-/work_items', params: list_params(conditions) ).build end |
#date_boundary(date_condition) ⇒ Object (private)
211 212 213 214 215 216 217 218 219 220 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 211 def date_boundary(date_condition) interval = date_condition[:interval] interval_type = date_condition[:interval_type] return unless interval && interval_type # A malformed interval_type raises here rather than silently dropping # the date bound, which would widen the matched set - dangerous in an # automation tool. interval_type is also validated by the date filter. interval.public_send(interval_type).ago # rubocop:disable GitlabSecurity/PublicSend -- interval_type constrained to a known set end |
#list(conditions) ⇒ Object
59 60 61 62 63 64 65 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 59 def list(conditions) raw = @network.query_api(build_list_url(conditions)) raise_if_unavailable!(raw) raw.map { |item| resolve_project_id!(@normalizer.call(item)) } end |
#list_params(conditions) ⇒ Object (private)
171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 171 def list_params(conditions) params = { per_page: @per_page, fields: LIST_FIELDS.join(','), features: LIST_FEATURES.join(',') } apply_filters(params, conditions) params end |
#project_id_by_full_path(full_path) ⇒ Object (private)
114 115 116 117 118 119 120 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 114 def project_id_by_full_path(full_path) @project_ids_by_full_path ||= {} return @project_ids_by_full_path[full_path] if @project_ids_by_full_path.key?(full_path) project = @network.query_api_cached(project_url(full_path)).first @project_ids_by_full_path[full_path] = project && project[:id] end |
#project_url(full_path) ⇒ Object (private)
122 123 124 125 126 127 128 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 122 def project_url(full_path) UrlBuilders::UrlBuilder.new( network_options: @options, source: 'projects', source_id: full_path ).build end |
#raise_if_unavailable!(raw) ⇒ Object (private)
The Work Items REST API is behind the :work_item_rest_api feature flag. When it is off, or the token lacks access, or the endpoint is absent, the response is an error hash (e.g. a 403 "feature flag is disabled" or a 404). The adapter does not raise on those, so without this guard the error hash would be normalized into a bogus resource and the run would silently match nothing - which in an automation tool looks like "all clear". Raise a clear, actionable error instead.
137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 137 def raise_if_unavailable!(raw) error = raw.find { |item| item.is_a?(Hash) && item[:iid].blank? && (item[:message] || item[:error]) } return unless error = error[:message] || error[:error] raise UnavailableError, "The Work Items REST API is not available (#{}). " \ 'Ensure the :work_item_rest_api feature flag is enabled and the ' \ 'token has sufficient access.' end |
#resolve_project_id!(resource) ⇒ Object (private)
The normalizer surfaces group_id directly for group-namespace items
(namespace.id == legacy group id) but cannot supply a project_id for
project-namespace items, because the list payload only carries the
project-namespace id and full path, not the legacy project id that the
gem's project-scoped resource helpers need. Resolve it here from the
full path, cached per run, so that a group-spanning run makes at most
one lookup per distinct project - and only for project items.
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 101 def resolve_project_id!(resource) namespace = resource[:namespace] return resource unless namespace && namespace[:kind] == 'project' full_path = namespace[:full_path] return resource if full_path.blank? project_id = project_id_by_full_path(full_path) resource[:project_id] = project_id if project_id resource end |
#scope_for(resource) ⇒ Object
Returns [source, source_id] for the namespace the resource lives in. A group run can return work items from descendant projects, and an iid is only unique within its own namespace, so writes and label lookups must target the resource's namespace, not the run-level source.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 76 def scope_for(resource) return ['projects', resource[:project_id]] if resource[:project_id] return ['groups', resource[:group_id]] if resource[:group_id] # A project item whose legacy project id could not be resolved must # not fall back to the run-level scope: on a group run the same iid # can name a different, visible work item there. if resource.dig(:namespace, :kind) == 'project' raise ScopeError, "Cannot resolve the project for work item #{resource[:web_url] || resource[:iid]}; " \ 'the token may lack access to its project.' end [@options.source, @options.source_id] end |
#update(resource, features) ⇒ Object
67 68 69 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 67 def update(resource, features) @network.patch_api(work_item_url(resource), features: features) end |
#work_item_url(resource) ⇒ Object (private)
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/gitlab/triage/sources/work_items_rest_source.rb', line 159 def work_item_url(resource) scope, scope_id = scope_for(resource) UrlBuilders::UrlBuilder.new( network_options: @options, source: scope, source_id: scope_id, resource_type: '-/work_items', resource_id: resource[:iid] ).build end |