Permalink
Browse files

enable more searches, sort by watchers

  • Loading branch information...
1 parent 0551f1b commit 3199dc699ef214e073242f92e794e2460470ce00 @btb committed May 4, 2012
View
52 html/AssetTracker/Search/Elements/SelectPersonType
@@ -1,38 +1,40 @@
%# BEGIN BPS TAGGED BLOCK {{{
-%#
+%#
%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
+%#
+%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+%# <sales@bestpractical.com>
+%#
%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
+%#
+%#
%# LICENSE:
-%#
+%#
%# This work is made available to you under the terms of Version 2 of
%# the GNU General Public License. A copy of that license should have
%# been provided with this software, but in any event can be snarfed
%# from www.gnu.org.
-%#
+%#
%# This work is distributed in the hope that it will be useful, but
%# WITHOUT ANY WARRANTY; without even the implied warranty of
%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%# General Public License for more details.
-%#
+%#
%# You should have received a copy of the GNU General Public License
%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
%# CONTRIBUTION SUBMISSION POLICY:
-%#
+%#
%# (The following paragraph is not intended to limit the rights granted
%# to you to modify and distribute this software under the terms of
%# the GNU General Public License and is only of importance to you if
%# you choose to contribute your changes and enhancements to the
%# community by submitting them to Best Practical Solutions, LLC.)
-%#
+%#
%# By intentionally submitting any modifications, corrections or
%# derivatives to this work, or any other work intended for use with
%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
@@ -41,36 +43,36 @@
%# royalty-free, perpetual, license to use, copy, create derivative
%# works based on those contributions, and sublicense and distribute
%# those contributions and any derivatives thereof.
-%#
+%#
%# END BPS TAGGED BLOCK }}}
-<SELECT NAME ="<%$Name%>">
+<select id="<%$Name%>" name="<%$Name%>">
% if ($AllowNull) {
-<OPTION VALUE="">-</OPTION>
+<option value="">-</option>
% }
% for my $option (@types) {
% if ($Suffix) {
-<OPTION VALUE="<% $option %><% $Suffix %>" <%$option eq $Default && "SELECTED"%> ><%loc($option)%></OPTION>
+<option value="<% $option %><% $Suffix %>"<%$option eq $Default && qq[ selected="selected"] |n %> ><%loc($option)%></option>
% next;
% }
% foreach my $subtype (@subtypes) {
-<OPTION VALUE="<%"$option.$subtype"%>" <%$option eq $Default && $subtype eq 'EmailAddress' && "SELECTED"%> ><% loc($option) %> <% loc($subtype) %></OPTION>
+<option value="<%"$option.$subtype"%>"<%$option eq $Default && $subtype eq 'EmailAddress' && qq[ selected="selected"] |n %> ><% loc($option) %> <% loc($subtype) %></option>
% }
% }
-</SELECT>
+</select>
<%INIT>
my @types;
if ($Scope =~ 'type') {
- @types = qw(Admin);
+ @types = ActiveRoleArray();
}
elsif ($Suffix eq 'Group') {
- @types = ActiveRoleArray();
+ @types = ( ActiveRoleArray(), qw(Watcher) );
}
else {
- @types = ActiveRoleArray();
+ @types = ( ActiveRoleArray(), qw(Watcher), map( { "Type$_" } ActiveRoleArray() ), qw(TypeWatcher) );
}
-my @subtypes = qw(EmailAddress Name RealName Nickname Organization Address1 Address2 WorkPhone HomePhone MobilePhone PagerPhone);
+my @subtypes = @{ $RTx::AssetTracker::Assets::SEARCHABLE_SUBFIELDS{'User'} };
</%INIT>
<%ARGS>
View
1 html/Elements/RTx__AssetTracker__Asset/ColumnMap
@@ -233,6 +233,7 @@ for my $role ( ActiveRoleArray() ) {
my $group_method = $role . 'RoleGroup';
my $export_method = $role . 'RoleGroupExportString';
$ASSET_COLUMN_MAP->{$role} = {
+ attribute => "$role.EmailAddress",
value => sub { return $_[0]->$group_method->MemberEmailAddressesAsString },
export_value => sub { return $_[0]->$export_method },
};
View
315 lib/RTx/AssetTracker/Assets_Overlay.pm
@@ -25,7 +25,7 @@ package RTx::AssetTracker::Assets;
use strict;
no warnings qw(redefine);
-use vars qw( @SORTFIELDS %FIELDS );
+use vars qw( %FIELDS );
use RT::CustomFields;
use File::Temp 'tempdir';
use HTML::Mason;
@@ -68,9 +68,12 @@ use XML::Parser;
Owner => ['WATCHERFIELD' => 'Owner',],
Admin => ['WATCHERFIELD' => 'Admin',],
Watcher => ['WATCHERFIELD'],
+ TypeOwner => [ 'WATCHERFIELD' => 'Owner' => 'Type', ],
+ TypeAdmin => [ 'WATCHERFIELD' => 'Admin' => 'Type', ],
+ TypeWatcher => [ 'WATCHERFIELD' => undef => 'Type', ],
LinkedTo => ['LINKFIELD',],
CustomFieldValue =>['CUSTOMFIELD',],
- CustomField => [ 'CUSTOMFIELD', ], #loc_left_pair
+ CustomField => [ 'CUSTOMFIELD', ],
CF => ['CUSTOMFIELD',],
OwnerGroup => [ 'MEMBERSHIPFIELD' => 'Owner', ],
AdminGroup => [ 'MEMBERSHIPFIELD' => 'Admin', ],
@@ -113,6 +116,13 @@ use XML::Parser;
#}
+our %SEARCHABLE_SUBFIELDS = (
+ User => [qw(
+ EmailAddress Name RealName Nickname Organization Address1 Address2
+ WorkPhone HomePhone MobilePhone PagerPhone id
+ )],
+);
+
# Mapping of Field Type to Function
my %dispatch = (
ENUM => \&_EnumLimit,
@@ -127,7 +137,7 @@ my %dispatch = (
LINKFIELD => \&_LinkFieldLimit,
CUSTOMFIELD => \&_CustomFieldLimit,
);
-my %can_bundle = ( WATCHERFIELD => "yes", );
+my %can_bundle = (); # WATCHERFIELD => "yes", );
$dispatch{IPFIELD} = \&_IPLimit;
$dispatch{PORTFIELD} = \&_PortLimit;
@@ -183,7 +193,7 @@ require RTx::AssetTracker::Assets_Overlay_SQL;
# {{{ sub SortFields
-@SORTFIELDS = qw(id Status Type Description Owner Created LastUpdated ); # Owner?
+our @SORTFIELDS = qw(id Status Type Description Created LastUpdated);
=head2 SortFields
@@ -202,6 +212,21 @@ sub SortFields {
# BEGIN SQL STUFF *********************************
+
+sub CleanSlate {
+ my $self = shift;
+ $self->SUPER::CleanSlate( @_ );
+ delete $self->{$_} foreach qw(
+ _sql_cf_alias
+ _sql_group_members_aliases
+ _sql_object_cfv_alias
+ _sql_role_group_aliases
+ _sql_transalias
+ _sql_trattachalias
+ _sql_u_watchers_alias_for_sort
+ );
+}
+
=head1 Limit Helper Routines
These routines are the targets of a dispatch table depending on the
@@ -756,10 +781,6 @@ Meta Data:
1: Field to query on
-=begin testing
-
-
-=end testing
=cut
@@ -770,137 +791,207 @@ sub _WatcherLimit {
my $value = shift;
my %rest = (@_);
- # Find out what sort of watcher we're looking for
- my $fieldname;
- if ( ref $field ) {
- $fieldname = $field->[0]->[0];
- }
- else {
- $fieldname = $field;
- $field = [ [ $field, $op, $value, %rest ] ]; # gross hack
- }
- my $meta = $FIELDS{$fieldname};
- my $type = ( defined $meta->[1] ? $meta->[1] : undef );
+ my $meta = $FIELDS{ $field };
+ my $type = $meta->[1] || '';
+ my $class = $meta->[2] || 'Asset';
- # Owner was ENUM field, so "Owner = 'xxx'" allowed user to
- # search by id and Name at the same time, this is workaround
- # to preserve backward compatibility
- if ( $fieldname eq 'Owner' ) {
- my $flag = 0;
- for my $chunk ( splice @$field ) {
- my ( $f, $op, $value, %rest ) = @$chunk;
- if ( !$rest{SUBKEY} && $op =~ /^!?=$/ ) {
- $self->_OpenParen unless $flag++;
- my $o = RT::User->new( $self->CurrentUser );
- $o->Load($value);
- $value = $o->Id;
- $self->_SQLLimit(
- FIELD => 'Owner',
- OPERATOR => $op,
- VALUE => $value,
- %rest,
- );
- }
- else {
- push @$field, $chunk;
- }
- }
- $self->_CloseParen if $flag;
- return unless @$field;
+ # Bail if the subfield is not allowed
+ if ( $rest{SUBKEY}
+ and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}})
+ {
+ die "Invalid watcher subfield: '$rest{SUBKEY}'";
}
- my $users = $self->_WatcherJoin($type);
+ $rest{SUBKEY} ||= 'EmailAddress';
- # If we're looking for multiple watchers of a given type,
- # TicketSQL will be handing it to us as an array of clauses in
- # $field
- $self->_OpenParen;
- for my $chunk (@$field) {
- ( $field, $op, $value, %rest ) = @$chunk;
- $rest{SUBKEY} ||= 'EmailAddress';
+ my $groups = $self->_RoleGroupsJoin( Type => $type, Class => $class, New => !$type );
- my $re_negative_op = qr[!=|NOT LIKE];
- $self->_OpenParen if $op =~ /$re_negative_op/;
+ $self->_OpenParen;
+ if ( $op =~ /^IS(?: NOT)?$/ ) {
+ # is [not] empty case
+ my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
+ # to avoid joining the table Users into the query, we just join GM
+ # and make sure we don't match records where group is member of itself
+ $self->SUPER::Limit(
+ LEFTJOIN => $group_members,
+ FIELD => 'GroupId',
+ OPERATOR => '!=',
+ VALUE => "$group_members.MemberId",
+ QUOTEVALUE => 0,
+ );
$self->_SQLLimit(
- ALIAS => $users,
- FIELD => $rest{SUBKEY},
- VALUE => $value,
+ ALIAS => $group_members,
+ FIELD => 'GroupId',
OPERATOR => $op,
- CASESENSITIVE => 0,
- %rest
+ VALUE => $value,
+ %rest,
);
+ }
+ elsif ( $op =~ /^!=$|^NOT\s+/i ) {
+ # negative condition case
+
+ # reverse op
+ $op =~ s/!|NOT\s+//i;
- if ( $op =~ /$re_negative_op/ ) {
+ # XXX: we have no way to build correct "Watcher.X != 'Y'" when condition
+ # "X = 'Y'" matches more then one user so we try to fetch two records and
+ # do the right thing when there is only one exist and semi-working solution
+ # otherwise.
+ my $users_obj = RT::Users->new( $self->CurrentUser );
+ $users_obj->Limit(
+ FIELD => $rest{SUBKEY},
+ OPERATOR => $op,
+ VALUE => $value,
+ );
+ $users_obj->OrderBy;
+ $users_obj->RowsPerPage(2);
+ my @users = @{ $users_obj->ItemsArrayRef };
+
+ my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
+ if ( @users <= 1 ) {
+ my $uid = 0;
+ $uid = $users[0]->id if @users;
+ $self->SUPER::Limit(
+ LEFTJOIN => $group_members,
+ ALIAS => $group_members,
+ FIELD => 'MemberId',
+ VALUE => $uid,
+ );
$self->_SQLLimit(
- ALIAS => $users,
- FIELD => $rest{SUBKEY},
+ %rest,
+ ALIAS => $group_members,
+ FIELD => 'id',
OPERATOR => 'IS',
VALUE => 'NULL',
- ENTRYAGGREGATOR => 'OR',
);
- $self->_CloseParen;
+ } else {
+ $self->SUPER::Limit(
+ LEFTJOIN => $group_members,
+ FIELD => 'GroupId',
+ OPERATOR => '!=',
+ VALUE => "$group_members.MemberId",
+ QUOTEVALUE => 0,
+ );
+ my $users = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $users,
+ ALIAS => $users,
+ FIELD => $rest{SUBKEY},
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ );
+ $self->_SQLLimit(
+ %rest,
+ ALIAS => $users,
+ FIELD => 'id',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ );
}
+ } else {
+ # positive condition case
+
+ my $group_members = $self->_GroupMembersJoin(
+ GroupsAlias => $groups, New => 1, Left => 0
+ );
+ my $users = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id',
+ );
+ $self->_SQLLimit(
+ %rest,
+ ALIAS => $users,
+ FIELD => $rest{'SUBKEY'},
+ VALUE => $value,
+ OPERATOR => $op,
+ CASESENSITIVE => 0,
+ );
}
$self->_CloseParen;
-
}
-=head2 _WatcherJoin
-
-Helper function which provides joins to a watchers table both for limits
-and for ordering.
-
-=cut
-
-sub _WatcherJoin {
+sub _RoleGroupsJoin {
my $self = shift;
- my $type = shift;
-
- # we cache joins chain per watcher type
- # if we limit by requestor then we shouldn't join requestors again
- # for sort or limit on other requestors
- if ( $self->{'_watcher_join_users_alias'}{ $type || 'any' } ) {
- return $self->{'_watcher_join_users_alias'}{ $type || 'any' };
- }
-
-# we always have watcher groups for ticket
-# this join should be NORMAL
-# XXX: if we change this from Join to NewAlias+Limit
-# then Pg will complain because SB build wrong query.
-# Query looks like "FROM (Tickets LEFT JOIN CGM ON(Groups.id = CGM.GroupId)), Groups"
-# Pg doesn't like that fact that it doesn't know about Groups table yet when
-# join CGM table into Tickets. Problem is in Join method which doesn't use
-# ALIAS1 argument when build braces.
+ my %args = (New => 0, Class => 'Asset', Type => '', @_);
+ return $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $args{'Type'} }
+ if $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $args{'Type'} }
+ && !$args{'New'};
+
+ # we always have watcher groups for asset, so we use INNER join
my $groups = $self->Join(
ALIAS1 => 'main',
- FIELD1 => 'id',
+ FIELD1 => $args{'Class'} eq 'Type'? 'Type': 'id',
TABLE2 => 'Groups',
FIELD2 => 'Instance',
- ENTRYAGGREGATOR => 'AND'
+ ENTRYAGGREGATOR => 'AND',
);
$self->SUPER::Limit(
+ LEFTJOIN => $groups,
ALIAS => $groups,
FIELD => 'Domain',
- VALUE => 'RTx::AssetTracker::Asset-Role',
- ENTRYAGGREGATOR => 'AND'
+ VALUE => 'RTx::AssetTracker::'. $args{'Class'} .'-Role',
);
$self->SUPER::Limit(
+ LEFTJOIN => $groups,
ALIAS => $groups,
FIELD => 'Type',
- VALUE => $type,
- ENTRYAGGREGATOR => 'AND'
- )
- if ($type);
+ VALUE => $args{'Type'},
+ ) if $args{'Type'};
- my $groupmembers = $self->Join(
- TYPE => 'LEFT',
- ALIAS1 => $groups,
- FIELD1 => 'id',
- TABLE2 => 'CachedGroupMembers',
- FIELD2 => 'GroupId'
+ $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $args{'Type'} } = $groups
+ unless $args{'New'};
+
+ return $groups;
+}
+
+sub _GroupMembersJoin {
+ my $self = shift;
+ my %args = (New => 1, GroupsAlias => undef, Left => 1, @_);
+
+ return $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} }
+ if $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} }
+ && !$args{'New'};
+
+ my $alias = $self->Join(
+ $args{'Left'} ? (TYPE => 'LEFT') : (),
+ ALIAS1 => $args{'GroupsAlias'},
+ FIELD1 => 'id',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'GroupId',
+ ENTRYAGGREGATOR => 'AND',
);
+ $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias
+ unless $args{'New'};
+
+ return $alias;
+}
+
+=head2 _WatcherJoin
+
+Helper function which provides joins to a watchers table both for limits
+and for ordering.
+
+=cut
+
+sub _WatcherJoin {
+ my $self = shift;
+ my $type = shift || '';
+
+
+ my $groups = $self->_RoleGroupsJoin( Type => $type );
+ my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
# XXX: work around, we must hide groups that
# are members of the role group we search in,
# otherwise them result in wrong NULLs in Users
@@ -909,20 +1000,20 @@ sub _WatcherJoin {
# ticket roles, so we just hide entries in CGM table
# with MemberId == GroupId from results
$self->SUPER::Limit(
- LEFTJOIN => $groupmembers,
+ LEFTJOIN => $group_members,
FIELD => 'GroupId',
OPERATOR => '!=',
- VALUE => "$groupmembers.MemberId",
+ VALUE => "$group_members.MemberId",
QUOTEVALUE => 0,
);
my $users = $self->Join(
- TYPE => 'LEFT',
- ALIAS1 => $groupmembers,
- FIELD1 => 'MemberId',
- TABLE2 => 'Users',
- FIELD2 => 'id'
+ TYPE => 'LEFT',
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id',
);
- return $self->{'_watcher_join_users_alias'}{ $type || 'any' } = $users;
+ return ($groups, $group_members, $users);
}
=head2 _WatcherMembershipLimit

0 comments on commit 3199dc6

Please sign in to comment.