Class: Issue

Direct Known Subclasses

WorkItem

Defined Under Namespace

Classes: Email, Metrics

Constant Summary collapse

DueDateStruct =
Struct.new(:title, :name).freeze
NoDueDate =
DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate =
DueDateStruct.new('Any Due Date', 'any').freeze
Overdue =
DueDateStruct.new('Overdue', 'overdue').freeze
DueToday =
DueDateStruct.new('Due Today', 'today').freeze
DueTomorrow =
DueDateStruct.new('Due Tomorrow', 'tomorrow').freeze
DueThisWeek =
DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth =
DueDateStruct.new('Due This Month', 'month').freeze
DueNextMonthAndPreviousTwoWeeks =
DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
SORTING_PREFERENCE_FIELD =
:issues_sort
TYPES_FOR_LIST =

Types of issues that should be displayed on lists across the app for example, project issues list, group issues list and issue boards. Some issue types, like test cases, should be hidden by default.

%w(issue incident).freeze

Constants included from PgFullTextSearchable

PgFullTextSearchable::LONG_WORDS_REGEX, PgFullTextSearchable::TEXT_SEARCH_DICTIONARY, PgFullTextSearchable::TSVECTOR_MAX_LENGTH

Constants included from ThrottledTouch

ThrottledTouch::TOUCH_INTERVAL

Constants included from Gitlab::RelativePositioning

Gitlab::RelativePositioning::IDEAL_DISTANCE, Gitlab::RelativePositioning::IllegalRange, Gitlab::RelativePositioning::InvalidPosition, Gitlab::RelativePositioning::IssuePositioningDisabled, Gitlab::RelativePositioning::MAX_GAP, Gitlab::RelativePositioning::MAX_POSITION, Gitlab::RelativePositioning::MIN_GAP, Gitlab::RelativePositioning::MIN_POSITION, Gitlab::RelativePositioning::NoSpaceLeft, Gitlab::RelativePositioning::START_POSITION, Gitlab::RelativePositioning::STEPS

Constants included from Noteable

Noteable::MAX_NOTES_LIMIT

Constants included from Issuable

Issuable::DESCRIPTION_HTML_LENGTH_MAX, Issuable::DESCRIPTION_LENGTH_MAX, Issuable::SEARCHABLE_FIELDS, Issuable::STATE_ID_MAP, Issuable::TITLE_HTML_LENGTH_MAX, Issuable::TITLE_LENGTH_MAX

Constants included from Taskable

Taskable::COMPLETED, Taskable::COMPLETE_PATTERN, Taskable::INCOMPLETE, Taskable::INCOMPLETE_PATTERN, Taskable::ITEM_PATTERN

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Constants included from Redactable

Redactable::UNSUBSCRIBE_PATTERN

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD

Constants included from AtomicInternalId

AtomicInternalId::MissingValueError

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Instance Attribute Summary

Attributes included from Noteable

#system_note_timestamp

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::Override

extended, extensions, included, method_added, override, prepended, queue_verification, verify!

Methods included from PgFullTextSearchable

#update_search_data!

Methods included from IssueAvailableFeatures

#issue_type_supports?

Methods included from Presentable

#present

Methods included from ThrottledTouch

#touch

Methods included from TimeTrackable

#human_time_change, #human_time_estimate, #human_total_time_spent, #spend_time, #time_change, #time_estimate=, #total_time_spent

Methods included from RelativePositioning

#exclude_self, #model_class, #move_after, #move_before, #move_between, #move_to_end, #move_to_start, mover, #relative_positioning_scoped_items, #reset_relative_position, #update_relative_siblings

Methods included from Gitlab::RelativePositioning

range

Methods included from FasterCacheKeys

#cache_key

Methods included from Spammable

#allow_possible_spam?, #clear_spam_flags!, #invalidate_if_spam, #needs_recaptcha!, #recaptcha_error!, #render_recaptcha?, #spam, #spam!, #spam_description, #spam_title, #spammable_entity_type, #spammable_text, #submittable_as_spam?, #submittable_as_spam_by?, #unrecoverable_spam_error!

Methods included from Referable

#referable_inspect, #reference_link_text, #to_reference_base

Methods included from Noteable

#after_note_created, #after_note_destroyed, #base_class_name, #capped_notes_count, #creatable_note_email_address, #discussion_ids_relation, #discussion_notes, #discussion_root_note_ids, #discussions, #discussions_can_be_resolved_by?, #discussions_resolvable?, #discussions_resolved?, #discussions_to_be_resolved, #expire_note_etag_cache, #grouped_diff_discussions, #has_any_diff_note_positions?, #human_class_name, #lockable?, #note_etag_key, #noteable_target_type_name, #preloads_discussion_diff_highlighting?, #resolvable_discussions, #supports_creating_notes_by_email?, #supports_discussions?, #supports_replying_to_individual_notes?, #supports_resolvable_notes?, #supports_suggestion?

Methods included from Issuable

#assignee_list, #assignee_or_author?, #assignee_username_list, #can_assign_epic?, #card_attributes, #created_hours_ago, #draftless_title_changed, #first_contribution?, #hook_association_changes, #label_names, #labels_array, #labels_hook_attrs, #new?, #notes_with_associations, #open?, #overdue?, #resource_parent, #state, #state=, #subscribed_without_subscriptions?, #to_ability_name, #to_hook_data, #today?, #updated_tasks, #user_notes_count

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from Editable

#edited?, #last_edited_by

Methods included from Taskable

get_tasks, get_updated_tasks, #task_completion_status, #task_list_items, #task_status, #task_status_short, #tasks, #tasks?

Methods included from Awardable

#awarded_emoji?, #downvotes, #emoji_awardable?, #grouped_awards, #upvotes, #user_authored?, #user_can_award?

Methods included from StripAttribute

#strip_attributes!

Methods included from Subscribable

#lazy_subscription, #set_subscription, #subscribe, #subscribed?, #subscribed_without_subscriptions?, #subscribers, #toggle_subscription, #unsubscribe

Methods included from Milestoneable

#milestone_available?, #supports_milestone?

Methods included from Mentionable

#all_references, #create_cross_references!, #create_new_cross_references!, #directly_addressed_users, #extractors, #gfm_reference, #local_reference, #matches_cross_reference_regex?, #mentioned_users, #referenced_group_users, #referenced_groups, #referenced_mentionables, #referenced_project_users, #referenced_projects, #referenced_users, #user_mention_class, #user_mention_identifier

Methods included from Participable

#participant?, #participants, #visible_participants

Methods included from CacheMarkdownField

#attribute_invalidated?, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #mentionable_attributes_changed?, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #store_mentions!, #updated_cached_html_for

Methods included from IidRoutes

#to_param

Methods included from AtomicInternalId

group_init, #internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage, project_init, scope_attrs, scope_usage

Methods inherited from ApplicationRecord

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, 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

Methods included from SensitiveSerializableHash

#serializable_hash

Class Method Details

.column_order_id_ascObject


353
354
355
356
357
358
# File 'app/models/issue.rb', line 353

def self.column_order_id_asc
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'id',
    order_expression: arel_table[:id].asc
  )
end

.column_order_relative_positionObject


341
342
343
344
345
346
347
348
349
350
351
# File 'app/models/issue.rb', line 341

def self.column_order_relative_position
  Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
    attribute_name: 'relative_position',
    column_expression: arel_table[:relative_position],
    order_expression: Issue.arel_table[:relative_position].asc.nulls_last,
    reversed_order_expression: Issue.arel_table[:relative_position].desc.nulls_last,
    order_direction: :asc,
    nullable: :nulls_last,
    distinct: false
  )
end

296
297
298
# File 'app/models/issue.rb', line 296

def self.link_reference_pattern
  @link_reference_pattern ||= super("issues", Gitlab::Regex.issue)
end

.order_by_relative_positionObject


337
338
339
# File 'app/models/issue.rb', line 337

def self.order_by_relative_position
  reorder(Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_id_asc]))
end

.order_upvotes_ascObject


237
238
239
# File 'app/models/issue.rb', line 237

def order_upvotes_asc
  reorder(upvotes_count: :asc)
end

.order_upvotes_descObject


232
233
234
# File 'app/models/issue.rb', line 232

def order_upvotes_desc
  reorder(upvotes_count: :desc)
end

.pg_full_text_search(search_term) ⇒ Object


242
243
244
# File 'app/models/issue.rb', line 242

def pg_full_text_search(search_term)
  super.where('issue_search_data.project_id = issues.project_id')
end

.project_foreign_keyObject


304
305
306
# File 'app/models/issue.rb', line 304

def self.project_foreign_key
  'project_id'
end

.reference_patternObject

Pattern used to extract `#123` issue references from text

This pattern supports cross-project references.


289
290
291
292
293
294
# File 'app/models/issue.rb', line 289

def self.reference_pattern
  @reference_pattern ||= %r{
    (#{Project.reference_pattern})?
    #{Regexp.escape(reference_prefix)}#{Gitlab::Regex.issue}
  }x
end

.reference_prefixObject


282
283
284
# File 'app/models/issue.rb', line 282

def self.reference_prefix
  '#'
end

.reference_valid?(reference) ⇒ Boolean

Returns:

  • (Boolean)

300
301
302
# File 'app/models/issue.rb', line 300

def self.reference_valid?(reference)
  reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
end

.relative_positioning_parent_columnObject


278
279
280
# File 'app/models/issue.rb', line 278

def self.relative_positioning_parent_column
  :project_id
end

.relative_positioning_query_base(issue) ⇒ Object


274
275
276
# File 'app/models/issue.rb', line 274

def self.relative_positioning_query_base(issue)
  in_projects(issue.relative_positioning_parent_projects)
end

.simple_sortsObject


308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'app/models/issue.rb', line 308

def self.simple_sorts
  super.merge(
    {
      'closest_future_date' => -> { order_closest_future_date },
      'closest_future_date_asc' => -> { order_closest_future_date },
      'due_date' => -> { order_due_date_asc.with_order_id_desc },
      'due_date_asc' => -> { order_due_date_asc.with_order_id_desc },
      'due_date_desc' => -> { order_due_date_desc.with_order_id_desc },
      'relative_position' => -> { order_by_relative_position },
      'relative_position_asc' => -> { order_by_relative_position }
    }
  )
end

.sort_by_attribute(method, excluded_labels: []) ⇒ Object


322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'app/models/issue.rb', line 322

def self.sort_by_attribute(method, excluded_labels: [])
  case method.to_s
  when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date
  when 'due_date', 'due_date_asc'                       then order_due_date_asc.with_order_id_desc
  when 'due_date_desc'                                  then order_due_date_desc.with_order_id_desc
  when 'relative_position', 'relative_position_asc'     then order_by_relative_position
  when 'severity_asc'                                   then order_severity_asc.with_order_id_desc
  when 'severity_desc'                                  then order_severity_desc.with_order_id_desc
  when 'escalation_status_asc'                          then order_escalation_status_asc.with_order_id_desc
  when 'escalation_status_desc'                         then order_escalation_status_desc.with_order_id_desc
  else
    super
  end
end

.to_branch_name(*args) ⇒ Object


360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'app/models/issue.rb', line 360

def self.to_branch_name(*args)
  branch_name = args.map(&:to_s).each_with_index.map do |arg, i|
    arg.parameterize(preserve_case: i == 0).presence
  end.compact.join('-')

  if branch_name.length > 100
    truncated_string = branch_name[0, 100]
    # Delete everything dangling after the last hyphen so as not to risk
    # existence of unintended words in the branch name due to mid-word split.
    branch_name = truncated_string.sub(/-[^-]*\Z/, '')
  end

  branch_name
end

Instance Method Details

#as_json(options = {}) ⇒ Object


497
498
499
500
501
502
503
504
505
506
507
# File 'app/models/issue.rb', line 497

def as_json(options = {})
  super(options).tap do |json|
    if options.key?(:labels)
      json[:labels] = labels.as_json(
        project: project,
        only: [:id, :title, :description, :color, :priority],
        methods: [:text_color]
      )
    end
  end
end

#banzai_render_context(field) ⇒ Object


531
532
533
# File 'app/models/issue.rb', line 531

def banzai_render_context(field)
  super.merge(label_url_method: :project_issues_url)
end

#blocked_for_repositioning?Boolean

Returns:

  • (Boolean)

383
384
385
# File 'app/models/issue.rb', line 383

def blocked_for_repositioning?
  resource_parent.root_namespace&.issue_repositioning_disabled?
end

#can_be_worked_on?Boolean

Returns:

  • (Boolean)

469
470
471
# File 'app/models/issue.rb', line 469

def can_be_worked_on?
  !self.closed? && !self.project.forked?
end

#can_move?(user, to_project = nil) ⇒ Boolean Also known as: can_clone?

Returns:

  • (Boolean)

429
430
431
432
433
434
435
436
# File 'app/models/issue.rb', line 429

def can_move?(user, to_project = nil)
  if to_project
    return false unless user.can?(:admin_issue, to_project)
  end

  !moved? && persisted? &&
    user.can?(:admin_issue, self.project)
end

#check_for_spam?(user:) ⇒ Boolean

Returns:

  • (Boolean)

485
486
487
488
489
490
491
492
493
494
495
# File 'app/models/issue.rb', line 485

def check_for_spam?(user:)
  # content created via support bots is always checked for spam, EVEN if
  # the issue is not publicly visible and/or confidential
  return true if user.support_bot? && spammable_attribute_changed?

  # Only check for spam on issues which are publicly visible (and thus indexed in search engines)
  return false unless publicly_visible?

  # Only check for spam if certain attributes have changed
  spammable_attribute_changed?
end

#check_repositioning_allowed!Object

Temporary disable moving null elements because of performance problems For more information check gitlab.com/gitlab-com/gl-infra/production/-/issues/4321


377
378
379
380
381
# File 'app/models/issue.rb', line 377

def check_repositioning_allowed!
  if blocked_for_repositioning?
    raise ::Gitlab::RelativePositioning::IssuePositioningDisabled, "Issue relative position changes temporarily disabled."
  end
end

#clear_closure_reason_referencesObject


424
425
426
427
# File 'app/models/issue.rb', line 424

def clear_closure_reason_references
  self.moved_to_id = nil
  self.duplicated_to_id = nil
end

#design_collectionObject


535
536
537
# File 'app/models/issue.rb', line 535

def design_collection
  @design_collection ||= ::DesignManagement::DesignCollection.new(self)
end

#discussions_rendered_on_frontend?Boolean

Returns:

  • (Boolean)

513
514
515
# File 'app/models/issue.rb', line 513

def discussions_rendered_on_frontend?
  true
end

#duplicated?Boolean

Returns:

  • (Boolean)

420
421
422
# File 'app/models/issue.rb', line 420

def duplicated?
  !duplicated_to_id.nil?
end

#email_participants_emailsObject


568
569
570
# File 'app/models/issue.rb', line 568

def email_participants_emails
  issue_email_participants.pluck(:email)
end

#email_participants_emails_downcaseObject


572
573
574
# File 'app/models/issue.rb', line 572

def email_participants_emails_downcase
  issue_email_participants.pluck(IssueEmailParticipant.arel_table[:email].lower)
end

#etag_caching_enabled?Boolean

Returns:

  • (Boolean)

509
510
511
# File 'app/models/issue.rb', line 509

def etag_caching_enabled?
  true
end

#from_service_desk?Boolean

Returns:

  • (Boolean)

539
540
541
# File 'app/models/issue.rb', line 539

def from_service_desk?
  author.id == User.support_bot.id
end

Returns boolean if a related branch exists for the current issue ignores merge requests branchs

Returns:

  • (Boolean)

405
406
407
408
409
# File 'app/models/issue.rb', line 405

def has_related_branch?
  project.repository.branch_names.any? do |branch|
    /\A#{iid}-(?!\d+-stable)/i =~ branch
  end
end

#hidden?Boolean

Returns:

  • (Boolean)

606
607
608
# File 'app/models/issue.rb', line 606

def hidden?
  author&.banned?
end

#issue_assignee_user_idsObject


576
577
578
# File 'app/models/issue.rb', line 576

def issue_assignee_user_ids
  issue_assignees.pluck(:user_id)
end

543
544
545
546
547
548
549
550
# File 'app/models/issue.rb', line 543

def issue_link_type
  return unless respond_to?(:issue_link_type_value) && respond_to?(:issue_link_source_id)

  type = IssueLink.link_types.key(issue_link_type_value) || IssueLink::TYPE_RELATES_TO
  return type if issue_link_source_id == id

  IssueLink.inverse_link_type(type)
end

#merge_requests_count(user = nil) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


523
524
525
# File 'app/models/issue.rb', line 523

def merge_requests_count(user = nil)
  ::MergeRequestsClosingIssues.count_for_issue(self.id, user)
end

#moved?Boolean

Returns:

  • (Boolean)

416
417
418
# File 'app/models/issue.rb', line 416

def moved?
  !moved_to_id.nil?
end

#next_object_by_relative_position(ignoring: nil, order: :asc) ⇒ Object


247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'app/models/issue.rb', line 247

def next_object_by_relative_position(ignoring: nil, order: :asc)
  array_mapping_scope = -> (id_expression) do
    relation = Issue.where(Issue.arel_table[:project_id].eq(id_expression))

    if order == :asc
      relation.where(Issue.arel_table[:relative_position].gt(relative_position))
    else
      relation.where(Issue.arel_table[:relative_position].lt(relative_position))
    end
  end

  relation = Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new(
    scope: Issue.order(relative_position: order, id: order),
    array_scope: relative_positioning_parent_projects,
    array_mapping_scope: array_mapping_scope,
    finder_query: -> (_, id_expression) { Issue.where(Issue.arel_table[:id].eq(id_expression)) }
  ).execute

  relation = exclude_self(relation, excluded: ignoring) if ignoring.present?

  relation.take
end

#previous_updated_atObject


527
528
529
# File 'app/models/issue.rb', line 527

def previous_updated_at
  previous_changes['updated_at']&.first || updated_at
end

#readable_by?(user) ⇒ Boolean

Returns `true` if the given User can read the current Issue.

This method duplicates the same check of issue_policy.rb for performance reasons, check commit: 002ad215818450d2cbbc5fa065850a953dc7ada8 Make sure to sync this method with issue_policy.rb

Returns:

  • (Boolean)

590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
# File 'app/models/issue.rb', line 590

def readable_by?(user)
  if user.can_read_all_resources?
    true
  elsif project.personal? && project.team.owner?(user)
    true
  elsif confidential? && !assignee_or_author?(user)
    project.team.member?(user, Gitlab::Access::REPORTER)
  elsif hidden?
    false
  elsif project.public? || (project.internal? && !user.external?)
    project.feature_available?(:issues, user)
  else
    project.team.member?(user)
  end
end

447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'app/models/issue.rb', line 447

def related_issues(current_user, preload: nil)
  related_issues = ::Issue
                     .select(['issues.*', 'issue_links.id AS issue_link_id',
                              'issue_links.link_type as issue_link_type_value',
                              'issue_links.target_id as issue_link_source_id',
                              'issue_links.created_at as issue_link_created_at',
                              'issue_links.updated_at as issue_link_updated_at'])
                     .joins("INNER JOIN issue_links ON
                            (issue_links.source_id = issues.id AND issue_links.target_id = #{id})
                            OR
                            (issue_links.target_id = issues.id AND issue_links.source_id = #{id})")
                     .preload(preload)
                     .reorder('issue_link_id')

  related_issues = yield related_issues if block_given?

  cross_project_filter = -> (issues) { issues.where(project: project) }
  Ability.issues_readable_by_user(related_issues,
    current_user,
    filters: { read_cross_project: cross_project_filter })
end

#relative_positioning_parent_projectsObject


270
271
272
# File 'app/models/issue.rb', line 270

def relative_positioning_parent_projects
  project.group&.root_ancestor&.all_projects&.select(:id) || Project.id_in(project).select(:id)
end

#relocation_targetObject


552
553
554
# File 'app/models/issue.rb', line 552

def relocation_target
  moved_to || duplicated_to
end

#source_projectObject

To allow polymorphism with MergeRequest.


412
413
414
# File 'app/models/issue.rb', line 412

def source_project
  project
end

#suggested_branch_nameObject


394
395
396
397
398
399
400
401
# File 'app/models/issue.rb', line 394

def suggested_branch_name
  return to_branch_name unless project.repository.branch_exists?(to_branch_name)

  start_counting_from = 2
  Uniquify.new(start_counting_from).string(-> (counter) { "#{to_branch_name}-#{counter}" }) do |suggested_branch_name|
    project.repository.branch_exists?(suggested_branch_name)
  end
end

#supports_assignee?Boolean

Returns:

  • (Boolean)

556
557
558
# File 'app/models/issue.rb', line 556

def supports_assignee?
  issue_type_supports?(:assignee)
end

#supports_move_and_clone?Boolean

Returns:

  • (Boolean)

564
565
566
# File 'app/models/issue.rb', line 564

def supports_move_and_clone?
  issue_type_supports?(:move_and_clone)
end

#supports_time_tracking?Boolean

Returns:

  • (Boolean)

560
561
562
# File 'app/models/issue.rb', line 560

def supports_time_tracking?
  issue_type_supports?(:time_tracking)
end

#to_branch_nameObject


439
440
441
442
443
444
445
# File 'app/models/issue.rb', line 439

def to_branch_name
  if self.confidential?
    "#{iid}-confidential-issue"
  else
    self.class.to_branch_name(iid, title)
  end
end

#to_reference(from = nil, full: false) ⇒ Object

`from` argument can be a Namespace or Project.


388
389
390
391
392
# File 'app/models/issue.rb', line 388

def to_reference(from = nil, full: false)
  reference = "#{self.class.reference_prefix}#{iid}"

  "#{project.to_reference_base(from, full: full)}#{reference}"
end

#update_project_counter_cachesObject

rubocop: disable CodeReuse/ServiceClass


518
519
520
# File 'app/models/issue.rb', line 518

def update_project_counter_caches
  Projects::OpenIssuesCountService.new(project).refresh_cache
end

#update_upvotes_countObject


580
581
582
583
# File 'app/models/issue.rb', line 580

def update_upvotes_count
  self.lock!
  self.update_column(:upvotes_count, self.upvotes)
end

#visible_to_user?(user = nil) ⇒ Boolean

Returns `true` if the current issue can be viewed by either a logged in User or an anonymous user.

Returns:

  • (Boolean)

475
476
477
478
479
480
481
482
483
# File 'app/models/issue.rb', line 475

def visible_to_user?(user = nil)
  return publicly_visible? unless user

  return false unless readable_by?(user)

  user.can_read_all_resources? ||
    ::Gitlab::ExternalAuthorization.access_allowed?(
      user, project.external_authorization_classification_label)
end

#work_item_typeObject

Necessary until all issues are backfilled and we add a NOT NULL constraint on the DB


611
612
613
# File 'app/models/issue.rb', line 611

def work_item_type
  super || WorkItems::Type.default_by_type(issue_type)
end