Project

General

Profile

Patch #5535 » Ability_to_use_nobody_in_Assigned_to_field_filter_2.patch

Jan Catrysse, 2025-11-22 00:04

View differences:

app/models/query.rb
607 607
      end
608 608
      principals.uniq!
609 609
      principals.sort!
610 610
      principals.reject! {|p| p.is_a?(GroupBuiltin)}
611 611
      principals
612 612
    end
613 613
  end
614 614

  
615 615
  def users
616 616
    principals.select {|p| p.is_a?(User)}
617 617
  end
618 618

  
619 619
  def author_values
620 620
    author_values = []
621 621
    author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
622 622
    author_values +=
623 623
      users.sort_by{|p| [p.status, p]}.
624 624
        collect{|s| [s.name, s.id.to_s, l("status_#{User::LABEL_BY_STATUS[s.status]}")]}
625 625
    author_values << [l(:label_user_anonymous), User.anonymous.id.to_s]
626 626
    author_values
627 627
  end
628 628

  
629 629
  def assigned_to_values
630 630
    assigned_to_values = []
631 631
    assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
632
    assigned_to_values << ["<< #{l(:label_nobody)} >>", "none"]
632 633
    assigned_to_values +=
633 634
      (Setting.issue_group_assignment? ? principals : users).sort_by{|p| [p.status, p]}.
634 635
        collect{|s| [s.name, s.id.to_s, l("status_#{User::LABEL_BY_STATUS[s.status]}")]}
635 636
    assigned_to_values
636 637
  end
637 638

  
638 639
  def fixed_version_values
639 640
    versions = []
640 641
    if project
641 642
      versions = project.shared_versions.to_a
642 643
    else
643 644
      versions = Version.visible.to_a
644 645
    end
645 646
    Version.sort_by_status(versions).
646 647
      collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s, l("version_status_#{s.status}")]}
647 648
  end
648 649

  
649 650
  # Returns a scope of issue statuses that are available as columns for filters
650 651
  def issue_statuses_values
651 652
    if project
652 653
      statuses = project.rolled_up_statuses
653 654
    else
654 655
      statuses = IssueStatus.all.sorted
655 656
    end
656 657
    statuses.collect{|s| [s.name, s.id.to_s]}
......
983 984
      next unless v and !v.empty?
984 985

  
985 986
      operator = operator_for(field)
986 987

  
987 988
      # "me" value substitution
988 989
      if %w(assigned_to_id author_id user_id watcher_id updated_by last_updated_by).include?(field)
989 990
        if v.delete("me")
990 991
          if User.current.logged?
991 992
            v.push(User.current.id.to_s)
992 993
            v += User.current.group_ids.map(&:to_s) if %w(assigned_to_id watcher_id).include?(field)
993 994
          else
994 995
            v.push("0")
995 996
          end
996 997
        end
997 998
      end
998 999

  
999 1000
      if field == 'project_id' || (is_a?(ProjectQuery) && %w[id parent_id].include?(field))
1000 1001
        if v.delete('mine')
1001 1002
          v += User.current.memberships.map {|m| m.project_id.to_s}
1002 1003
        end
1003 1004
        if v.delete('bookmarks')
1004 1005
          v += User.current.bookmarked_project_ids
1005 1006
        end
1006 1007
      end
1007 1008

  
1009
      include_none = (field == 'assigned_to_id' && operator == '=' && v.delete('none'))
1010

  
1008 1011
      if field =~ /^cf_(\d+)\.cf_(\d+)$/
1009 1012
        filters_clauses << sql_for_chained_custom_field(field, operator, v, $1, $2)
1010 1013
      elsif field =~ /cf_(\d+)$/
1011 1014
        # custom field
1012 1015
        filters_clauses << sql_for_custom_field(field, operator, v, $1)
1013 1016
      elsif field =~ /^cf_(\d+)\.(.+)$/
1014 1017
        filters_clauses << sql_for_custom_field_attribute(field, operator, v, $1, $2)
1015 1018
      elsif respond_to?(method = "sql_for_#{field.tr('.', '_')}_field")
1016 1019
        # specific statement
1017 1020
        filters_clauses << send(method, field, operator, v)
1021
      elsif include_none
1022
        clause = sql_for_field(field, operator, v, queried_table_name, field)
1023
        clause = if v.empty?
1024
                  "#{queried_table_name}.#{field} IS NULL"
1025
                else
1026
                  "(#{queried_table_name}.#{field} IS NULL OR #{clause})"
1027
                end
1028
        filters_clauses << '(' + clause + ')'
1018 1029
      else
1019 1030
        # regular field
1020 1031
        filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
1021 1032
      end
1022 1033
    end if filters and valid?
1023 1034

  
1024 1035
    if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn)
1025 1036
      # Excludes results for which the grouped custom field is not visible
1026 1037
      filters_clauses << c.custom_field.visibility_by_project_condition
1027 1038
    end
1028 1039

  
1029 1040
    filters_clauses << project_statement
1030 1041
    filters_clauses.reject!(&:blank?)
1031 1042

  
1032 1043
    filters_clauses.any? ? filters_clauses.join(' AND ') : nil
1033 1044
  end
1034 1045

  
1035 1046
  # Returns the result count by group or nil if query is not grouped
1036 1047
  def result_count_by_group
1037 1048
    grouped_query do |scope|
1038 1049
      scope.count
1039 1050
    end
1040 1051
  end
1041 1052

  
1042 1053
  # Returns the sum of values for the given column
test/unit/query_test.rb
938 938
    with_settings :issue_group_assignment => '1' do
939 939
      i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
940 940
      i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
941 941
      i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => other_group)
942 942
      query =
943 943
        IssueQuery.new(
944 944
          :name => '_',
945 945
          :filters => {
946 946
            'assigned_to_id' => {
947 947
              :operator => '=',
948 948
              :values => ['me']
949 949
            }
950 950
          }
951 951
        )
952 952
      result = query.issues
953 953
      assert_equal(
954 954
        Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id),
955 955
        result.sort_by(&:id)
956 956
      )
957 957
      assert result.include?(i1)
958 958
      assert result.include?(i2)
959 959
      assert !result.include?(i3)
960 960
    end
961 961
  end
962 962

  
963
  def test_filter_assigned_to_none_or_selected_user
964
    user = User.find(2)
965

  
966
    issue_with_user = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
967
    issue_without_user = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => nil)
968
    issue_with_other_user = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to_id => 3)
969

  
970
    query = IssueQuery.new(:name => '_')
971
    query.add_filter('assigned_to_id', '=', ['none', user.id.to_s])
972

  
973
    results = query.issues
974

  
975
    assert_includes results, issue_with_user
976
    assert_includes results, issue_without_user
977
    assert_not_includes results, issue_with_other_user
978
  end
979

  
963 980
  def test_filter_notes
964 981
    user = User.generate!
965 982
    Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes.')
966 983
    Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes.')
967 984

  
968 985
    issue_journals = Issue.find(1).journals.sort
969 986
    assert_equal ['Journal notes', 'Some notes with Redmine links: #2, r2.'], issue_journals.map(&:notes)
970 987
    assert_equal [false, false], issue_journals.map(&:private_notes)
971 988

  
972 989
    query = IssueQuery.new(:name => '_')
973 990
    filter_name = 'notes'
974 991
    assert_include filter_name, query.available_filters.keys
975 992

  
976 993
    {
977 994
      '~' => [1, 2, 3],
978 995
      '!~' => Issue.ids.sort - [1, 2, 3],
979 996
      '^' => [2, 3],
980 997
      '$' => [1],
981 998
    }.each do |operator, expected|
982 999
      query.filters = {filter_name => {:operator => operator, :values => ['Notes']}}
983 1000
      assert_equal expected, find_issues_with_query(query).map(&:id).sort
984 1001
    end
985 1002
  end
986 1003

  
987 1004
  def test_filter_notes_should_ignore_private_notes_that_are_not_visible
......
2727 2744
      IssueQuery.create!(
2728 2745
        :name => 'Query',
2729 2746
        :type => "IssueQuery",
2730 2747
        :user => User.find(7),
2731 2748
        :filters => {"status_id" => {:values => ["1"], :operator => "o"}},
2732 2749
        :column_names => [:tracker, :status],
2733 2750
        :sort_criteria => ['id', 'asc'],
2734 2751
        :group_by => "project",
2735 2752
        :options => {
2736 2753
          :totalable_names=>[:estimated_hours],
2737 2754
          :draw_relations => '1',
2738 2755
          :draw_progress_line => '1'
2739 2756
        }
2740 2757
      )
2741 2758
    old_attributes = q.attributes
2742 2759
    q.build_from_params({})
2743 2760
    assert_equal old_attributes, q.attributes
2744 2761
  end
2745 2762

  
2746 2763
  test "#available_filters should include users of visible projects in cross-project view" do
2747 2764
    users = IssueQuery.new.available_filters["assigned_to_id"]
2748 2765
    assert_not_nil users
2749 2766
    assert users[:values].pluck(1).include?("3")
2750 2767
  end
2751 2768

  
2769
  def test_assigned_to_filter_values_should_include_nobody
2770
    set_language_if_valid('en')
2771

  
2772
    users = IssueQuery.new.available_filters["assigned_to_id"]
2773

  
2774
    assert_include ["<< #{l(:label_nobody)} >>", 'none'], users[:values]
2775
  end
2776

  
2752 2777
  test "#available_filters should include users of subprojects" do
2753 2778
    user1 = User.generate!
2754 2779
    user2 = User.generate!
2755 2780
    project = Project.find(1)
2756 2781
    Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
2757 2782
    users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
2758 2783
    assert_not_nil users
2759 2784
    assert users[:values].pluck(1).include?(user1.id.to_s)
2760 2785
    assert !users[:values].pluck(1).include?(user2.id.to_s)
2761 2786
  end
2762 2787

  
2763 2788
  test "#available_filters should include visible projects in cross-project view" do
2764 2789
    projects = IssueQuery.new.available_filters["project_id"]
2765 2790
    assert_not_nil projects
2766 2791
    assert projects[:values].pluck(1).include?("1")
2767 2792
  end
2768 2793

  
2769 2794
  test "#available_filters should include 'member_of_group' filter" do
2770 2795
    query = IssueQuery.new
2771 2796
    assert query.available_filters.key?("member_of_group")
2772 2797
    assert_equal :list_optional, query.available_filters["member_of_group"][:type]
2773 2798
    assert query.available_filters["member_of_group"][:values].present?
2774 2799
    assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
2775 2800
                 query.available_filters["member_of_group"][:values].sort
2776 2801
  end
(2-2/2)