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"
}.freeze
- SECTION_TYPE_JIRA_TRIGGER =
'jira_trigger'
- SECTION_TYPE_JIRA_ISSUES =
'jira_issues'
- AUTH_TYPE_BASIC =
0
- AUTH_TYPE_PAT =
1
- SNOWPLOW_EVENT_CATEGORY =
self.name
Constants inherited
from Integration
Integration::BASE_CLASSES, Integration::DEV_INTEGRATION_NAMES, Integration::INTEGRATION_NAMES, Integration::PROJECT_SPECIFIC_INTEGRATION_NAMES, Integration::SECTION_TYPE_CONFIGURATION, Integration::SECTION_TYPE_CONNECTION, Integration::SECTION_TYPE_TRIGGER, Integration::SNOWPLOW_EVENT_ACTION, Integration::SNOWPLOW_EVENT_LABEL, Integration::UnknownType
ApplicationRecord::MAX_PLUCK
ResetOnUnionError::MAX_RESET_PERIOD
Instance Attribute Summary
Attributes included from Importable
#imported, #importing
Class Method Summary
collapse
Instance Method Summary
collapse
#active_when, #add_page_specific_style, #add_page_startup_api_call, #admin_section?, #asset_to_string, #autocomplete_data_sources, #body_data, #body_data_page, #client_class_list, #client_js_flags, #collapsed_sidebar?, #collapsed_super_sidebar?, community_forum, #community_forum, #conditional_link_to, #controller_full_path, #current_action?, #current_controller?, #discord_url, #dispensable_render, #dispensable_render_if_exists, #edited_time_ago_with_tooltip, #external_storage_url_or_path, #extra_config, #gitlab_config, #gitlab_ui_form_for, #gitlab_ui_form_with, #hexdigest, #hidden_resource_icon, #instance_review_permitted?, #last_commit, #linkedin_url, #locale_path, #outdated_browser?, #page_class, #page_filter_path, #page_startup_api_calls, #partial_exists?, #path_to_key, #project_data, #promo_host, promo_host, #promo_url, #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?, #stylesheet_link_tag_defer, #support_url, #system_message_class, #template_exists?, #time_ago_with_tooltip, #truncate_first_line, #twitter_url
includes_helpers, redirect_legacy_paths, url_helpers
#activate_disabled_reason, base_reference_pattern, #default?, #handle_properties, #issue_path, #issue_tracker_path, #issue_url, #legacy_properties_data, #new_issue_path, #supports_data_fields?
Methods inherited from Integration
#activate_disabled_reason, #activated?, #api_field_names, #async_execute, #attributes, available_integration_names, available_integration_types, boolean_accessor, build_from_integration, #category, #chat?, #ci?, #configurable_events, create_from_active_default_integrations, default_integration, default_test_event, #default_test_event, dev_integration_names, #dup, #editable?, #event_channel_names, event_description, event_names, #event_names, field, #fields, fields, find_or_initialize_all_non_project_specific, find_or_initialize_non_project_specific_integration, #form_fields, #group_level?, #inheritable?, inherited_descendants_from_self_or_ancestors_from, #initialize_properties, instance_exists_for?, #instance_level?, integration_name_to_model, integration_name_to_type, integration_names, #json_fields, #operating?, #parent, #project_level?, project_specific_integration_names, prop_accessor, #properties=, #reencrypt_properties, #reset_updated_properties, #secret_fields, #show_active_box?, #supported_events, #supports_data_fields?, #testable?, #to_database_hash, #to_param, #updated_properties
#extended, extensions, #included, #method_added, #override, #prepended, #queue_verification, verify!
#exposing_secrets_fields
Methods included from Loggable
#build_message, #log_error, #log_exception, #log_info, #logger
cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, 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
#serializable_hash
Class Method Details
.supported_events ⇒ Object
When these are false GitLab does not create cross reference comments on Jira except when an issue gets transitioned.
128
129
130
|
# File 'app/models/integrations/jira.rb', line 128
def self.supported_events
%w[commit merge_request]
end
|
.to_param ⇒ Object
201
202
203
|
# File 'app/models/integrations/jira.rb', line 201
def self.to_param
'jira'
end
|
.valid_jira_cloud_url?(url) ⇒ Boolean
137
138
139
140
141
142
143
144
|
# File 'app/models/integrations/jira.rb', line 137
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
271
272
273
|
# File 'app/models/integrations/jira.rb', line 271
def api_url
original_api_url&.delete_suffix('/')
end
|
#client ⇒ Object
181
182
183
184
185
186
|
# File 'app/models/integrations/jira.rb', line 181
def client
@client ||= JIRA::Client.new(options).tap do |client|
client.request_client = Gitlab::Jira::HttpClient.new(client.options)
end
end
|
#close_issue(entity, external_issue, current_user) ⇒ Object
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 293
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
|
354
355
356
|
# File 'app/models/integrations/jira.rb', line 354
def configured?
active? && valid_connection?
end
|
#create_cross_reference_note(external_issue, mentioned_in, author) ⇒ Object
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
|
# File 'app/models/integrations/jira.rb', line 314
def create_cross_reference_note(external_issue, mentioned_in, author)
unless can_cross_reference?(mentioned_in)
return 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
146
147
148
|
# File 'app/models/integrations/jira.rb', line 146
def data_fields
jira_tracker_data || self.build_jira_tracker_data
end
|
#description ⇒ Object
197
198
199
|
# File 'app/models/integrations/jira.rb', line 197
def description
s_("JiraService|Use Jira as this project's issue tracker.")
end
|
#execute(push) ⇒ Object
275
276
277
278
|
# File 'app/models/integrations/jira.rb', line 275
def execute(push)
end
|
#find_issue(issue_key, rendered_fields: false, transitions: false, restrict_project_key: false) ⇒ Object
280
281
282
283
284
285
286
287
288
289
290
291
|
# File 'app/models/integrations/jira.rb', line 280
def find_issue(issue_key, rendered_fields: false, transitions: false, restrict_project_key: false)
return if restrict_project_key && parse_project_from_issue_key(issue_key) != project_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
|
#help ⇒ Object
188
189
190
191
|
# File 'app/models/integrations/jira.rb', line 188
def help
jira_doc_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('integration/jira/index') }
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
|
#issue_transition_enabled? ⇒ Boolean
376
377
378
|
# File 'app/models/integrations/jira.rb', line 376
def issue_transition_enabled?
jira_issue_transition_automatic || jira_issue_transition_id.present?
end
|
#issues_url ⇒ Object
257
258
259
|
# File 'app/models/integrations/jira.rb', line 257
def issues_url
web_url('browse/:id')
end
|
#new_issue_url ⇒ Object
261
262
263
|
# File 'app/models/integrations/jira.rb', line 261
def new_issue_url
web_url('secure/CreateIssue!default.jspa')
end
|
#options ⇒ Object
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
# File 'app/models/integrations/jira.rb', line 159
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
270
|
# File 'app/models/integrations/jira.rb', line 270
alias_method :original_api_url, :api_url
|
#original_url ⇒ Object
265
|
# File 'app/models/integrations/jira.rb', line 265
alias_method :original_url, :url
|
#personal_access_token_authorization? ⇒ Boolean
380
381
382
|
# File 'app/models/integrations/jira.rb', line 380
def personal_access_token_authorization?
jira_auth_type == AUTH_TYPE_PAT
end
|
#reference_pattern(only_long: true) ⇒ Object
PROJECT-KEY-NUMBER Examples: JIRA-1, PROJECT-1
133
134
135
|
# File 'app/models/integrations/jira.rb', line 133
def reference_pattern(only_long: true)
@reference_pattern ||= jira_issue_match_regex
end
|
#sections ⇒ Object
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
# File 'app/models/integrations/jira.rb', line 205
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')
}
]
if project_level?
sections.push({
type: SECTION_TYPE_JIRA_ISSUES,
title: _('Issues'),
description: jira_issues_section_description,
plan: 'premium'
})
end
sections
end
|
#set_default_data ⇒ Object
150
151
152
153
154
155
156
157
|
# File 'app/models/integrations/jira.rb', line 150
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
367
368
369
|
# File 'app/models/integrations/jira.rb', line 367
def support_close_issue?
true
end
|
#support_cross_reference? ⇒ Boolean
372
373
374
|
# File 'app/models/integrations/jira.rb', line 372
def support_cross_reference?
true
end
|
#test(_) ⇒ Object
358
359
360
361
362
363
364
|
# File 'app/models/integrations/jira.rb', line 358
def test(_)
result = server_info
success = result.present?
result = @error&.message unless success
{ success: success, result: result }
end
|
#title ⇒ Object
193
194
195
|
# File 'app/models/integrations/jira.rb', line 193
def title
'Jira'
end
|
#url ⇒ Object
266
267
268
|
# File 'app/models/integrations/jira.rb', line 266
def url
original_url&.delete_suffix('/')
end
|
#valid_connection? ⇒ Boolean
350
351
352
|
# File 'app/models/integrations/jira.rb', line 350
def valid_connection?
test(nil)[:success]
end
|
#web_url(path = nil, **params) ⇒ Object
Also known as:
project_url
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
|
# File 'app/models/integrations/jira.rb', line 237
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
|