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/bulk_update_service.rb,
app/services/issuable/callbacks/milestone.rb,
app/services/issuable/callbacks/description.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, ImportCsv Classes: BulkUpdateService, CommonSystemNotesService, 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
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_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
- #label_names ⇒ Object
- #labels_array ⇒ Object
- #labels_hook_attrs ⇒ Object
- #notes_for_participants ⇒ 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
-
#reviewers_hook_attrs(re_requested_reviewer_id: nil) ⇒ Object
rubocop:enable Metrics/PerceivedComplexity.
- #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
#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?
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_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?, #store_mentions_after_commit?, #updated_cached_html_for
Methods included from Gitlab::SQL::Pattern
Instance Method Details
#allows_scoped_labels? ⇒ Boolean
626 627 628 |
# File 'app/models/concerns/issuable.rb', line 626 def allows_scoped_labels? false end |
#assignee?(user) ⇒ Boolean
514 515 516 517 518 519 520 521 |
# File 'app/models/concerns/issuable.rb', line 514 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
648 649 650 |
# File 'app/models/concerns/issuable.rb', line 648 def assignee_list assignees.map(&:name).to_sentence end |
#assignee_or_author?(user) ⇒ Boolean
510 511 512 |
# File 'app/models/concerns/issuable.rb', line 510 def (user) == user.id || assignee?(user) end |
#assignee_username_list ⇒ Object
652 653 654 |
# File 'app/models/concerns/issuable.rb', line 652 def assignee_username_list assignees.map(&:username).join(',') end |
#can_move? ⇒ Boolean
Method that checks if issuable can be moved to another project.
Should be overridden if issuable can be moved.
695 696 697 |
# File 'app/models/concerns/issuable.rb', line 695 def can_move?(*) false end |
#card_attributes ⇒ Object
Returns a Hash of attributes to be used for Twitter card metadata
641 642 643 644 645 646 |
# File 'app/models/concerns/issuable.rb', line 641 def card_attributes { 'Author' => .try(:name), 'Assignee' => assignee_list } end |
#draftless_title_changed(old_title) ⇒ Object
Overridden in MergeRequest
709 710 711 |
# File 'app/models/concerns/issuable.rb', line 709 def draftless_title_changed(old_title) old_title != title end |
#first_contribution? ⇒ Boolean
Override in issuable specialization
702 703 704 |
# File 'app/models/concerns/issuable.rb', line 702 def first_contribution? false end |
#hook_association_changes(old_associations) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity – Related issue: gitlab.com/gitlab-org/gitlab/-/issues/437679
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 |
# File 'app/models/concerns/issuable.rb', line 548 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 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
591 592 593 594 595 596 597 598 599 600 601 |
# File 'app/models/concerns/issuable.rb', line 591 def hook_reviewer_changes(old_associations) changes = {} re_requested_reviewer_id = old_associations.fetch(:re_requested_reviewer_id, nil) old_reviewer_attrs = old_associations.fetch(:reviewers_hook_attrs, []) current_reviewer_attrs = reviewers_hook_attrs(re_requested_reviewer_id: re_requested_reviewer_id) changes[:reviewers] = [old_reviewer_attrs, current_reviewer_attrs] if old_reviewer_attrs != current_reviewer_attrs changes end |
#label_names ⇒ Object
618 619 620 |
# File 'app/models/concerns/issuable.rb', line 618 def label_names labels.order('title ASC').pluck(:title) end |
#labels_array ⇒ Object
614 615 616 |
# File 'app/models/concerns/issuable.rb', line 614 def labels_array labels.to_a end |
#labels_hook_attrs ⇒ Object
622 623 624 |
# File 'app/models/concerns/issuable.rb', line 622 def labels_hook_attrs labels.map(&:hook_attrs) end |
#notes_for_participants ⇒ Object
656 657 658 |
# File 'app/models/concerns/issuable.rb', line 656 def notes_for_participants notes_with_associations.limit(Noteable::MAX_NOTES_LIMIT * 2) end |
#notes_with_associations ⇒ Object
660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 |
# File 'app/models/concerns/issuable.rb', line 660 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? unless Feature.enabled?(:remove_per_source_permission_from_participants, Feature.current_request) includes << :project unless notes.projects_loaded? includes << :system_note_metadata unless notes. end if persisted? && includes.any? notes.includes(includes) else notes end end |
#old_assignees(assoc) ⇒ Object
726 727 728 |
# File 'app/models/concerns/issuable.rb', line 726 def old_assignees(assoc) @_old_assignees ||= assoc.fetch(:assignees, assignees) end |
#old_escalation_status(assoc) ⇒ Object
742 743 744 |
# File 'app/models/concerns/issuable.rb', line 742 def old_escalation_status(assoc) @_old_escalation_status ||= assoc.fetch(:escalation_status, escalation_status.status_name) end |
#old_labels(assoc) ⇒ Object
730 731 732 |
# File 'app/models/concerns/issuable.rb', line 730 def old_labels(assoc) @_old_labels ||= assoc.fetch(:labels, labels) end |
#old_severity(assoc) ⇒ Object
734 735 736 |
# File 'app/models/concerns/issuable.rb', line 734 def old_severity(assoc) @_old_severity ||= assoc.fetch(:severity, severity) end |
#old_target_branch(assoc) ⇒ Object
738 739 740 |
# File 'app/models/concerns/issuable.rb', line 738 def old_target_branch(assoc) @_old_target_branch ||= assoc.fetch(:target_branch, target_branch) end |
#old_time_change(assoc) ⇒ Object
750 751 752 |
# File 'app/models/concerns/issuable.rb', line 750 def old_time_change(assoc) @_old_time_change ||= assoc.fetch(:time_change, time_change) end |
#old_total_time_spent(assoc) ⇒ Object
746 747 748 |
# File 'app/models/concerns/issuable.rb', line 746 def old_total_time_spent(assoc) @_old_total_time_spent ||= assoc.fetch(:total_time_spent, total_time_spent) end |
#open? ⇒ Boolean
523 524 525 |
# File 'app/models/concerns/issuable.rb', line 523 def open? opened? end |
#overdue? ⇒ Boolean
527 528 529 530 531 |
# File 'app/models/concerns/issuable.rb', line 527 def overdue? return false unless respond_to?(:due_date) due_date.try(:past?) || false end |
#read_ability_for(participable_source) ⇒ Object
713 714 715 716 717 718 719 720 |
# File 'app/models/concerns/issuable.rb', line 713 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
506 507 508 |
# File 'app/models/concerns/issuable.rb', line 506 def resource_parent project end |
#reviewers_hook_attrs(re_requested_reviewer_id: nil) ⇒ Object
rubocop:enable Metrics/PerceivedComplexity
581 582 583 584 585 586 587 588 589 |
# File 'app/models/concerns/issuable.rb', line 581 def reviewers_hook_attrs(re_requested_reviewer_id: nil) return [] unless allows_reviewers? merge_request_reviewers.includes(:reviewer).map do |mr_reviewer| attrs = mr_reviewer.reviewer.hook_attrs.merge(state: mr_reviewer.state) attrs[:re_requested] = !!(re_requested_reviewer_id && mr_reviewer.reviewer.id == re_requested_reviewer_id) attrs end end |
#state ⇒ Object
498 499 500 |
# File 'app/models/concerns/issuable.rb', line 498 def state self.class.available_states.key(state_id) end |
#state=(value) ⇒ Object
502 503 504 |
# File 'app/models/concerns/issuable.rb', line 502 def state=(value) self.state_id = self.class.available_states[value] end |
#subscribed_without_subscriptions?(user, _project) ⇒ Boolean
543 544 545 |
# File 'app/models/concerns/issuable.rb', line 543 def subscribed_without_subscriptions?(user, _project) participant?(user) end |
#supports_health_status? ⇒ Boolean
722 723 724 |
# File 'app/models/concerns/issuable.rb', line 722 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"
636 637 638 |
# File 'app/models/concerns/issuable.rb', line 636 def to_ability_name self.class.to_ability_name end |
#to_hook_data(user, old_associations: {}, action: nil) ⇒ Object
603 604 605 606 607 608 609 610 611 612 |
# File 'app/models/concerns/issuable.rb', line 603 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
683 684 685 686 687 688 |
# File 'app/models/concerns/issuable.rb', line 683 def updated_tasks Taskable.get_updated_tasks( old_content: previous_changes['description'].first, new_content: description ) end |
#user_notes_count ⇒ Object
533 534 535 536 537 538 539 540 541 |
# File 'app/models/concerns/issuable.rb', line 533 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 |