Class: Issue
- Inherits:
-
ApplicationRecord
show all
- Extended by:
- Gitlab::Utils::Override
- Includes:
- AtomicInternalId, EachBatch, FasterCacheKeys, FromUnion, Gitlab::DueAtFilterable, IdInOrdered, IidRoutes, Issuable, IssueAvailableFeatures, LabelEventable, MilestoneEventable, Noteable, PgFullTextSearchable, Presentable, Referable, RelativePositioning, Spammable, StateEventable, ThrottledTouch, TimeTrackable, Todoable, WhereComposite
- Defined in:
- app/models/issue.rb
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
- IssueTypeOutOfSyncError =
Class.new(StandardError)
- SORTING_PREFERENCE_FIELD =
:issues_sort
- MAX_BRANCH_TEMPLATE =
255
- TYPES_FOR_LIST =
%w[issue incident test_case task objective key_result ticket].freeze
- TYPES_FOR_BOARD_LIST =
Types of issues that should be displayed on issue board lists
%w[issue incident ticket].freeze
- DEFAULT_ISSUE_TYPE =
This default came from the enum issue_type column. Defined as default in the DB
:issue
PgFullTextSearchable::LONG_WORDS_REGEX, PgFullTextSearchable::TEXT_SEARCH_DICTIONARY, PgFullTextSearchable::TSVECTOR_MAX_LENGTH, PgFullTextSearchable::VERY_LONG_WORDS_WITH_AT_REGEX, PgFullTextSearchable::XML_TAG_REGEX
ThrottledTouch::TOUCH_INTERVAL
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_LENGTH_MAX, Issuable::MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS, Issuable::SEARCHABLE_FIELDS, Issuable::STATE_ID_MAP, Issuable::TITLE_LENGTH_MAX
Import::HasImportSource::IMPORT_SOURCES
Constants included
from Taskable
Taskable::COMPLETED, Taskable::COMPLETE_PATTERN, Taskable::INCOMPLETE, Taskable::INCOMPLETE_PATTERN, Taskable::ITEM_PATTERN
CacheMarkdownField::INVALIDATED_BY
Constants included
from Redactable
Redactable::UNSUBSCRIBE_PATTERN
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM
AtomicInternalId::MissingValueError
ApplicationRecord::MAX_PLUCK
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
ResetOnColumnErrors::MAX_RESET_PERIOD
Instance Attribute Summary
Attributes included from Noteable
#system_note_timestamp
#transitioning
Attributes included from Importable
#importing, #user_contributions
#skip_markdown_cache_validation
Class Method Summary
collapse
Instance Method Summary
collapse
extended, extensions, included, method_added, override, prepended, queue_verification, verify!
#update_search_data!
#issue_type_supports?
#present
#touch
#clear_memoized_total_time_spent, #human_time_change, #human_time_estimate, #human_total_time_spent, #reload, #reset, #set_time_estimate_default_value, #spend_time, #time_change, #time_estimate, #time_estimate=, #total_time_spent
#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
range
#cache_key
Methods included from Spammable
#check_for_spam, #check_for_spam?, #clear_spam_flags!, #invalidate_if_spam, #needs_recaptcha!, #recaptcha_error!, #render_recaptcha?, #spam, #spam!, #spam_description, #spam_title, #spammable_attribute_changed?, #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, #broadcast_notes_changed, #capped_notes_count, #commenters, #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, #grouped_diff_discussions, #has_any_diff_note_positions?, #human_class_name, #lockable?, #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
#allows_scoped_labels?, #assignee?, #assignee_list, #assignee_or_author?, #assignee_username_list, #card_attributes, #draftless_title_changed, #first_contribution?, #hook_association_changes, #hook_reviewer_changes, #label_names, #labels_array, #labels_hook_attrs, #notes_for_participants, #notes_with_associations, #old_assignees, #old_escalation_status, #old_labels, #old_severity, #old_target_branch, #old_time_change, #old_total_time_spent, #open?, #overdue?, #read_ability_for, #reviewers_hook_attrs, #state, #state=, #subscribed_without_subscriptions?, #supports_health_status?, #to_ability_name, #to_hook_data, #updated_tasks, #user_notes_count
#imported?
#changes_applied, #clear_changes_information, #reload, #reportable_changes
Methods included from Exportable
#exportable_association?, #restricted_associations, #to_authorized_json
#run_after_commit, #run_after_commit_or_now
Methods included from Editable
#edited?, #last_edited_by
#disable_transitioning, #enable_transitioning, #transitioning?
Methods included from Taskable
#complete_task_list_item_count, get_tasks, get_updated_tasks, #task_completion_status, #task_list_items, #task_status, #task_status_short, #tasks?
Methods included from Awardable
#awarded_emoji?, #downvotes, #emoji_awardable?, #grouped_awards, #upvotes, #user_authored?, #user_can_award?
#strip_attributes!
#lazy_subscription, #set_subscription, #subscribe, #subscribed?, #subscribed_without_subscriptions?, #subscribers, #toggle_subscription, #unsubscribe
#milestone_available?, #supports_milestone?
#all_references, #create_cross_references!, #create_new_cross_references!, #directly_addressed_users, #extractors, #local_reference, #matches_cross_reference_regex?, #mentioned_users, #referenced_group_users, #referenced_groups, #referenced_mentionables, #referenced_projects, #referenced_users, #user_mention_class, #user_mention_identifier
#participant?, #participants, #visible_participants
#attribute_invalidated?, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #mentionable_attributes_changed?, #mentioned_filtered_user_ids_for, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #store_mentions!, #store_mentions?, #store_mentions_after_commit?, #updated_cached_html_for
split_query_to_search_terms
Methods included from IidRoutes
#to_param
group_init, #internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage, namespace_init, project_init, scope_attrs, scope_usage
===, 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
.alternative_reference_prefix_with_postfix ⇒ Object
437
438
439
|
# File 'app/models/issue.rb', line 437
def self.alternative_reference_prefix_with_postfix
'[issue:'
end
|
.alternative_reference_prefix_without_postfix ⇒ Object
Alternative prefix for situations where the standard prefix would be interpreted as a comment, most notably to begin commit messages with (e.g. “GL-123: My commit”)
433
434
435
|
# File 'app/models/issue.rb', line 433
def self.alternative_reference_prefix_without_postfix
'GL-'
end
|
.column_order_id_asc ⇒ Object
.column_order_relative_position ⇒ Object
518
519
520
521
522
523
524
525
|
# File 'app/models/issue.rb', line 518
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,
nullable: :nulls_last
)
end
|
.full_search(query, matched_columns: nil, use_minimum_char_limit: true) ⇒ Object
.in_namespaces_with_cte(namespaces) ⇒ Object
345
346
347
348
349
|
# File 'app/models/issue.rb', line 345
def in_namespaces_with_cte(namespaces)
cte = Gitlab::SQL::CTE.new(:namespace_ids, namespaces.select(:id))
where('issues.namespace_id IN (SELECT id FROM namespace_ids)').with(cte.to_arel)
end
|
.link_reference_pattern ⇒ Object
469
470
471
|
# File 'app/models/issue.rb', line 469
def self.link_reference_pattern
@link_reference_pattern ||= compose_link_reference_pattern(%r{issues(?:\/incident)?}, Gitlab::Regex.issue)
end
|
.order_by_relative_position ⇒ Object
514
515
516
|
# File 'app/models/issue.rb', line 514
def self.order_by_relative_position
reorder(Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_id_asc]))
end
|
.order_upvotes_asc ⇒ Object
364
365
366
|
# File 'app/models/issue.rb', line 364
def order_upvotes_asc
reorder(upvotes_count: :asc)
end
|
.order_upvotes_desc ⇒ Object
359
360
361
|
# File 'app/models/issue.rb', line 359
def order_upvotes_desc
reorder(upvotes_count: :desc)
end
|
.participant_includes ⇒ Object
383
384
385
|
# File 'app/models/issue.rb', line 383
def self.participant_includes
[:assignees] + super
end
|
.project_foreign_key ⇒ Object
477
478
479
|
# File 'app/models/issue.rb', line 477
def self.project_foreign_key
'project_id'
end
|
.reference_pattern ⇒ Object
Pattern used to extract issue references from text This pattern supports cross-project references.
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 self.reference_pattern
prefix_with_postfix = alternative_reference_prefix_with_postfix
if prefix_with_postfix.empty?
@reference_pattern ||= %r{
(?:
(#{Project.reference_pattern})?#{Regexp.escape(reference_prefix)} |
#{Regexp.escape(alternative_reference_prefix_without_postfix)}
)#{Gitlab::Regex.issue}
}x
else
%r{
((?:
(#{Project.reference_pattern})?#{Regexp.escape(reference_prefix)} |
#{alternative_reference_prefix_without_postfix}
)#{Gitlab::Regex.issue}) |
((?:
#{Regexp.escape(prefix_with_postfix)}(#{Project.reference_pattern}/)?
)#{Gitlab::Regex.issue(reference_postfix)})
}x
end
end
|
.reference_postfix ⇒ Object
441
442
443
|
# File 'app/models/issue.rb', line 441
def self.reference_postfix
']'
end
|
.reference_prefix ⇒ Object
426
427
428
|
# File 'app/models/issue.rb', line 426
def self.reference_prefix
'#'
end
|
.reference_valid?(reference) ⇒ Boolean
473
474
475
|
# File 'app/models/issue.rb', line 473
def self.reference_valid?(reference)
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
end
|
378
379
380
|
# File 'app/models/issue.rb', line 378
def related_link_class
IssueLink
end
|
.relative_positioning_parent_column ⇒ Object
422
423
424
|
# File 'app/models/issue.rb', line 422
def self.relative_positioning_parent_column
:project_id
end
|
.relative_positioning_query_base(issue) ⇒ Object
418
419
420
|
# File 'app/models/issue.rb', line 418
def self.relative_positioning_query_base(issue)
in_projects(issue.relative_positioning_parent_projects)
end
|
.simple_sorts ⇒ Object
481
482
483
484
485
486
487
488
489
490
491
492
493
|
# File 'app/models/issue.rb', line 481
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
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
|
# File 'app/models/issue.rb', line 495
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 'start_date', 'start_date_asc' then order_start_date_asc.with_order_id_desc
when 'start_date_desc' then order_start_date_desc.with_order_id_desc
when 'relative_position', 'relative_position_asc' then order_by_relative_position
when 'severity_asc' then order_severity_asc
when 'severity_desc' then order_severity_desc
when 'escalation_status_asc' then order_escalation_status_asc
when 'escalation_status_desc' then order_escalation_status_desc
when 'closed_at', 'closed_at_asc' then order_closed_at_asc
when 'closed_at_desc' then order_closed_at_desc
else
super
end
end
|
.supported_keyset_orderings ⇒ Object
560
561
562
563
564
565
566
567
568
569
|
# File 'app/models/issue.rb', line 560
def self.supported_keyset_orderings
{
id: [:asc, :desc],
title: [:asc, :desc],
created_at: [:asc, :desc],
updated_at: [:asc, :desc],
due_date: [:asc, :desc],
relative_position: [:asc, :desc]
}
end
|
.to_branch_name(id, title, project: nil) ⇒ Object
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
|
# File 'app/models/issue.rb', line 534
def self.to_branch_name(id, title, project: nil)
params = {
'id' => id.to_s.parameterize(preserve_case: true),
'title' => title.to_s.parameterize
}
template = project&.issue_branch_template
branch_name =
if template.present?
Gitlab::StringPlaceholderReplacer.replace_string_placeholders(template, /(#{params.keys.join('|')})/) do |arg|
params[arg]
end
else
params.values.select(&:present?).join('-')
end
if branch_name.length > 100
truncated_string = branch_name[0, 100]
branch_name = truncated_string.sub(/-[^-]*\Z/, '')
end
branch_name
end
|
.with_accessible_sub_namespace_ids_cte(namespace_ids) ⇒ Object
351
352
353
354
355
356
|
# File 'app/models/issue.rb', line 351
def with_accessible_sub_namespace_ids_cte(namespace_ids)
accessible_sub_namespace_ids = Gitlab::SQL::CTE.new(:accessible_sub_namespace_ids, namespace_ids, materialized: true)
with(accessible_sub_namespace_ids.to_arel)
end
|
Instance Method Details
#==(other) ⇒ Object
Also known as:
eql?
926
927
928
929
930
|
# File 'app/models/issue.rb', line 926
def ==(other)
return super unless id.present?
other.is_a?(Issue) && other.id == id
end
|
#allow_possible_spam?(user) ⇒ Boolean
Always enforce spam check for support bot but allow for other users when issue is not publicly visible
670
671
672
673
674
675
|
# File 'app/models/issue.rb', line 670
def allow_possible_spam?(user)
return true if Gitlab::CurrentSettings.allow_possible_spam
return false if user.support_bot?
!publicly_visible?
end
|
#as_json(options = {}) ⇒ Object
684
685
686
687
688
689
690
691
692
693
694
|
# File 'app/models/issue.rb', line 684
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
|
#autoclose_by_merged_closing_merge_request? ⇒ Boolean
872
873
874
875
876
|
# File 'app/models/issue.rb', line 872
def autoclose_by_merged_closing_merge_request?
return false if group_level?
project.autoclose_referenced_issues
end
|
#banzai_render_context(field) ⇒ Object
720
721
722
723
724
725
|
# File 'app/models/issue.rb', line 720
def banzai_render_context(field)
additional_attributes = { label_url_method: :project_issues_url }
additional_attributes[:group] = namespace if namespace.is_a?(Group)
super.merge(additional_attributes)
end
|
#blocked_for_repositioning? ⇒ Boolean
579
580
581
|
# File 'app/models/issue.rb', line 579
def blocked_for_repositioning?
namespace.root_ancestor&.issue_repositioning_disabled?
end
|
#can_be_worked_on? ⇒ Boolean
665
666
667
|
# File 'app/models/issue.rb', line 665
def can_be_worked_on?
!self.closed? && !self.project.forked?
end
|
#can_move?(user, to_namespace = nil) ⇒ Boolean
Also known as:
can_clone?
623
624
625
626
627
628
629
|
# File 'app/models/issue.rb', line 623
def can_move?(user, to_namespace = nil)
if to_namespace
return false unless user.can?(:admin_issue, to_namespace)
end
!moved? && persisted? && user.can?(:admin_issue, self)
end
|
#check_repositioning_allowed! ⇒ Object
573
574
575
576
577
|
# File 'app/models/issue.rb', line 573
def check_repositioning_allowed!
if blocked_for_repositioning?
raise ::Gitlab::RelativePositioning::IssuePositioningDisabled, "Issue relative position changes temporarily disabled."
end
end
|
#clear_closure_reason_references ⇒ Object
618
619
620
621
|
# File 'app/models/issue.rb', line 618
def clear_closure_reason_references
self.moved_to_id = nil
self.duplicated_to_id = nil
end
|
#design_collection ⇒ Object
#discussions_rendered_on_frontend? ⇒ Boolean
700
701
702
|
# File 'app/models/issue.rb', line 700
def discussions_rendered_on_frontend?
true
end
|
#duplicated? ⇒ Boolean
614
615
616
|
# File 'app/models/issue.rb', line 614
def duplicated?
!duplicated_to_id.nil?
end
|
#email_participants_emails ⇒ Object
761
762
763
|
# File 'app/models/issue.rb', line 761
def email_participants_emails
issue_email_participants.pluck(:email)
end
|
#email_participants_emails_downcase ⇒ Object
765
766
767
|
# File 'app/models/issue.rb', line 765
def email_participants_emails_downcase
issue_email_participants.pluck(IssueEmailParticipant.arel_table[:email].lower)
end
|
#ensure_work_item_description ⇒ Object
913
914
915
916
917
918
919
920
921
922
923
924
|
# File 'app/models/issue.rb', line 913
def ensure_work_item_description
return if work_item_description.present?
build_work_item_description(
description: description,
description_html: description_html,
last_edited_at: last_edited_at,
last_edited_by_id: last_edited_by_id,
lock_version: lock_version,
cached_markdown_version: cached_markdown_version
)
end
|
#epic_work_item? ⇒ Boolean
878
879
880
|
# File 'app/models/issue.rb', line 878
def epic_work_item?
work_item_type&.epic?
end
|
#expire_etag_cache ⇒ Object
#from_service_desk? ⇒ Boolean
731
732
733
|
# File 'app/models/issue.rb', line 731
def from_service_desk?
author.support_bot?
end
|
#gfm_reference(from = nil) ⇒ Object
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
|
# File 'app/models/issue.rb', line 825
def gfm_reference(from = nil)
params = {}
params[:full] = true if (from.is_a?(Project) || from.is_a?(Namespaces::ProjectNamespace)) && group_level?
params[:absolute_path] = true unless namespace.has_parent?
"#{work_item_type_with_default.name.underscore} #{to_reference(from, **params)}"
end
|
#group_epic_work_item? ⇒ Boolean
882
883
884
|
# File 'app/models/issue.rb', line 882
def group_epic_work_item?
epic_work_item? && group_level?
end
|
#group_level? ⇒ Boolean
868
869
870
|
# File 'app/models/issue.rb', line 868
def group_level?
project_id.blank?
end
|
862
863
864
865
866
|
# File 'app/models/issue.rb', line 862
def has_widget?(widget)
widget_class = WorkItems::Widgets.const_get(widget.to_s.camelize, false)
work_item_type.widget_classes(resource_parent).include?(widget_class)
end
|
#hidden? ⇒ Boolean
778
779
780
|
# File 'app/models/issue.rb', line 778
def hidden?
author&.banned?
end
|
#invalidate_project_counter_caches ⇒ Object
rubocop: disable CodeReuse/ServiceClass
#issue_assignee_user_ids ⇒ Object
769
770
771
|
# File 'app/models/issue.rb', line 769
def issue_assignee_user_ids
issue_assignees.pluck(:user_id)
end
|
#issue_link_type ⇒ Object
735
736
737
738
739
740
741
742
743
|
# File 'app/models/issue.rb', line 735
def issue_link_type
link_class = self.class.related_link_class
return unless respond_to?(:issue_link_type_value) && respond_to?(:issue_link_source_id)
type = link_class.link_types.key(issue_link_type_value) || link_class::TYPE_RELATES_TO
return type if issue_link_source_id == id
link_class.inverse_link_type(type)
end
|
#issue_type ⇒ Object
816
817
818
|
# File 'app/models/issue.rb', line 816
def issue_type
work_item_type_with_default.base_type
end
|
#issuing_parent_id ⇒ Object
806
807
808
|
# File 'app/models/issue.rb', line 806
def issuing_parent_id
project_id.presence || namespace_id
end
|
#linked_items_count ⇒ Object
661
662
663
|
# File 'app/models/issue.rb', line 661
def linked_items_count
related_issues(authorize: false).size
end
|
#merge_requests_count(user = nil) ⇒ Object
rubocop: enable CodeReuse/ServiceClass
#moved? ⇒ Boolean
610
611
612
|
# File 'app/models/issue.rb', line 610
def moved?
!moved_to_id.nil?
end
|
#next_object_by_relative_position(ignoring: nil, order: :asc) ⇒ Object
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
|
# File 'app/models/issue.rb', line 387
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_at ⇒ Object
716
717
718
|
# File 'app/models/issue.rb', line 716
def previous_updated_at
previous_changes['updated_at']&.first || updated_at
end
|
#real_time_notes_enabled? ⇒ Boolean
696
697
698
|
# File 'app/models/issue.rb', line 696
def real_time_notes_enabled?
true
end
|
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
|
# File 'app/models/issue.rb', line 640
def related_issues(current_user = nil, authorize: true, preload: nil)
return [] if new_record?
related_issues =
linked_issues_select
.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?
return related_issues unless authorize
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_projects ⇒ Object
410
411
412
413
414
415
416
|
# File 'app/models/issue.rb', line 410
def relative_positioning_parent_projects
if namespace.parent&.user_namespace?
Project.id_in(namespace.project).select(:id)
else
namespace.root_ancestor&.all_projects&.select(:id)
end
end
|
#relocation_target ⇒ Object
745
746
747
|
# File 'app/models/issue.rb', line 745
def relocation_target
moved_to || duplicated_to
end
|
#require_legacy_views? ⇒ Boolean
Legacy views/workflows only
909
910
911
|
# File 'app/models/issue.rb', line 909
def require_legacy_views?
from_service_desk? || work_item_type&.incident?
end
|
#resource_parent ⇒ Object
Also known as:
issuing_parent
801
802
803
|
# File 'app/models/issue.rb', line 801
def resource_parent
project || namespace
end
|
#show_as_work_item? ⇒ Boolean
Some Issues types/conditions were not fully migrated to WorkItems UI/workflows yet. On the other hand some other Issue types/conditions are only available through WorkItems UI/workflows.
Overriden on EE (For OKRs and Epics)
898
899
900
901
902
903
904
|
# File 'app/models/issue.rb', line 898
def show_as_work_item?
return false if require_legacy_views?
return true if group_level?
return true if work_item_type&.task?
resource_parent.work_items_consolidated_list_enabled?
end
|
#skip_metrics? ⇒ Boolean
858
859
860
|
# File 'app/models/issue.rb', line 858
def skip_metrics?
importing?
end
|
#source_project ⇒ Object
To allow polymorphism with MergeRequest.
606
607
608
|
# File 'app/models/issue.rb', line 606
def source_project
project
end
|
#suggested_branch_name ⇒ Object
590
591
592
593
594
595
596
597
598
599
600
601
602
603
|
# File 'app/models/issue.rb', line 590
def suggested_branch_name
return to_branch_name unless project.repository.branch_exists?(to_branch_name)
start_counting_from = 2
branch_name_generator = ->(counter) do
suffix = counter > 5 ? SecureRandom.hex(8) : counter
"#{to_branch_name}-#{suffix}"
end
Gitlab::Utils::Uniquify.new(start_counting_from).string(branch_name_generator) do |suggested_branch_name|
project.repository.branch_exists?(suggested_branch_name)
end
end
|
#supports_assignee? ⇒ Boolean
749
750
751
|
# File 'app/models/issue.rb', line 749
def supports_assignee?
work_item_type_with_default.supports_assignee?(resource_parent)
end
|
#supports_confidentiality? ⇒ Boolean
791
792
793
|
# File 'app/models/issue.rb', line 791
def supports_confidentiality?
true
end
|
#supports_move_and_clone? ⇒ Boolean
757
758
759
|
# File 'app/models/issue.rb', line 757
def supports_move_and_clone?
issue_type_supports?(:move_and_clone)
end
|
#supports_parent? ⇒ Boolean
682
|
# File 'app/models/issue.rb', line 682
def supports_parent?; end
|
#supports_recaptcha? ⇒ Boolean
677
678
679
|
# File 'app/models/issue.rb', line 677
def supports_recaptcha?
true
end
|
#supports_time_tracking? ⇒ Boolean
753
754
755
|
# File 'app/models/issue.rb', line 753
def supports_time_tracking?
issue_type_supports?(:time_tracking)
end
|
#to_branch_name ⇒ Object
632
633
634
635
636
637
638
|
# File 'app/models/issue.rb', line 632
def to_branch_name
if self.confidential?
"#{iid}-confidential-issue"
else
self.class.to_branch_name(iid, title, project: project)
end
end
|
#to_reference(from = nil, full: false, absolute_path: false) ⇒ Object
from argument can be a Namespace or Project.
584
585
586
587
588
|
# File 'app/models/issue.rb', line 584
def to_reference(from = nil, full: false, absolute_path: false)
reference = "#{self.class.reference_prefix}#{iid}"
"#{namespace.to_reference_base(from, full: full, absolute_path: absolute_path)}#{reference}"
end
|
#to_work_item_global_id ⇒ Object
we want to have subscriptions working on work items only, legacy issues do not support graphql subscriptions, yet so we need sometimes GID of an issue instance to be represented as WorkItem GID. E.g. notes subscriptions.
#update_upvotes_count ⇒ Object
773
774
775
776
|
# File 'app/models/issue.rb', line 773
def update_upvotes_count
self.lock!
self.update_column(:upvotes_count, self.upvotes)
end
|
#use_work_item_url? ⇒ Boolean
886
887
888
889
890
891
|
# File 'app/models/issue.rb', line 886
def use_work_item_url?
return false if require_legacy_views?
return true if work_item_type&.task?
resource_parent.use_work_item_url?
end
|
#work_item_type_with_default ⇒ Object
Persisted records will always have a work_item_type. This method is useful in places where we use a non persisted issue to perform feature checks