Class: Integrations::Jira
Constant Summary
collapse
- PROJECTS_PER_PAGE =
50
- JIRA_CLOUD_HOST =
'.atlassian.net'
- ATLASSIAN_REFERRER_GITLAB_COM =
{
atlOrigin: 'eyJpIjoiY2QyZTJiZDRkNGZhNGZlMWI3NzRkNTBmZmVlNzNiZTkiLCJwIjoianN3LWdpdGxhYi1pbnQifQ'
}.freeze
- ATLASSIAN_REFERRER_SELF_MANAGED =
{
atlOrigin: 'eyJpIjoiYjM0MTA4MzUyYTYxNDVkY2IwMzVjOGQ3ZWQ3NzMwM2QiLCJwIjoianN3LWdpdGxhYlNNLWludCJ9'
}.freeze
- API_ENDPOINTS =
{
find_issue: "/rest/api/2/issue/%s",
server_info: "/rest/api/2/serverInfo",
transition_issue: "/rest/api/2/issue/%s/transitions",
issue_comments: "/rest/api/2/issue/%s/comment",
link_remote_issue: "/rest/api/2/issue/%s/remotelink",
client_info: "/rest/api/2/myself"
}.freeze
- SECTION_TYPE_JIRA_TRIGGER =
'jira_trigger'
- SECTION_TYPE_JIRA_ISSUES =
'jira_issues'
- SECTION_TYPE_JIRA_ISSUE_CREATION =
'jira_issue_creation'
- AUTH_TYPE_BASIC =
0
- AUTH_TYPE_PAT =
1
- SNOWPLOW_EVENT_CATEGORY =
name
- RE2_SYNTAX_DOC_URL =
'https://github.com/google/re2/wiki/Syntax'
Base::IssueTracker::REFERENCE_PATTERN_LONG_REGEXP, Base::IssueTracker::REFERENCE_PATTERN_REGEXP
Base::Integration::BASE_ATTRIBUTES, Base::Integration::BASE_CLASSES, Base::Integration::DEV_INTEGRATION_NAMES, Base::Integration::ENCRYPTED_PROPERTIES_MAX_SIZE, Base::Integration::INSTANCE_LEVEL_ONLY_INTEGRATION_NAMES, Base::Integration::INTEGRATION_NAMES, Base::Integration::PROJECT_AND_GROUP_LEVEL_ONLY_INTEGRATION_NAMES, Base::Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES, Base::Integration::SECTION_TYPE_CONFIGURATION, Base::Integration::SECTION_TYPE_CONNECTION, Base::Integration::SECTION_TYPE_TRIGGER, Base::Integration::SNOWPLOW_EVENT_ACTION, Base::Integration::SNOWPLOW_EVENT_LABEL, Base::Integration::UnknownType
ApplicationRecord::MAX_PLUCK
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
ResetOnColumnErrors::MAX_RESET_PERIOD
Class Method Summary
collapse
Instance Method Summary
collapse
Methods included from HasAvatar
#avatar_url
#safe_format, #tag_pair
#active_when, #add_issuable_stylesheet, #add_page_specific_style, #add_page_startup_api_call, #add_work_items_stylesheet, #admin_section?, #ai_panel_expanded?, #asset_to_string, #autocomplete_data_sources, #bluesky_url, #body_data, #body_data_page, #client_class_list, #client_js_flags, #collapsed_super_sidebar?, community_forum, #community_forum, #conditional_link_to, #current_action?, #current_controller?, #discord_url, #dispensable_render, #dispensable_render_if_exists, #edited_time_ago_with_tooltip, #error_css, #external_storage_url_or_path, #extra_config, #github_url, #gitlab_config, #gitlab_ui_form_for, #gitlab_ui_form_with, #hexdigest, #hidden_resource_icon, #instance_review_permitted?, #last_commit, #linkedin_name, #linkedin_url, #locale_path, #mastodon_url, #orcid_url, #outdated_browser?, #page_class, #page_filter_path, #page_startup_api_calls, #partial_exists?, #path_to_key, #project_data, #project_studio_enabled?, #read_only_message, #registry_config, #render_if_exists, #show_callout?, #show_last_push_widget?, #sign_in_with_redirect?, #simple_sanitize, #static_objects_external_storage_enabled?, #support_url, #system_message_class, #template_exists?, #time_ago_with_tooltip, #twitter_url, #university_url
Methods included from ViteHelper
#universal_path_to_stylesheet, #universal_stylesheet_link_tag, #vite_enabled?, #vite_page_entrypoint_paths
includes_helpers, redirect_legacy_paths, url_helpers
#activate_disabled_reason, #default?, #handle_properties, #issue_path, #issue_tracker_path, #issue_url, #legacy_properties_data, #new_issue_path, #supports_data_fields?
#activate!, #activate_disabled_reason, #activated?, #after_build_from_integration, #api_field_names, #async_execute, #attributes, #attribution_notice, #category, #chat?, #ci?, #configurable_events, #deactivate!, #default_test_event, #description, #dup, #editable?, #event_channel_names, #event_names, #fields, #form_fields, #group_level?, #help, #inheritable?, #initialize_properties, #instance_level?, #json_fields, #manual_activation?, #operating?, #organization_id_from_parent, #parent, #project_level?, #reencrypt_properties, #reset_updated_properties, #secret_fields, #supported_events, #supports_data_fields?, #title, #to_database_hash, #to_param, #toggle!, #updated_properties, #validate_encrypted_properties_size_limit
#extended, extensions, #included, #method_added, #override, #prepended, #queue_verification, verify!
===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order
#sharding_organization
#reset_on_union_error, #reset_on_unknown_attribute_error
#serializable_hash
Class Method Details
.description ⇒ Object
254
255
256
|
# File 'app/models/integrations/jira.rb', line 254
def self.description
s_("JiraService|Use Jira as this project's issue tracker.")
end
|
.help ⇒ Object
258
259
260
261
262
263
264
265
266
|
# File 'app/models/integrations/jira.rb', line 258
def self.help
jira_doc_link_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe,
url: Gitlab::Routing.url_helpers.help_page_path('integration/jira/_index.md'))
format(
s_("JiraService|You must configure Jira before enabling this integration. " \
"%{jira_doc_link_start}Learn more.%{link_end}"),
jira_doc_link_start: jira_doc_link_start,
link_end: '</a>'.html_safe)
end
|
.supported_events ⇒ Object
When these are false GitLab does not create cross reference comments on Jira except when an issue gets transitioned.
190
191
192
|
# File 'app/models/integrations/jira.rb', line 190
def self.supported_events
%w[commit merge_request]
end
|
.title ⇒ Object
250
251
252
|
# File 'app/models/integrations/jira.rb', line 250
def self.title
'Jira issues'
end
|
.to_param ⇒ Object
268
269
270
|
# File 'app/models/integrations/jira.rb', line 268
def self.to_param
'jira'
end
|
.valid_jira_cloud_url?(url) ⇒ Boolean
199
200
201
202
203
204
205
206
|
# File 'app/models/integrations/jira.rb', line 199
def self.valid_jira_cloud_url?(url)
return false unless url.present?
uri = URI.parse(url)
uri.is_a?(URI::HTTPS) && !!uri.hostname&.end_with?(JIRA_CLOUD_HOST)
rescue URI::InvalidURIError
false
end
|
Instance Method Details
#api_url ⇒ Object
347
348
349
|
# File 'app/models/integrations/jira.rb', line 347
def api_url
original_api_url&.delete_suffix('/')
end
|
#client(additional_options = {}) ⇒ Object
243
244
245
246
247
248
|
# File 'app/models/integrations/jira.rb', line 243
def client(additional_options = {})
JIRA::Client.new(options.merge(additional_options)).tap do |client|
client.request_client = Gitlab::Jira::HttpClient.new(client.options)
end
end
|
#close_issue(entity, external_issue, current_user) ⇒ Object
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
|
# File 'app/models/integrations/jira.rb', line 369
def close_issue(entity, external_issue, current_user)
issue = find_issue(external_issue.iid, transitions: jira_issue_transition_automatic)
return if issue.nil? || has_resolution?(issue) || !issue_transition_enabled?
commit_id = case entity
when Commit then entity.id
when MergeRequest then entity.diff_head_sha
end
commit_url = build_entity_url(:commit, commit_id)
issue = find_issue(issue.key) if transition_issue(issue)
(issue, commit_id, commit_url) if has_resolution?(issue)
log_usage(:close_issue, current_user)
end
|
431
432
433
|
# File 'app/models/integrations/jira.rb', line 431
def configured?
active? && valid_connection?
end
|
#create_cross_reference_note(external_issue, mentioned_in, author) ⇒ Object
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
|
# File 'app/models/integrations/jira.rb', line 390
def create_cross_reference_note(external_issue, mentioned_in, author)
unless can_cross_reference?(mentioned_in)
return format(s_("JiraService|Events for %{noteable_model_name} are disabled."),
noteable_model_name: mentioned_in.model_name.plural.humanize(capitalize: false))
end
jira_issue = find_issue(external_issue.id)
return unless jira_issue.present?
mentioned_in_id = mentioned_in.respond_to?(:iid) ? mentioned_in.iid : mentioned_in.id
mentioned_in_type = mentionable_name(mentioned_in)
entity_url = build_entity_url(mentioned_in_type, mentioned_in_id)
entity_meta = build_entity_meta(mentioned_in)
data = {
user: {
name: author.name,
url: resource_url(user_path(author))
},
project: {
name: project.full_path,
url: resource_url(project_path(project))
},
entity: {
id: entity_meta[:id],
name: mentioned_in_type.humanize.downcase,
url: entity_url,
title: mentioned_in.title,
description: entity_meta[:description],
branch: entity_meta[:branch]
}
}
(data, jira_issue).tap { log_usage(:cross_reference, author) }
end
|
#data_fields ⇒ Object
208
209
210
|
# File 'app/models/integrations/jira.rb', line 208
def data_fields
jira_tracker_data || build_jira_tracker_data
end
|
#execute(push) ⇒ Object
351
352
353
354
|
# File 'app/models/integrations/jira.rb', line 351
def execute(push)
end
|
#find_issue(issue_key, rendered_fields: false, transitions: false, restrict_project_key: false) ⇒ Object
356
357
358
359
360
361
362
363
364
365
366
367
|
# File 'app/models/integrations/jira.rb', line 356
def find_issue(issue_key, rendered_fields: false, transitions: false, restrict_project_key: false)
return if restrict_project_key && !issue_key_allowed?(issue_key)
expands = []
expands << 'renderedFields' if rendered_fields
expands << 'transitions' if transitions
options = { expand: expands.join(',') } if expands.any?
path = API_ENDPOINTS[:find_issue] % issue_key
jira_request(path) { client.Issue.find(issue_key, options || {}) }
end
|
#issue_transition_enabled? ⇒ Boolean
453
454
455
|
# File 'app/models/integrations/jira.rb', line 453
def issue_transition_enabled?
jira_issue_transition_automatic || jira_issue_transition_id.present?
end
|
#issues_url ⇒ Object
333
334
335
|
# File 'app/models/integrations/jira.rb', line 333
def issues_url
web_url('browse/:id')
end
|
#new_issue_url ⇒ Object
337
338
339
|
# File 'app/models/integrations/jira.rb', line 337
def new_issue_url
web_url('secure/CreateIssue!default.jspa')
end
|
#options ⇒ Object
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
# File 'app/models/integrations/jira.rb', line 221
def options
url = URI.parse(client_url)
options = {
site: URI.join(url, '/').to_s.chomp('/'), context_path: (url.path.presence || '/').delete_suffix('/'),
auth_type: :basic,
use_ssl: url.scheme == 'https'
}
if personal_access_token_authorization?
options[:default_headers] = { 'Authorization' => "Bearer #{password}" }
else
options[:username] = username&.strip
options[:password] = password
options[:use_cookies] = true
options[:additional_cookies] = ['OBBasicAuth=fromDialog']
end
options
end
|
#original_api_url ⇒ Object
346
|
# File 'app/models/integrations/jira.rb', line 346
alias_method :original_api_url, :api_url
|
#original_url ⇒ Object
341
|
# File 'app/models/integrations/jira.rb', line 341
alias_method :original_url, :url
|
#personal_access_token_authorization? ⇒ Boolean
457
458
459
|
# File 'app/models/integrations/jira.rb', line 457
def personal_access_token_authorization?
jira_auth_type == AUTH_TYPE_PAT
end
|
#project_keys_as_string ⇒ Object
465
466
467
|
# File 'app/models/integrations/jira.rb', line 465
def project_keys_as_string
project_keys.join(',')
end
|
#reference_pattern ⇒ Object
PROJECT-KEY-NUMBER Examples: JIRA-1, PROJECT-1
195
196
197
|
# File 'app/models/integrations/jira.rb', line 195
def reference_pattern(*)
@reference_pattern ||= jira_issue_match_regex
end
|
#sections ⇒ Object
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
|
# File 'app/models/integrations/jira.rb', line 272
def sections
sections = [
{
type: SECTION_TYPE_CONNECTION,
title: s_('Integrations|Connection details'),
description: help
},
{
type: SECTION_TYPE_JIRA_TRIGGER,
title: _('Trigger'),
description: s_('JiraService|When a Jira issue is mentioned in a commit or merge request, a remote link ' \
'and comment (if enabled) will be created.')
},
{
type: SECTION_TYPE_CONFIGURATION,
title: _('Jira issue matching'),
description: s_('Configure custom rules for Jira issue key matching')
}
]
unless instance_level?
sections.push({
type: SECTION_TYPE_JIRA_ISSUES,
title: s_('JiraService|Jira issues (optional)'),
description: jira_issues_section_description,
plan: 'premium'
})
sections.push({
type: SECTION_TYPE_JIRA_ISSUE_CREATION,
title: s_('JiraService|Jira issues for vulnerabilities (optional)'),
description: s_('JiraService|Create Jira issues from GitLab to track any action taken ' \
'to resolve or mitigate vulnerabilities.'),
plan: 'ultimate'
})
end
sections
end
|
#set_default_data ⇒ Object
212
213
214
215
216
217
218
219
|
# File 'app/models/integrations/jira.rb', line 212
def set_default_data
return unless issues_tracker.present?
return if url
data_fields.url ||= issues_tracker['url']
data_fields.api_url ||= issues_tracker['api_url']
end
|
#support_close_issue? ⇒ Boolean
444
445
446
|
# File 'app/models/integrations/jira.rb', line 444
def support_close_issue?
true
end
|
#support_cross_reference? ⇒ Boolean
449
450
451
|
# File 'app/models/integrations/jira.rb', line 449
def support_cross_reference?
true
end
|
#test(_) ⇒ Object
435
436
437
438
439
440
441
|
# File 'app/models/integrations/jira.rb', line 435
def test(_)
result = {}.merge!(server_info, client_info) if server_info && client_info
success = server_info.present? && client_info.present?
result = @error&.message unless success
{ success: success, result: result }
end
|
#testable? ⇒ Boolean
461
462
463
|
# File 'app/models/integrations/jira.rb', line 461
def testable?
group_level? || project_level?
end
|
#url ⇒ Object
342
343
344
|
# File 'app/models/integrations/jira.rb', line 342
def url
original_url&.delete_suffix('/')
end
|
#valid_connection? ⇒ Boolean
427
428
429
|
# File 'app/models/integrations/jira.rb', line 427
def valid_connection?
test(nil)[:success]
end
|
#web_url(path = nil, **params) ⇒ Object
Also known as:
project_url
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
|
# File 'app/models/integrations/jira.rb', line 313
def web_url(path = nil, **params)
return '' unless url.present?
if Gitlab.com?
params.merge!(ATLASSIAN_REFERRER_GITLAB_COM) unless Gitlab.staging?
else
params.merge!(ATLASSIAN_REFERRER_SELF_MANAGED) unless Gitlab.dev_or_test_env?
end
url = Addressable::URI.parse(self.url)
url.path = url.path.delete_suffix('/')
url.path << "/#{path.delete_prefix('/').delete_suffix('/')}" if path.present?
url.query_values = (url.query_values || {}).merge(params)
url.query_values = nil if url.query_values.empty?
url.to_s
end
|