Module: Issuable
- Extended by:
- ActiveSupport::Concern
- Includes:
- AfterCommitQueue, Awardable, CacheMarkdownField, ClosedAtFilterable, CreatedAtFilterable, Editable, Exportable, Gitlab::SQL::Pattern, Import::HasImportSource, Importable, Mentionable, Milestoneable, Participable, Redactable, ReportableChanges, Sortable, SortableTitle, StripAttribute, Subscribable, Taskable, Transitionable, UpdatedAtFilterable, VersionedDescription
- Included in:
- Issue, MergeRequest
- Defined in:
- app/models/concerns/issuable.rb,
app/services/issuable/callbacks/base.rb,
app/services/issuable/destroy_service.rb,
app/services/issuable/callbacks/labels.rb,
app/services/issuable/process_assignees.rb,
app/services/issuable/clone/base_service.rb,
app/services/issuable/bulk_update_service.rb,
app/services/issuable/callbacks/milestone.rb,
app/services/issuable/callbacks/description.rb,
app/workers/issuable/create_reminder_worker.rb,
app/services/issuable/callbacks/time_tracking.rb,
app/services/issuable/import_csv/base_service.rb,
app/services/issuable/discussions_list_service.rb,
app/workers/issuable/label_links_destroy_worker.rb,
app/workers/issuable/related_links_create_worker.rb,
app/services/issuable/common_system_notes_service.rb,
app/services/issuable/destroy_label_links_service.rb
Overview
This service return notes grouped by discussion ID and paginated per discussion. System notes also have a discussion ID assigned including Synthetic system notes.
Defined Under Namespace
Modules: Callbacks, Clone, ImportCsv Classes: BulkUpdateService, CommonSystemNotesService, CreateReminderWorker, DestroyLabelLinksService, DestroyService, DiscussionsListService, LabelLinksDestroyWorker, ProcessAssignees, RelatedLinksCreateWorker
Constant Summary collapse
- TITLE_LENGTH_MAX =
255
- DESCRIPTION_LENGTH_MAX =
1.megabyte
- SEARCHABLE_FIELDS =
%w[title description].freeze
- MAX_NUMBER_OF_ASSIGNEES_OR_REVIEWERS =
200
- STATE_ID_MAP =
{ opened: 1, closed: 2, merged: 3, locked: 4 }.with_indifferent_access.freeze
Constants included from Import::HasImportSource
Import::HasImportSource::IMPORT_SOURCES
Constants included from Taskable
Taskable::COMPLETED, Taskable::COMPLETE_PATTERN, Taskable::INCOMPLETE, Taskable::INCOMPLETE_PATTERN, Taskable::ITEM_PATTERN, Taskable::ITEM_PATTERN_UNTRUSTED, Taskable::REGEX
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_TERM
Instance Attribute Summary
Attributes included from Transitionable
Attributes included from Importable
#importing, #user_contributions
Attributes included from CacheMarkdownField
#skip_markdown_cache_validation
Instance Method Summary collapse
- #allows_scoped_labels? ⇒ Boolean
- #assignee?(user) ⇒ Boolean
- #assignee_list ⇒ Object
- #assignee_or_author?(user) ⇒ Boolean
- #assignee_username_list ⇒ Object
- #can_assign_epic?(user) ⇒ Boolean
-
#can_move? ⇒ Boolean
Method that checks if issuable can be moved to another project.
-
#card_attributes ⇒ Object
Returns a Hash of attributes to be used for Twitter card metadata.
-
#draftless_title_changed(old_title) ⇒ Object
Overridden in MergeRequest.
-
#first_contribution? ⇒ Boolean
Override in issuable specialization.
-
#hook_association_changes(old_associations) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity – Related issue: gitlab.com/gitlab-org/gitlab/-/issues/437679.
-
#hook_reviewer_changes(old_associations) ⇒ Object
rubocop:enable Metrics/PerceivedComplexity.
- #label_names ⇒ Object
- #labels_array ⇒ Object
- #labels_hook_attrs ⇒ Object
- #notes_with_associations ⇒ Object
- #old_assignees(assoc) ⇒ Object
- #old_escalation_status(assoc) ⇒ Object
- #old_labels(assoc) ⇒ Object
- #old_severity(assoc) ⇒ Object
- #old_target_branch(assoc) ⇒ Object
- #old_time_change(assoc) ⇒ Object
- #old_total_time_spent(assoc) ⇒ Object
- #open? ⇒ Boolean
- #overdue? ⇒ Boolean
- #read_ability_for(participable_source) ⇒ Object
- #resource_parent ⇒ Object
- #state ⇒ Object
- #state=(value) ⇒ Object
- #subscribed_without_subscriptions?(user, project) ⇒ Boolean
- #supports_health_status? ⇒ Boolean
-
#to_ability_name ⇒ Object
Convert this Issuable class name to a format usable by Ability definitions.
- #to_hook_data(user, old_associations: {}, action: nil) ⇒ Object
- #updated_tasks ⇒ Object
- #user_notes_count ⇒ Object
Methods included from Import::HasImportSource
Methods included from ReportableChanges
#as_json, #changes_applied, #clear_changes_information, #reload, #reportable_changes
Methods included from Exportable
#exportable_association?, #restricted_associations, #to_authorized_json
Methods included from AfterCommitQueue
#run_after_commit, #run_after_commit_or_now
Methods included from Editable
Methods included from Transitionable
#disable_transitioning, #enable_transitioning, #transitioning?
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
Methods included from Subscribable
#lazy_subscription, #set_subscription, #subscribe, #subscribed?, #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?, #banzai_render_context, #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_after_commit?, #updated_cached_html_for
Methods included from Gitlab::SQL::Pattern
Instance Method Details
#allows_scoped_labels? ⇒ Boolean
603 604 605 |
# File 'app/models/concerns/issuable.rb', line 603 def allows_scoped_labels? false end |
#assignee?(user) ⇒ Boolean
499 500 501 502 503 504 505 506 |
# File 'app/models/concerns/issuable.rb', line 499 def assignee?(user) # Necessary so we can preload the association and avoid N + 1 queries if assignees.loaded? assignees.to_a.include?(user) else assignees.exists?(user.id) end end |
#assignee_list ⇒ Object
625 626 627 |
# File 'app/models/concerns/issuable.rb', line 625 def assignee_list assignees.map(&:name).to_sentence end |
#assignee_or_author?(user) ⇒ Boolean
495 496 497 |
# File 'app/models/concerns/issuable.rb', line 495 def (user) == user.id || assignee?(user) end |
#assignee_username_list ⇒ Object
629 630 631 |
# File 'app/models/concerns/issuable.rb', line 629 def assignee_username_list assignees.map(&:username).to_sentence end |
#can_assign_epic?(user) ⇒ Boolean
532 533 534 |
# File 'app/models/concerns/issuable.rb', line 532 def can_assign_epic?(user) false end |
#can_move? ⇒ Boolean
Method that checks if issuable can be moved to another project.
Should be overridden if issuable can be moved.
665 666 667 |
# File 'app/models/concerns/issuable.rb', line 665 def can_move?(*) false end |
#card_attributes ⇒ Object
Returns a Hash of attributes to be used for Twitter card metadata
618 619 620 621 622 623 |
# File 'app/models/concerns/issuable.rb', line 618 def card_attributes { 'Author' => .try(:name), 'Assignee' => assignee_list } end |
#draftless_title_changed(old_title) ⇒ Object
Overridden in MergeRequest
679 680 681 |
# File 'app/models/concerns/issuable.rb', line 679 def draftless_title_changed(old_title) old_title != title end |
#first_contribution? ⇒ Boolean
Override in issuable specialization
672 673 674 |
# File 'app/models/concerns/issuable.rb', line 672 def first_contribution? false end |
#hook_association_changes(old_associations) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity – Related issue: gitlab.com/gitlab-org/gitlab/-/issues/437679
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 |
# File 'app/models/concerns/issuable.rb', line 537 def hook_association_changes(old_associations) changes = {} if old_assignees(old_associations) != assignees changes[:assignees] = [old_assignees(old_associations).map(&:hook_attrs), assignees.map(&:hook_attrs)] end if old_labels(old_associations) != labels changes[:labels] = [old_labels(old_associations).map(&:hook_attrs), labels.map(&:hook_attrs)] end if supports_severity? && old_severity(old_associations) != severity changes[:severity] = [old_severity(old_associations), severity] end if is_a?(MergeRequest) && old_target_branch(old_associations) != target_branch changes[:target_branch] = [old_target_branch(old_associations), target_branch] end if supports_escalation? && escalation_status && old_escalation_status(old_associations) != escalation_status.status_name changes[:escalation_status] = [old_escalation_status(old_associations), escalation_status.status_name] end if self.respond_to?(:total_time_spent) && old_total_time_spent(old_associations) != total_time_spent changes[:total_time_spent] = [old_total_time_spent(old_associations), total_time_spent] changes[:time_change] = [old_time_change(old_associations), time_change] end changes end |
#hook_reviewer_changes(old_associations) ⇒ Object
rubocop:enable Metrics/PerceivedComplexity
569 570 571 572 573 574 575 576 577 578 |
# File 'app/models/concerns/issuable.rb', line 569 def hook_reviewer_changes(old_associations) changes = {} old_reviewers = old_associations.fetch(:reviewers, reviewers) if old_reviewers != reviewers changes[:reviewers] = [old_reviewers.map(&:hook_attrs), reviewers.map(&:hook_attrs)] end changes end |
#label_names ⇒ Object
595 596 597 |
# File 'app/models/concerns/issuable.rb', line 595 def label_names labels.order('title ASC').pluck(:title) end |
#labels_array ⇒ Object
591 592 593 |
# File 'app/models/concerns/issuable.rb', line 591 def labels_array labels.to_a end |
#labels_hook_attrs ⇒ Object
599 600 601 |
# File 'app/models/concerns/issuable.rb', line 599 def labels_hook_attrs labels.map(&:hook_attrs) end |
#notes_with_associations ⇒ Object
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 |
# File 'app/models/concerns/issuable.rb', line 633 def notes_with_associations # If A has_many Bs, and B has_many Cs, and you do # `A.includes(b: :c).each { |a| a.b.includes(:c) }`, sadly ActiveRecord # will do the inclusion again. So, we check if all notes in the relation # already have their authors loaded (possibly because the scope # `inc_notes_with_associations` was used) and skip the inclusion if that's # the case. includes = [] includes << :author unless notes. includes << :award_emoji unless notes.award_emojis_loaded? includes << :project unless notes.projects_loaded? includes << :system_note_metadata unless notes. if includes.any? notes.includes(includes) else notes end end |
#old_assignees(assoc) ⇒ Object
696 697 698 |
# File 'app/models/concerns/issuable.rb', line 696 def old_assignees(assoc) @_old_assignees ||= assoc.fetch(:assignees, assignees) end |
#old_escalation_status(assoc) ⇒ Object
712 713 714 |
# File 'app/models/concerns/issuable.rb', line 712 def old_escalation_status(assoc) @_old_escalation_status ||= assoc.fetch(:escalation_status, escalation_status.status_name) end |
#old_labels(assoc) ⇒ Object
700 701 702 |
# File 'app/models/concerns/issuable.rb', line 700 def old_labels(assoc) @_old_labels ||= assoc.fetch(:labels, labels) end |
#old_severity(assoc) ⇒ Object
704 705 706 |
# File 'app/models/concerns/issuable.rb', line 704 def old_severity(assoc) @_old_severity ||= assoc.fetch(:severity, severity) end |
#old_target_branch(assoc) ⇒ Object
708 709 710 |
# File 'app/models/concerns/issuable.rb', line 708 def old_target_branch(assoc) @_old_target_branch ||= assoc.fetch(:target_branch, target_branch) end |
#old_time_change(assoc) ⇒ Object
720 721 722 |
# File 'app/models/concerns/issuable.rb', line 720 def old_time_change(assoc) @_old_time_change ||= assoc.fetch(:time_change, time_change) end |
#old_total_time_spent(assoc) ⇒ Object
716 717 718 |
# File 'app/models/concerns/issuable.rb', line 716 def old_total_time_spent(assoc) @_old_total_time_spent ||= assoc.fetch(:total_time_spent, total_time_spent) end |
#open? ⇒ Boolean
508 509 510 |
# File 'app/models/concerns/issuable.rb', line 508 def open? opened? end |
#overdue? ⇒ Boolean
512 513 514 515 516 |
# File 'app/models/concerns/issuable.rb', line 512 def overdue? return false unless respond_to?(:due_date) due_date.try(:past?) || false end |
#read_ability_for(participable_source) ⇒ Object
683 684 685 686 687 688 689 690 |
# File 'app/models/concerns/issuable.rb', line 683 def read_ability_for(participable_source) return super if participable_source == self return super if participable_source.is_a?(Note) && participable_source.system? name = participable_source.try(:issuable_ability_name) || :read_issuable_participables { name: name, subject: self } end |
#resource_parent ⇒ Object
491 492 493 |
# File 'app/models/concerns/issuable.rb', line 491 def resource_parent project end |
#state ⇒ Object
483 484 485 |
# File 'app/models/concerns/issuable.rb', line 483 def state self.class.available_states.key(state_id) end |
#state=(value) ⇒ Object
487 488 489 |
# File 'app/models/concerns/issuable.rb', line 487 def state=(value) self.state_id = self.class.available_states[value] end |
#subscribed_without_subscriptions?(user, project) ⇒ Boolean
528 529 530 |
# File 'app/models/concerns/issuable.rb', line 528 def subscribed_without_subscriptions?(user, project) participant?(user) end |
#supports_health_status? ⇒ Boolean
692 693 694 |
# File 'app/models/concerns/issuable.rb', line 692 def supports_health_status? false end |
#to_ability_name ⇒ Object
Convert this Issuable class name to a format usable by Ability definitions
Examples:
issuable.class # => MergeRequest
issuable.to_ability_name # => "merge_request"
613 614 615 |
# File 'app/models/concerns/issuable.rb', line 613 def to_ability_name self.class.to_ability_name end |
#to_hook_data(user, old_associations: {}, action: nil) ⇒ Object
580 581 582 583 584 585 586 587 588 589 |
# File 'app/models/concerns/issuable.rb', line 580 def to_hook_data(user, old_associations: {}, action: nil) changes = reportable_changes if old_associations.present? changes.merge!(hook_association_changes(old_associations)) changes.merge!(hook_reviewer_changes(old_associations)) if allows_reviewers? end Gitlab::DataBuilder::Issuable.new(self).build(user: user, changes: changes, action: action) end |
#updated_tasks ⇒ Object
653 654 655 656 657 658 |
# File 'app/models/concerns/issuable.rb', line 653 def updated_tasks Taskable.get_updated_tasks( old_content: previous_changes['description'].first, new_content: description ) end |
#user_notes_count ⇒ Object
518 519 520 521 522 523 524 525 526 |
# File 'app/models/concerns/issuable.rb', line 518 def user_notes_count if notes.loaded? # Use the in-memory association to select and count to avoid hitting the db notes.to_a.count { |note| !note.system? } else # do the count query notes.user.count end end |