diff options
author | sunnavy <sunnavy@bestpractical.com> | 2022-10-02 14:11:18 +0300 |
---|---|---|
committer | sunnavy <sunnavy@bestpractical.com> | 2022-10-19 00:25:59 +0300 |
commit | 76f93bf82b0039ae58b352f9becc58c26de4382a (patch) | |
tree | d51bcdbf67bb22553d1387c2a2a1f5059cc0cfe8 | |
parent | 9c386adb1719d811097e5683acac3fcdbf65b671 (diff) |
Support custom roles for asset searches
-rw-r--r-- | lib/RT/Assets.pm | 57 | ||||
-rw-r--r-- | share/html/Asset/Search/Bulk.html | 35 | ||||
-rw-r--r-- | share/html/Search/Elements/BuildFormatString | 13 | ||||
-rw-r--r-- | share/html/Search/Elements/PickBasics | 2 | ||||
-rw-r--r-- | share/html/Search/Elements/PickCustomRoles | 31 | ||||
-rw-r--r-- | share/html/Search/Elements/SelectPersonType | 28 |
6 files changed, 135 insertions, 31 deletions
diff --git a/lib/RT/Assets.pm b/lib/RT/Assets.pm index 046a834e9e..ca7c355be1 100644 --- a/lib/RT/Assets.pm +++ b/lib/RT/Assets.pm @@ -89,6 +89,7 @@ our %FIELD_METADATA = ( HeldByGroup => [ 'MEMBERSHIPFIELD' => 'HeldBy', ], #loc_left_pair Contact => [ 'WATCHERFIELD' => 'Contact', ], #loc_left_pair ContactGroup => [ 'MEMBERSHIPFIELD' => 'Contact', ], #loc_left_pair + CustomRole => [ 'WATCHERFIELD' ], # loc_left_pair CustomFieldValue => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair CustomField => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair @@ -1217,6 +1218,47 @@ sub _StringLimit { ); } +=head2 _CustomRoleDecipher + +Try and turn a custom role descriptor (e.g. C<CustomRole.{Engineer}>) into +(role, column, original name). + +=cut + +sub _CustomRoleDecipher { + my ( $self, $string ) = @_; + + # $column could be core fields like "EmailAddress" or CFs like + # "CustomField.{Department}", the CF format is used in OrderByCols. + my ( $field, $column ) = ( $string =~ /^\{(.+?)\}(?:\.(.+))?$/ ); + + my $role; + + if ( $field =~ /\D/ ) { + my $roles = RT::CustomRoles->new( $self->CurrentUser ); + $roles->LimitToLookupType( RT::Asset->CustomFieldLookupType ); + $roles->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); + + # in case there are multiple matches, bail out as we + # don't know which one to use + $role = $roles->First; + if ($role) { + if ( $roles->Next ) { + RT->Logger->error( + "Ambiguous custom role named '$field' in AssetSQL; skipping. Perhaps specify __CustomRole.{id}__ instead." + ); + $role = undef; + } + } + } + else { + $role = RT::CustomRole->new( $self->CurrentUser ); + $role->Load($field); + } + + return ( $role, $column, $field ); +} + =head2 _WatcherLimit Handle watcher limits. (Requestor, CC, etc..) @@ -1238,18 +1280,25 @@ sub _WatcherLimit { my $meta = $FIELD_METADATA{ $field }; my $type = $meta->[1] || ''; my $class = $meta->[2] || 'Asset'; + my $column = $rest{SUBKEY}; + + if ($field eq 'CustomRole') { + my ($role, $col, $original_name) = $self->_CustomRoleDecipher( $column ); + $column = $col || 'id'; + $type = $role ? $role->GroupType : $original_name; + } # Bail if the subfield is not allowed - if ( $rest{SUBKEY} - and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}}) + if ( $column + and not grep { $_ eq $column } @{$SEARCHABLE_SUBFIELDS{'User'}}) { - die "Invalid watcher subfield: '$rest{SUBKEY}'"; + die "Invalid watcher subfield: '$column'"; } $self->RoleLimit( TYPE => $type, CLASS => "RT::$class", - FIELD => $rest{SUBKEY}, + FIELD => $column, OPERATOR => $op, VALUE => $value, SUBCLAUSE => "assetsql", diff --git a/share/html/Asset/Search/Bulk.html b/share/html/Asset/Search/Bulk.html index f1331b3af2..43b716296d 100644 --- a/share/html/Asset/Search/Bulk.html +++ b/share/html/Asset/Search/Bulk.html @@ -124,29 +124,29 @@ </&> <&| /Widgets/TitleBox, title => loc("People"), class => "asset-people asset-bulk-people", title_class => "inverse" &> -% for my $rname ( $asset->Roles( ACLOnly => 0 ) ) { -% my $role = $asset->Role( $rname ); -% if ( $role->{'Single'} ) { +% for my $rname ( $asset->Roles( ACLOnly => 0, Single => 1 ), map { $_->GroupType } @{ $single_roles->ItemsArrayRef } ) { % my $input = "SetRoleMember-$rname"; <div class="form-row"> <div class="col-6"> - <&| /Elements/LabeledValue, Label => loc($rname) &> + <&| /Elements/LabeledValue, Label => RT::Asset->LabelForRole($rname) &> <input class="form-control" type="text" value="<% $ARGS{ $input } || '' %>" name="<% $input %>" id="<% $input %>" data-autocomplete="Users" data-autocomplete-return="Name" /> </&> </div> </div> -% } else { +% } + +% for my $rname ( $asset->Roles( ACLOnly => 0, Single => 0 ), map { $_->GroupType } @{ $multi_roles->ItemsArrayRef } ) { % my $input = "AddRoleMember-$rname"; <div class="form-row"> <div class="col-6"> - <&| /Elements/LabeledValue, Label => loc("Add [_1]", loc($rname)) &> + <&| /Elements/LabeledValue, Label => loc("Add [_1]", RT::Asset->LabelForRole($rname)) &> <input class="form-control" type="text" value="<% $ARGS{ $input } || '' %>" name="<% $input %>" id="<% $input %>" data-autocomplete="Users" data-autocomplete-return="Name" /> </&> </div> % $input = "RemoveRoleMember-$rname"; <div class="col-6"> - <&| /Elements/LabeledValue, Label => loc("Remove [_1]", loc($rname)) &> + <&| /Elements/LabeledValue, Label => loc("Remove [_1]", RT::Asset->LabelForRole($rname)) &> <input class="form-control" type="text" value="<% $ARGS{ $input } || '' %>" name="<% $input %>" id="<% $input %>" data-autocomplete="Users" data-autocomplete-return="Name" /> <div class="custom-control custom-checkbox"> @@ -157,7 +157,6 @@ </div> </div> % } -% } % my $people_cfs = $cfs->Clone; % $people_cfs->LimitToGrouping( 'RT::Asset' => 'People'); % if ( $people_cfs->Count ) { @@ -229,6 +228,9 @@ delete $ARGS{$_} foreach grep { $ARGS{$_} =~ /^$/ } keys %ARGS; $DECODED_ARGS->{'UpdateAssetAll'} = 1 unless @UpdateAsset; my $cfs; +my $single_roles = RT::CustomRoles->new( $session{CurrentUser} ); +my $multi_roles = RT::CustomRoles->new( $session{CurrentUser} ); + if ( $ARGS{Query} ) { $cfs = RT::CustomFields->new( $session{'CurrentUser'} ); $cfs->LimitToLookupType( RT::Asset->CustomFieldLookupType ); @@ -252,9 +254,26 @@ if ( $ARGS{Query} ) { } } $cfs->LimitToGlobalOrObjectId(@ids); + + if ( @ids ) { + $single_roles->LimitToObjectId($_) for @ids; + $multi_roles->LimitToObjectId($_) for @ids; + } } else { $cfs = $catalog_obj->AssetCustomFields; + $single_roles->LimitToObjectId( $catalog_obj->Id ); + $multi_roles->LimitToObjectId( $catalog_obj->Id ); +} + +if ( $single_roles->_isLimited ) { + $single_roles->LimitToLookupType( RT::Asset->CustomFieldLookupType ); + $single_roles->LimitToSingleValue; +} + +if ( $multi_roles->_isLimited ) { + $multi_roles->LimitToLookupType( RT::Asset->CustomFieldLookupType ); + $multi_roles->LimitToMultipleValue; } if ( $ARGS{'CreateLinkedTicket'} ){ diff --git a/share/html/Search/Elements/BuildFormatString b/share/html/Search/Elements/BuildFormatString index 6211f8369c..12e630d857 100644 --- a/share/html/Search/Elements/BuildFormatString +++ b/share/html/Search/Elements/BuildFormatString @@ -144,6 +144,19 @@ elsif ( $Class eq 'RT::Assets' ) { push @fields, "CustomFieldView.{" . $CustomField->Name . "}"; } + my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'} ); + foreach my $id ( keys %catalogs ) { + + # Gotta load up the $catalog object, since catalogs get stored by name now. + my $catalog = RT::Catalog->new( $session{'CurrentUser'} ); + $catalog->Load($id); + next unless $catalog->Id; + $CustomRoles->LimitToObjectId( $catalog->Id ); + } + $CustomRoles->LimitToLookupType( RT::Asset->CustomFieldLookupType ) if $CustomRoles->_isLimited; + while ( my $role = $CustomRoles->Next ) { + push @fields, 'CustomRole.{' . $role->Name . '}'; + } } else { $Format ||= RT->Config->Get('DefaultSearchResultFormat'); diff --git a/share/html/Search/Elements/PickBasics b/share/html/Search/Elements/PickBasics index 4e134b1e81..22ff9d67a0 100644 --- a/share/html/Search/Elements/PickBasics +++ b/share/html/Search/Elements/PickBasics @@ -275,7 +275,7 @@ elsif ( $Class eq 'RT::Assets' ) { Field => { Type => 'component', Path => 'SelectPersonType', - Arguments => { Default => 'Owner', Class => 'RT::Assets' }, + Arguments => { Default => 'Owner', Class => 'RT::Assets', Catalogs => \%catalogs }, }, Op => { Type => 'component', diff --git a/share/html/Search/Elements/PickCustomRoles b/share/html/Search/Elements/PickCustomRoles index b24956e6fb..ab86e39f04 100644 --- a/share/html/Search/Elements/PickCustomRoles +++ b/share/html/Search/Elements/PickCustomRoles @@ -47,20 +47,35 @@ %# END BPS TAGGED BLOCK }}} <%ARGS> %queues => () +%catalogs => () </%ARGS> <%INIT> RT->Deprecated( Message => '/Search/Elements/PickCustomRoles is obsolete', Remove => '5.2' ); my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'}); -foreach my $id (keys %queues) { - # Gotta load up the $queue object, since queues get stored by name now. - my $queue = RT::Queue->new($session{'CurrentUser'}); - $queue->Load($id); - next unless $queue->Id; - $CustomRoles->LimitToObjectId($queue->Id); +if ( %queues ) { + foreach my $id (keys %queues) { + # Gotta load up the $queue object, since queues get stored by name now. + my $queue = RT::Queue->new($session{'CurrentUser'}); + $queue->Load($id); + next unless $queue->Id; + $CustomRoles->LimitToObjectId($queue->Id); + } + # If there are no referenced queues, do not limit LookupType to return 0 custom roles. + $CustomRoles->LimitToLookupType( RT::Ticket->CustomFieldLookupType ) if $CustomRoles->_isLimited; } -# If there are no referenced queues, do not limit LookupType to return 0 custom roles. -$CustomRoles->LimitToLookupType( RT::Ticket->CustomFieldLookupType ) if $CustomRoles->_isLimited; +elsif ( %catalogs ) { + foreach my $id (keys %catalogs) { + # Gotta load up the $catalog object, since catalogs get stored by name now. + my $catalog = RT::Catalog->new($session{'CurrentUser'}); + $catalog->Load($id); + next unless $catalog->Id; + $CustomRoles->LimitToObjectId($catalog->Id); + } + # If there are no referenced catalogs, do not limit LookupType to return 0 custom roles. + $CustomRoles->LimitToLookupType( RT::Asset->CustomFieldLookupType ) if $CustomRoles->_isLimited; +} + $m->callback( CallbackName => 'MassageCustomRoles', CustomRoles => $CustomRoles, diff --git a/share/html/Search/Elements/SelectPersonType b/share/html/Search/Elements/SelectPersonType index 942a3a408a..9e029f78c6 100644 --- a/share/html/Search/Elements/SelectPersonType +++ b/share/html/Search/Elements/SelectPersonType @@ -83,10 +83,18 @@ <%INIT> my ( @types, @subtypes ); +my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'}); + if ( $Class eq 'RT::Assets' ) { @types = qw(Owner HeldBy Contact); @subtypes = @{ $RT::Assets::SEARCHABLE_SUBFIELDS{'User'} }; + foreach my $id (keys %Catalogs) { + my $catalog = RT::Catalog->new($session{'CurrentUser'}); + $catalog->Load($id); + next unless $catalog->Id; + $CustomRoles->LimitToObjectId($catalog->Id); + } } else { if ($Role) { @@ -106,27 +114,26 @@ else { else { @types = qw(Requestor Cc AdminCc Watcher Owner QueueCc QueueAdminCc QueueWatcher); - my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'}); foreach my $id (keys %Queues) { my $queue = RT::Queue->new($session{'CurrentUser'}); $queue->Load($id); next unless $queue->Id; $CustomRoles->LimitToObjectId($queue->Id); } - - # If there are no referenced queues/catalogs, do not limit LookupType to return 0 custom roles. - $CustomRoles->LimitToLookupType( $Class->RecordClass->CustomFieldLookupType ) if $CustomRoles->_isLimited; - - $m->callback( - CallbackName => 'MassageCustomRoles', - CustomRoles => $CustomRoles, - ); - push @types, map { [ "CustomRole.{" . $_->Name . "}", $_->Name ] } @{ $CustomRoles->ItemsArrayRef }; } @subtypes = @{ $RT::Tickets::SEARCHABLE_SUBFIELDS{'User'} }; } +# If there are no referenced queues/catalogs, do not limit LookupType to return 0 custom roles. +$CustomRoles->LimitToLookupType( $Class->RecordClass->CustomFieldLookupType ) if $CustomRoles->_isLimited; + +$m->callback( + CallbackName => 'MassageCustomRoles', + CustomRoles => $CustomRoles, +); +push @types, map { [ "CustomRole.{" . $_->Name . "}", $_->Name ] } @{ $CustomRoles->ItemsArrayRef }; + $m->callback(Types => \@types, Subtypes => \@subtypes); </%INIT> @@ -140,4 +147,5 @@ $Name => 'WatcherType' $Role => undef @Roles => () %Queues => () +%Catalogs => () </%ARGS> |